diff --git a/.eslintrc.js b/.eslintrc.js index 7af162f349b5c..3d6a5c262c453 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -322,6 +322,7 @@ module.exports = { 'x-pack/test/functional/apps/**/*.js', 'x-pack/legacy/plugins/apm/**/*.js', 'test/*/config.ts', + 'test/*/config_open.ts', 'test/*/{tests,test_suites,apis,apps}/**/*', 'test/visual_regression/tests/**/*', 'x-pack/test/*/{tests,test_suites,apis,apps}/**/*', diff --git a/docs/apm/advanced-queries.asciidoc b/docs/apm/advanced-queries.asciidoc index ed77ebb4c4930..add6f601489e1 100644 --- a/docs/apm/advanced-queries.asciidoc +++ b/docs/apm/advanced-queries.asciidoc @@ -5,7 +5,7 @@ When querying in the APM app, you're simply searching and selecting data from fi Queries entered into the query bar are also added as parameters to the URL, so it's easy to share a specific query or view with others. -In the screenshot below, you can begin to see some of the transaction fields available for filtering on: +You can begin to see some of the transaction fields available for filtering: [role="screenshot"] image::apm/images/apm-query-bar.png[Example of the Kibana Query bar in APM app in Kibana] diff --git a/docs/apm/spans.asciidoc b/docs/apm/spans.asciidoc index b1d54ce49c7cd..b09de576f2d4a 100644 --- a/docs/apm/spans.asciidoc +++ b/docs/apm/spans.asciidoc @@ -12,12 +12,12 @@ This makes it useful for visualizing where the selected transaction spent most o image::apm/images/apm-transaction-sample.png[Example of distributed trace colors in the APM app in Kibana] View a span in detail by clicking on it in the timeline waterfall. -For example, in the below screenshot we've clicked on an SQL Select database query. -The information displayed includes the actual SQL that was executed, how long it took, +When you click on an SQL Select database query, +the information displayed includes the actual SQL that was executed, how long it took, and the percentage of the trace's total time. You also get a stack trace, which shows the SQL query in your code. Finally, APM knows which files are your code and which are just modules or libraries that you've installed. -These library frames will be minimized by default in order to show you the most relevant stack trace. +These library frames will be minimized by default in order to show you the most relevant stack trace. [role="screenshot"] image::apm/images/apm-span-detail.png[Example view of a span detail in the APM app in Kibana] diff --git a/docs/apm/transactions.asciidoc b/docs/apm/transactions.asciidoc index 9c21a569f152c..536ab2ec29c80 100644 --- a/docs/apm/transactions.asciidoc +++ b/docs/apm/transactions.asciidoc @@ -50,7 +50,7 @@ If there's a particular endpoint you're worried about, you can click on it to vi [IMPORTANT] ==== If you only see one route in the Transactions table, or if you have transactions named "unknown route", -it could be a symptom that the agent either wasn't installed correctly or doesn't support your framework. +it could be a symptom that the agent either wasn't installed correctly or doesn't support your framework. For further details, including troubleshooting and custom implementation instructions, refer to the documentation for each {apm-agents-ref}[APM Agent] you've implemented. @@ -103,9 +103,7 @@ The number of requests per bucket is displayed when hovering over the graph, and [role="screenshot"] image::apm/images/apm-transaction-duration-dist.png[Example view of transactions duration distribution graph] -Let's look at an example. -In the screenshot below, -you'll notice most of the requests fall into buckets on the left side of the graph, +Most of the requests fall into buckets on the left side of the graph, with a long tail of smaller buckets to the right. This is a typical distribution, and indicates most of our requests were served quickly - awesome! It's the requests on the right, the ones taking longer than average, that we probably want to focus on. @@ -133,4 +131,4 @@ For a particular transaction sample, we can get even more information in the *me * Custom - You can configure your agent to add custom contextual information on transactions. TIP: All of this data is stored in documents in Elasticsearch. -This means you can select "Actions - View sample document" to see the actual Elasticsearch document under the discover tab. \ No newline at end of file +This means you can select "Actions - View sample document" to see the actual Elasticsearch document under the discover tab. diff --git a/docs/canvas/canvas-tinymath-functions.asciidoc b/docs/canvas/canvas-tinymath-functions.asciidoc index 8c9f445b052a3..73808fc6625d1 100644 --- a/docs/canvas/canvas-tinymath-functions.asciidoc +++ b/docs/canvas/canvas-tinymath-functions.asciidoc @@ -3,21 +3,21 @@ === TinyMath functions TinyMath provides a set of functions that can be used with the Canvas expression -language to perform complex math calculations. Read on for detailed information about -the functions available in TinyMath, including what parameters each function accepts, +language to perform complex math calculations. Read on for detailed information about +the functions available in TinyMath, including what parameters each function accepts, the return value of that function, and examples of how each function behaves. -Most of the functions below accept arrays and apply JavaScript Math methods to -each element of that array. For the functions that accept multiple arrays as -parameters, the function generally does the calculation index by index. +Most of the functions accept arrays and apply JavaScript Math methods to +each element of that array. For the functions that accept multiple arrays as +parameters, the function generally does the calculation index by index. -Any function below can be wrapped by another function as long as the return type +Any function can be wrapped by another function as long as the return type of the inner function matches the acceptable parameter type of the outer function. [float] === abs( a ) -Calculates the absolute value of a number. For arrays, the function will be +Calculates the absolute value of a number. For arrays, the function will be applied index-wise to each element. [cols="3*^<"] @@ -29,7 +29,7 @@ applied index-wise to each element. |a number or an array of numbers |=== -*Returns*: `number` | `Array.`. The absolute value of `a`. Returns +*Returns*: `number` | `Array.`. The absolute value of `a`. Returns an array with the absolute values of each element if `a` is an array. *Example* @@ -43,7 +43,7 @@ abs([-1 , -2, 3, -4]) // returns [1, 2, 3, 4] [float] === add( ...args ) -Calculates the sum of one or more numbers/arrays passed into the function. If at +Calculates the sum of one or more numbers/arrays passed into the function. If at least one array of numbers is passed into the function, the function will calculate the sum by index. [cols="3*^<"] @@ -55,9 +55,9 @@ least one array of numbers is passed into the function, the function will calcul |one or more numbers or arrays of numbers |=== -*Returns*: `number` | `Array.`. The sum of all numbers in `args` if `args` -contains only numbers. Returns an array of sums of the elements at each index, -including all scalar numbers in `args` in the calculation at each index if `args` +*Returns*: `number` | `Array.`. The sum of all numbers in `args` if `args` +contains only numbers. Returns an array of sums of the elements at each index, +including all scalar numbers in `args` in the calculation at each index if `args` contains at least one array. *Throws*: `'Array length mismatch'` if `args` contains arrays of different lengths @@ -73,7 +73,7 @@ add([1, 2], 3, [4, 5], 6) // returns [(1 + 3 + 4 + 6), (2 + 3 + 5 + 6)] = [14, 1 [float] === cbrt( a ) -Calculates the cube root of a number. For arrays, the function will be applied +Calculates the cube root of a number. For arrays, the function will be applied index-wise to each element. [cols="3*^<"] @@ -85,7 +85,7 @@ index-wise to each element. |a number or an array of numbers |=== -*Returns*: `number` | `Array.`. The cube root of `a`. Returns an array with +*Returns*: `number` | `Array.`. The cube root of `a`. Returns an array with the cube roots of each element if `a` is an array. *Example* @@ -99,7 +99,7 @@ cbrt([27, 64, 125]) // returns [3, 4, 5] [float] === ceil( a ) -Calculates the ceiling of a number, i.e., rounds a number towards positive infinity. +Calculates the ceiling of a number, i.e., rounds a number towards positive infinity. For arrays, the function will be applied index-wise to each element. [cols="3*^<"] @@ -111,7 +111,7 @@ For arrays, the function will be applied index-wise to each element. |a number or an array of numbers |=== -*Returns*: `number` | `Array.`. The ceiling of `a`. Returns an array with +*Returns*: `number` | `Array.`. The ceiling of `a`. Returns an array with the ceilings of each element if `a` is an array. *Example* @@ -125,7 +125,7 @@ ceil([1.1, 2.2, 3.3]) // returns [2, 3, 4] [float] === clamp( ...a, min, max ) -Restricts value to a given range and returns closed available value. If only `min` +Restricts value to a given range and returns closed available value. If only `min` is provided, values are restricted to only a lower bound. [cols="3*^<"] @@ -145,11 +145,11 @@ is provided, values are restricted to only a lower bound. |(optional) The maximum value this function will return. |=== -*Returns*: `number` | `Array.`. The closest value between `min` (inclusive) -and `max` (inclusive). Returns an array with values greater than or equal to `min` +*Returns*: `number` | `Array.`. The closest value between `min` (inclusive) +and `max` (inclusive). Returns an array with values greater than or equal to `min` and less than or equal to `max` (if provided) at each index. -*Throws*: +*Throws*: - `'Array length mismatch'` if a `min` and/or `max` are arrays of different lengths @@ -194,7 +194,7 @@ count(100) // returns 1 [float] === cube( a ) -Calculates the cube of a number. For arrays, the function will be applied +Calculates the cube of a number. For arrays, the function will be applied index-wise to each element. [cols="3*^<"] @@ -206,7 +206,7 @@ index-wise to each element. |a number or an array of numbers |=== -*Returns*: `number` | `Array.`. The cube of `a`. Returns an array +*Returns*: `number` | `Array.`. The cube of `a`. Returns an array with the cubes of each element if `a` is an array. *Example* @@ -219,7 +219,7 @@ cube([3, 4, 5]) // returns [27, 64, 125] [float] === divide( a, b ) -Divides two numbers. If at least one array of numbers is passed into the function, +Divides two numbers. If at least one array of numbers is passed into the function, the function will be applied index-wise to each element. [cols="3*^<"] @@ -235,8 +235,8 @@ the function will be applied index-wise to each element. |divisor, a number or an array of numbers, b != 0 |=== -*Returns*: `number` | `Array.`. Returns the quotient of `a` and `b` -if both are numbers. Returns an array with the quotients applied index-wise to +*Returns*: `number` | `Array.`. Returns the quotient of `a` and `b` +if both are numbers. Returns an array with the quotients applied index-wise to each element if `a` or `b` is an array. *Throws*: @@ -257,7 +257,7 @@ divide([14, 42, 65, 108], [2, 7, 5, 12]) // returns [7, 6, 13, 9] [float] === exp( a ) -Calculates _e^x_ where _e_ is Euler's number. For arrays, the function will be applied +Calculates _e^x_ where _e_ is Euler's number. For arrays, the function will be applied index-wise to each element. [cols="3*^<"] @@ -269,7 +269,7 @@ index-wise to each element. |a number or an array of numbers |=== -*Returns*: `number` | `Array.`. Returns an array with the values of +*Returns*: `number` | `Array.`. Returns an array with the values of `e^x` evaluated where `x` is each element of `a` if `a` is an array. *Example* @@ -282,7 +282,7 @@ exp([1, 2, 3]) // returns [e^1, e^2, e^3] = [2.718281828459045, 7.38905609893064 [float] === first( a ) -Returns the first element of an array. If anything other than an array is passed +Returns the first element of an array. If anything other than an array is passed in, the input is returned. [cols="3*^<"] @@ -306,7 +306,7 @@ first([1, 2, 3]) // returns 1 [float] === fix( a ) -Calculates the fix of a number, i.e., rounds a number towards 0. For arrays, the +Calculates the fix of a number, i.e., rounds a number towards 0. For arrays, the function will be applied index-wise to each element. [cols="3*^<"] @@ -318,7 +318,7 @@ function will be applied index-wise to each element. |a number or an array of numbers |=== -*Returns*: `number` | `Array.`. The fix of `a`. Returns an array with +*Returns*: `number` | `Array.`. The fix of `a`. Returns an array with the fixes for each element if `a` is an array. *Example* @@ -332,7 +332,7 @@ fix([1.8, 2.9, -3.7, -4.6]) // returns [1, 2, -3, -4] [float] === floor( a ) -Calculates the floor of a number, i.e., rounds a number towards negative infinity. +Calculates the floor of a number, i.e., rounds a number towards negative infinity. For arrays, the function will be applied index-wise to each element. [cols="3*^<"] @@ -344,7 +344,7 @@ For arrays, the function will be applied index-wise to each element. |a number or an array of numbers |=== -*Returns*: `number` | `Array.`. The floor of `a`. Returns an array +*Returns*: `number` | `Array.`. The floor of `a`. Returns an array with the floor of each element if `a` is an array. *Example* @@ -358,7 +358,7 @@ floor([1.7, 2.8, 3.9]) // returns [1, 2, 3] [float] === last( a ) -Returns the last element of an array. If anything other than an array is passed +Returns the last element of an array. If anything other than an array is passed in, the input is returned. [cols="3*^<"] @@ -382,7 +382,7 @@ last([1, 2, 3]) // returns 3 [float] === log( a, b ) -Calculates the logarithm of a number. For arrays, the function will be applied +Calculates the logarithm of a number. For arrays, the function will be applied index-wise to each element. [cols="3*^<"] @@ -398,7 +398,7 @@ index-wise to each element. |(optional) base for the logarithm. If not provided a value, the default base is e, and the natural log is calculated. |=== -*Returns*: `number` | `Array.`. The logarithm of `a`. Returns an array +*Returns*: `number` | `Array.`. The logarithm of `a`. Returns an array with the the logarithms of each element if `a` is an array. *Throws*: @@ -419,7 +419,7 @@ log([2, 4, 8, 16, 32], 2) // returns [1, 2, 3, 4, 5] [float] === log10( a ) -Calculates the logarithm base 10 of a number. For arrays, the function will be +Calculates the logarithm base 10 of a number. For arrays, the function will be applied index-wise to each element. [cols="3*^<"] @@ -431,7 +431,7 @@ applied index-wise to each element. |a number or an array of numbers, `a` must be greater than 0 |=== -*Returns*: `number` | `Array.`. The logarithm of `a`. Returns an array +*Returns*: `number` | `Array.`. The logarithm of `a`. Returns an array with the the logarithms base 10 of each element if `a` is an array. *Throws*: `'Must be greater than 0'` if `a` < 0 @@ -448,8 +448,8 @@ log([10, 100, 1000, 10000, 100000]) // returns [1, 2, 3, 4, 5] [float] === max( ...args ) -Finds the maximum value of one of more numbers/arrays of numbers passed into the function. -If at least one array of numbers is passed into the function, the function will +Finds the maximum value of one of more numbers/arrays of numbers passed into the function. +If at least one array of numbers is passed into the function, the function will find the maximum by index. [cols="3*^<"] @@ -461,9 +461,9 @@ find the maximum by index. |one or more numbers or arrays of numbers |=== -*Returns*: `number` | `Array.`. The maximum value of all numbers if -`args` contains only numbers. Returns an array with the the maximum values at each -index, including all scalar numbers in `args` in the calculation at each index if +*Returns*: `number` | `Array.`. The maximum value of all numbers if +`args` contains only numbers. Returns an array with the the maximum values at each +index, including all scalar numbers in `args` in the calculation at each index if `args` contains at least one array. *Throws*: `'Array length mismatch'` if `args` contains arrays of different lengths @@ -479,8 +479,8 @@ max([1, 9], 4, [3, 5]) // returns [max([1, 4, 3]), max([9, 4, 5])] = [4, 9] [float] === mean( ...args ) -Finds the mean value of one of more numbers/arrays of numbers passed into the function. -If at least one array of numbers is passed into the function, the function will +Finds the mean value of one of more numbers/arrays of numbers passed into the function. +If at least one array of numbers is passed into the function, the function will find the mean by index. [cols="3*^<"] @@ -492,9 +492,9 @@ find the mean by index. |one or more numbers or arrays of numbers |=== -*Returns*: `number` | `Array.`. The maximum value of all numbers if -`args` contains only numbers. Returns an array with the the maximum values at each -index, including all scalar numbers in `args` in the calculation at each index if +*Returns*: `number` | `Array.`. The maximum value of all numbers if +`args` contains only numbers. Returns an array with the the maximum values at each +index, including all scalar numbers in `args` in the calculation at each index if `args` contains at least one array. *Throws*: `'Array length mismatch'` if `args` contains arrays of different lengths @@ -510,8 +510,8 @@ max([1, 9], 4, [3, 5]) // returns [max([1, 4, 3]), max([9, 4, 5])] = [4, 9] [float] === mean( ...args ) -Finds the mean value of one of more numbers/arrays of numbers passed into the function. -If at least one array of numbers is passed into the function, the function will +Finds the mean value of one of more numbers/arrays of numbers passed into the function. +If at least one array of numbers is passed into the function, the function will find the mean by index. [cols="3*^<"] @@ -523,9 +523,9 @@ find the mean by index. |one or more numbers or arrays of numbers |=== -*Returns*: `number` | `Array.`. The mean value of all numbers if `args` -contains only numbers. Returns an array with the the mean values of each index, -including all scalar numbers in `args` in the calculation at each index if `args` +*Returns*: `number` | `Array.`. The mean value of all numbers if `args` +contains only numbers. Returns an array with the the mean values of each index, +including all scalar numbers in `args` in the calculation at each index if `args` contains at least one array. *Example* @@ -539,8 +539,8 @@ mean([1, 9], 5, [3, 4]) // returns [mean([1, 5, 3]), mean([9, 5, 4])] = [3, 6] [float] === median( ...args ) -Finds the median value(s) of one of more numbers/arrays of numbers passed into the function. -If at least one array of numbers is passed into the function, the function will +Finds the median value(s) of one of more numbers/arrays of numbers passed into the function. +If at least one array of numbers is passed into the function, the function will find the median by index. [cols="3*^<"] @@ -552,9 +552,9 @@ find the median by index. |one or more numbers or arrays of numbers |=== -*Returns*: `number` | `Array.`. The median value of all numbers if `args` -contains only numbers. Returns an array with the the median values of each index, -including all scalar numbers in `args` in the calculation at each index if `args` +*Returns*: `number` | `Array.`. The median value of all numbers if `args` +contains only numbers. Returns an array with the the median values of each index, +including all scalar numbers in `args` in the calculation at each index if `args` contains at least one array. *Example* @@ -569,8 +569,8 @@ median([1, 9], 2, 4, [3, 5]) // returns [median([1, 2, 4, 3]), median([9, 2, 4, [float] === min( ...args ) -Finds the minimum value of one of more numbers/arrays of numbers passed into the function. -If at least one array of numbers is passed into the function, the function will +Finds the minimum value of one of more numbers/arrays of numbers passed into the function. +If at least one array of numbers is passed into the function, the function will find the minimum by index. [cols="3*^<"] @@ -582,9 +582,9 @@ find the minimum by index. |one or more numbers or arrays of numbers |=== -*Returns*: `number` | `Array.`. The minimum value of all numbers if -`args` contains only numbers. Returns an array with the the minimum values of each -index, including all scalar numbers in `args` in the calculation at each index if `a` +*Returns*: `number` | `Array.`. The minimum value of all numbers if +`args` contains only numbers. Returns an array with the the minimum values of each +index, including all scalar numbers in `args` in the calculation at each index if `a` is an array. *Throws*: `'Array length mismatch'` if `args` contains arrays of different lengths. @@ -600,7 +600,7 @@ min([1, 9], 4, [3, 5]) // returns [min([1, 4, 3]), min([9, 4, 5])] = [1, 4] [float] === mod( a, b ) -Remainder after dividing two numbers. If at least one array of numbers is passed +Remainder after dividing two numbers. If at least one array of numbers is passed into the function, the function will be applied index-wise to each element. [cols="3*^<"] @@ -616,8 +616,8 @@ into the function, the function will be applied index-wise to each element. |divisor, a number or an array of numbers, b != 0 |=== -*Returns*: `number` | `Array.`. The remainder of `a` divided by `b` if -both are numbers. Returns an array with the the remainders applied index-wise to +*Returns*: `number` | `Array.`. The remainder of `a` divided by `b` if +both are numbers. Returns an array with the the remainders applied index-wise to each element if `a` or `b` is an array. *Throws*: @@ -638,8 +638,8 @@ mod([14, 42, 65, 108], [5, 4, 14, 2]) // returns [5, 2, 9, 0] [float] === mode( ...args ) -Finds the mode value(s) of one of more numbers/arrays of numbers passed into the function. -If at least one array of numbers is passed into the function, the function will +Finds the mode value(s) of one of more numbers/arrays of numbers passed into the function. +If at least one array of numbers is passed into the function, the function will find the mode by index. [cols="3*^<"] @@ -651,9 +651,9 @@ find the mode by index. |one or more numbers or arrays of numbers |=== -*Returns*: `number` | `Array.>`. An array of mode value(s) of all -numbers if `args` contains only numbers. Returns an array of arrays with mode value(s) -of each index, including all scalar numbers in `args` in the calculation at each index +*Returns*: `number` | `Array.>`. An array of mode value(s) of all +numbers if `args` contains only numbers. Returns an array of arrays with mode value(s) +of each index, including all scalar numbers in `args` in the calculation at each index if `args` contains at least one array. *Example* @@ -668,7 +668,7 @@ mode([1, 9], 1, 4, [3, 5]) // returns [mode([1, 1, 4, 3]), mode([9, 1, 4, 5])] = [float] === multiply( a, b ) -Multiplies two numbers. If at least one array of numbers is passed into the function, +Multiplies two numbers. If at least one array of numbers is passed into the function, the function will be applied index-wise to each element. [cols="3*^<"] @@ -684,11 +684,11 @@ the function will be applied index-wise to each element. |a number or an array of numbers |=== -*Returns*: `number` | `Array.`. The product of `a` and `b` if both are -numbers. Returns an array with the the products applied index-wise to each element +*Returns*: `number` | `Array.`. The product of `a` and `b` if both are +numbers. Returns an array with the the products applied index-wise to each element if `a` or `b` is an array. -*Throws*: `'Array length mismatch'` if `a` and `b` are arrays with different lengths +*Throws*: `'Array length mismatch'` if `a` and `b` are arrays with different lengths *Example* [source, js] @@ -702,7 +702,7 @@ multiply([1, 2, 3, 4], [2, 7, 5, 12]) // returns [2, 14, 15, 48] [float] === pow( a, b ) -Calculates the cube root of a number. For arrays, the function will be applied +Calculates the cube root of a number. For arrays, the function will be applied index-wise to each element. [cols="3*^<"] @@ -718,7 +718,7 @@ index-wise to each element. |the power that `a` is raised to |=== -*Returns*: `number` | `Array.`. `a` raised to the power of `b`. Returns +*Returns*: `number` | `Array.`. `a` raised to the power of `b`. Returns an array with the each element raised to the power of `b` if `a` is an array. *Throws*: `'Missing exponent'` if `b` is not provided @@ -733,8 +733,8 @@ pow([1, 2, 3], 4) // returns [1, 16, 81] [float] === random( a, b ) -Generates a random number within the given range where the lower bound is inclusive -and the upper bound is exclusive. If no numbers are passed in, it will return a +Generates a random number within the given range where the lower bound is inclusive +and the upper bound is exclusive. If no numbers are passed in, it will return a number between 0 and 1. If only one number is passed in, it will return a number between 0 and the number passed in. @@ -751,11 +751,11 @@ between 0 and the number passed in. |(optional) must be greater than `a` |=== -*Returns*: `number`. A random number between 0 and 1 if no numbers are passed in. -Returns a random number between 0 and `a` if only one number is passed in. Returns +*Returns*: `number`. A random number between 0 and 1 if no numbers are passed in. +Returns a random number between 0 and `a` if only one number is passed in. Returns a random number between `a` and `b` if two numbers are passed in. -*Throws*: `'Min must be greater than max'` if `a` < 0 when only `a` is passed in +*Throws*: `'Min must be greater than max'` if `a` < 0 when only `a` is passed in or if `a` > `b` when both `a` and `b` are passed in *Example* @@ -769,8 +769,8 @@ random(-10,10) // returns a random number between -10 (inclusive) and 10 (exclus [float] === range( ...args ) -Finds the range of one of more numbers/arrays of numbers passed into the function. If at -least one array of numbers is passed into the function, the function will find +Finds the range of one of more numbers/arrays of numbers passed into the function. If at +least one array of numbers is passed into the function, the function will find the range by index. [cols="3*^<"] @@ -782,9 +782,9 @@ the range by index. |one or more numbers or arrays of numbers |=== -*Returns*: `number` | `Array.`. The range value of all numbers if `args` -contains only numbers. Returns an array with the range values at each index, -including all scalar numbers in `args` in the calculation at each index if `args` +*Returns*: `number` | `Array.`. The range value of all numbers if `args` +contains only numbers. Returns an array with the range values at each index, +including all scalar numbers in `args` in the calculation at each index if `args` contains at least one array. *Example* @@ -798,8 +798,8 @@ range([1, 9], 4, [3, 5]) // returns [range([1, 4, 3]), range([9, 4, 5])] = [3, 5 [float] === range( ...args ) -Finds the range of one of more numbers/arrays of numbers into the function. If at -least one array of numbers is passed into the function, the function will find +Finds the range of one of more numbers/arrays of numbers into the function. If at +least one array of numbers is passed into the function, the function will find the range by index. [cols="3*^<"] @@ -811,9 +811,9 @@ the range by index. |one or more numbers or arrays of numbers |=== -*Returns*: `number` | `Array.`. The range value of all numbers if `args` -contains only numbers. Returns an array with the the range values at each index, -including all scalar numbers in `args` in the calculation at each index if `args` +*Returns*: `number` | `Array.`. The range value of all numbers if `args` +contains only numbers. Returns an array with the the range values at each index, +including all scalar numbers in `args` in the calculation at each index if `args` contains at least one array. *Example* @@ -827,7 +827,7 @@ range([1, 9], 4, [3, 5]) // returns [range([1, 4, 3]), range([9, 4, 5])] = [3, 5 [float] === round( a, b ) -Rounds a number towards the nearest integer by default, or decimal place (if passed in as `b`). +Rounds a number towards the nearest integer by default, or decimal place (if passed in as `b`). For arrays, the function will be applied index-wise to each element. [cols="3*^<"] @@ -843,7 +843,7 @@ For arrays, the function will be applied index-wise to each element. |(optional) number of decimal places, default value: 0 |=== -*Returns*: `number` | `Array.`. The rounded value of `a`. Returns an +*Returns*: `number` | `Array.`. The rounded value of `a`. Returns an array with the the rounded values of each element if `a` is an array. *Example* @@ -885,7 +885,7 @@ size(100) // returns 1 [float] === sqrt( a ) -Calculates the square root of a number. For arrays, the function will be applied +Calculates the square root of a number. For arrays, the function will be applied index-wise to each element. [cols="3*^<"] @@ -897,7 +897,7 @@ index-wise to each element. |a number or an array of numbers |=== -*Returns*: `number` | `Array.`. The square root of `a`. Returns an array +*Returns*: `number` | `Array.`. The square root of `a`. Returns an array with the the square roots of each element if `a` is an array. *Throws*: `'Unable find the square root of a negative number'` if `a` < 0 @@ -913,7 +913,7 @@ sqrt([9, 16, 25]) // returns [3, 4, 5] [float] === square( a ) -Calculates the square of a number. For arrays, the function will be applied +Calculates the square of a number. For arrays, the function will be applied index-wise to each element. [cols="3*^<"] @@ -925,7 +925,7 @@ index-wise to each element. |a number or an array of numbers |=== -*Returns*: `number` | `Array.`. The square of `a`. Returns an array +*Returns*: `number` | `Array.`. The square of `a`. Returns an array with the the squares of each element if `a` is an array. *Example* @@ -938,7 +938,7 @@ square([3, 4, 5]) // returns [9, 16, 25] [float] === subtract( a, b ) -Subtracts two numbers. If at least one array of numbers is passed into the function, +Subtracts two numbers. If at least one array of numbers is passed into the function, the function will be applied index-wise to each element. [cols="3*^<"] @@ -954,7 +954,7 @@ the function will be applied index-wise to each element. |a number or an array of numbers |=== -*Returns*: `number` | `Array.`. The difference of `a` and `b` if both are +*Returns*: `number` | `Array.`. The difference of `a` and `b` if both are numbers, or an array of differences applied index-wise to each element. *Throws*: `'Array length mismatch'` if `a` and `b` are arrays with different lengths @@ -971,11 +971,11 @@ subtract([14, 42, 65, 108], [2, 7, 5, 12]) // returns [12, 35, 52, 96] [float] === sum( ...args ) -Calculates the sum of one or more numbers/arrays passed into the function. If at -least one array is passed, the function will sum up one or more numbers/arrays of +Calculates the sum of one or more numbers/arrays passed into the function. If at +least one array is passed, the function will sum up one or more numbers/arrays of numbers and distinct values of an array. Sum accepts arrays of different lengths. -*Returns*: `number`. The sum of one or more numbers/arrays of numbers including +*Returns*: `number`. The sum of one or more numbers/arrays of numbers including distinct values in arrays *Example* @@ -992,7 +992,7 @@ sum([10, 20, 30, 40], 10, [1, 2, 3], 22) // returns sum(10, 20, 30, 40, 10, 1, 2 Counts the number of unique values in an array. -*Returns*: `number`. The number of unique values in the array. Returns 1 if `a` +*Returns*: `number`. The number of unique values in the array. Returns 1 if `a` is not an array. *Example* @@ -1003,4 +1003,3 @@ unique([]) // returns 0 unique([1, 2, 3, 4]) // returns 4 unique([1, 2, 3, 4, 2, 2, 2, 3, 4, 2, 4, 5, 2, 1, 4, 2]) // returns 5 ------------ - diff --git a/docs/dev-tools/searchprofiler/more-complicated.asciidoc b/docs/dev-tools/searchprofiler/more-complicated.asciidoc index a0771f4a0f240..338341d65924d 100644 --- a/docs/dev-tools/searchprofiler/more-complicated.asciidoc +++ b/docs/dev-tools/searchprofiler/more-complicated.asciidoc @@ -25,11 +25,11 @@ POST test/_bulk // CONSOLE -- -. From the {searchprofiler}, enter "test" in the *Index* field to restrict profiled +. From the {searchprofiler}, enter "test" in the *Index* field to restrict profiled queries to the `test` index. . Replace the default `match_all` query in the query editor with a query that has two sub-query -components and includes a simple aggregation, like the example below. +components and includes a simple aggregation: + -- [source,js] diff --git a/docs/developer/core/development-functional-tests.asciidoc b/docs/developer/core/development-functional-tests.asciidoc index 77a2bfe77b4ab..dcb3d65b8b83f 100644 --- a/docs/developer/core/development-functional-tests.asciidoc +++ b/docs/developer/core/development-functional-tests.asciidoc @@ -69,7 +69,7 @@ node scripts/functional_tests_server.js node ../scripts/functional_test_runner.js ---------- -** Selenium tests are run in headless mode on CI. Locally the same tests will be executed in a real browser. You can activate headless mode by setting the environment variable below: +** Selenium tests are run in headless mode on CI. Locally the same tests will be executed in a real browser. You can activate headless mode by setting the environment variable: + ["source", "shell"] ---------- @@ -181,7 +181,7 @@ node scripts/functional_test_runner --config test/functional/config.firefox.js [float] ===== Anatomy of a test file -The annotated example file below shows the basic structure every test suite uses. It starts by importing https://github.com/elastic/kibana/tree/master/packages/kbn-expect[`@kbn/expect`] and defining its default export: an anonymous Test Provider. The test provider then destructures the Provider API for the `getService()` and `getPageObjects()` functions. It uses these functions to collect the dependencies of this suite. The rest of the test file will look pretty normal to mocha.js users. `describe()`, `it()`, `before()` and the lot are used to define suites that happen to automate a browser via services and objects of type `PageObject`. +This annotated example file shows the basic structure every test suite uses. It starts by importing https://github.com/elastic/kibana/tree/master/packages/kbn-expect[`@kbn/expect`] and defining its default export: an anonymous Test Provider. The test provider then destructures the Provider API for the `getService()` and `getPageObjects()` functions. It uses these functions to collect the dependencies of this suite. The rest of the test file will look pretty normal to mocha.js users. `describe()`, `it()`, `before()` and the lot are used to define suites that happen to automate a browser via services and objects of type `PageObject`. ["source","js"] ---- diff --git a/docs/developer/plugin/development-plugin-feature-registration.asciidoc b/docs/developer/plugin/development-plugin-feature-registration.asciidoc index 2c686964d369a..ca61e5309ce85 100644 --- a/docs/developer/plugin/development-plugin-feature-registration.asciidoc +++ b/docs/developer/plugin/development-plugin-feature-registration.asciidoc @@ -46,7 +46,7 @@ Registering a feature consists of the following fields. For more information, co |`privileges` (required) |{repo}blob/{branch}/x-pack/plugins/features/server/feature.ts[`FeatureWithAllOrReadPrivileges`]. -|see examples below +|See <> and <> |The set of privileges this feature requires to function. |`icon` @@ -80,6 +80,7 @@ if (canUserSave) { } ----------- +[[example-1-canvas]] ==== Example 1: Canvas Application ["source","javascript"] ----------- @@ -134,6 +135,7 @@ if (canUserSave) { Because the `read` privilege does not define the `save` capability, users with read-only access will have their `uiCapabilities.canvas.save` flag set to `false`. +[[example-2-dev-tools]] ==== Example 2: Dev Tools ["source","javascript"] diff --git a/docs/developer/plugin/development-plugin-localization.asciidoc b/docs/developer/plugin/development-plugin-localization.asciidoc index 78ee933f681f4..1fb8b6aa0cbde 100644 --- a/docs/developer/plugin/development-plugin-localization.asciidoc +++ b/docs/developer/plugin/development-plugin-localization.asciidoc @@ -161,7 +161,7 @@ Full details are {repo}tree/master/packages/kbn-i18n#angularjs[here]. To learn more about i18n tooling, see {blob}src/dev/i18n/README.md[i18n dev tooling]. -To learn more about implementing i18n in the UI, follow the links below: +To learn more about implementing i18n in the UI, use the following links: * {blob}packages/kbn-i18n/README.md[i18n plugin] * {blob}packages/kbn-i18n/GUIDELINE.md[i18n guidelines] diff --git a/docs/development/core/public/kibana-plugin-core-public.iuisettingsclient.getall.md b/docs/development/core/public/kibana-plugin-core-public.iuisettingsclient.getall.md index 805ac57b2fb9a..004979977376e 100644 --- a/docs/development/core/public/kibana-plugin-core-public.iuisettingsclient.getall.md +++ b/docs/development/core/public/kibana-plugin-core-public.iuisettingsclient.getall.md @@ -9,5 +9,5 @@ Gets the metadata about all uiSettings, including the type, default value, and u Signature: ```typescript -getAll: () => Readonly>; +getAll: () => Readonly>; ``` diff --git a/docs/development/core/public/kibana-plugin-core-public.iuisettingsclient.md b/docs/development/core/public/kibana-plugin-core-public.iuisettingsclient.md index da566ed25cff5..87ef5784a6c6d 100644 --- a/docs/development/core/public/kibana-plugin-core-public.iuisettingsclient.md +++ b/docs/development/core/public/kibana-plugin-core-public.iuisettingsclient.md @@ -18,7 +18,7 @@ export interface IUiSettingsClient | --- | --- | --- | | [get](./kibana-plugin-core-public.iuisettingsclient.get.md) | <T = any>(key: string, defaultOverride?: T) => T | Gets the value for a specific uiSetting. If this setting has no user-defined value then the defaultOverride parameter is returned (and parsed if setting is of type "json" or "number). If the parameter is not defined and the key is not registered by any plugin then an error is thrown, otherwise reads the default value defined by a plugin. | | [get$](./kibana-plugin-core-public.iuisettingsclient.get_.md) | <T = any>(key: string, defaultOverride?: T) => Observable<T> | Gets an observable of the current value for a config key, and all updates to that config key in the future. Providing a defaultOverride argument behaves the same as it does in \#get() | -| [getAll](./kibana-plugin-core-public.iuisettingsclient.getall.md) | () => Readonly<Record<string, UiSettingsParams & UserProvidedValues>> | Gets the metadata about all uiSettings, including the type, default value, and user value for each key. | +| [getAll](./kibana-plugin-core-public.iuisettingsclient.getall.md) | () => Readonly<Record<string, PublicUiSettingsParams & UserProvidedValues>> | Gets the metadata about all uiSettings, including the type, default value, and user value for each key. | | [getSaved$](./kibana-plugin-core-public.iuisettingsclient.getsaved_.md) | <T = any>() => Observable<{
key: string;
newValue: T;
oldValue: T;
}> | Returns an Observable that notifies subscribers of each update to the uiSettings, including the key, newValue, and oldValue of the setting that changed. | | [getUpdate$](./kibana-plugin-core-public.iuisettingsclient.getupdate_.md) | <T = any>() => Observable<{
key: string;
newValue: T;
oldValue: T;
}> | Returns an Observable that notifies subscribers of each update to the uiSettings, including the key, newValue, and oldValue of the setting that changed. | | [getUpdateErrors$](./kibana-plugin-core-public.iuisettingsclient.getupdateerrors_.md) | () => Observable<Error> | Returns an Observable that notifies subscribers of each error while trying to update the settings, containing the actual Error class. | diff --git a/docs/development/core/public/kibana-plugin-core-public.md b/docs/development/core/public/kibana-plugin-core-public.md index bafc2eb3a4bc9..a9fbaa25ea150 100644 --- a/docs/development/core/public/kibana-plugin-core-public.md +++ b/docs/development/core/public/kibana-plugin-core-public.md @@ -147,6 +147,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [MountPoint](./kibana-plugin-core-public.mountpoint.md) | A function that should mount DOM content inside the provided container element and return a handler to unmount it. | | [PluginInitializer](./kibana-plugin-core-public.plugininitializer.md) | The plugin export at the root of a plugin's public directory should conform to this interface. | | [PluginOpaqueId](./kibana-plugin-core-public.pluginopaqueid.md) | | +| [PublicUiSettingsParams](./kibana-plugin-core-public.publicuisettingsparams.md) | A sub-set of [UiSettingsParams](./kibana-plugin-core-public.uisettingsparams.md) exposed to the client-side. | | [RecursiveReadonly](./kibana-plugin-core-public.recursivereadonly.md) | | | [SavedObjectAttribute](./kibana-plugin-core-public.savedobjectattribute.md) | Type definition for a Saved Object attribute value | | [SavedObjectAttributeSingle](./kibana-plugin-core-public.savedobjectattributesingle.md) | Don't use this type, it's simply a helper type for [SavedObjectAttribute](./kibana-plugin-core-public.savedobjectattribute.md) | diff --git a/docs/development/core/public/kibana-plugin-core-public.publicuisettingsparams.md b/docs/development/core/public/kibana-plugin-core-public.publicuisettingsparams.md new file mode 100644 index 0000000000000..678a69289ff23 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.publicuisettingsparams.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [PublicUiSettingsParams](./kibana-plugin-core-public.publicuisettingsparams.md) + +## PublicUiSettingsParams type + +A sub-set of [UiSettingsParams](./kibana-plugin-core-public.uisettingsparams.md) exposed to the client-side. + +Signature: + +```typescript +export declare type PublicUiSettingsParams = Omit; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.uisettingsparams.md b/docs/development/core/public/kibana-plugin-core-public.uisettingsparams.md index 00f1c0f0deca5..e7facb4a109cd 100644 --- a/docs/development/core/public/kibana-plugin-core-public.uisettingsparams.md +++ b/docs/development/core/public/kibana-plugin-core-public.uisettingsparams.md @@ -9,7 +9,7 @@ UiSettings parameters defined by the plugins. Signature: ```typescript -export interface UiSettingsParams +export interface UiSettingsParams ``` ## Properties @@ -24,7 +24,8 @@ export interface UiSettingsParams | [options](./kibana-plugin-core-public.uisettingsparams.options.md) | string[] | array of permitted values for this setting | | [readonly](./kibana-plugin-core-public.uisettingsparams.readonly.md) | boolean | a flag indicating that value cannot be changed | | [requiresPageReload](./kibana-plugin-core-public.uisettingsparams.requirespagereload.md) | boolean | a flag indicating whether new value applying requires page reloading | +| [schema](./kibana-plugin-core-public.uisettingsparams.schema.md) | Type<T> | | | [type](./kibana-plugin-core-public.uisettingsparams.type.md) | UiSettingsType | defines a type of UI element [UiSettingsType](./kibana-plugin-core-public.uisettingstype.md) | | [validation](./kibana-plugin-core-public.uisettingsparams.validation.md) | ImageValidation | StringValidation | | -| [value](./kibana-plugin-core-public.uisettingsparams.value.md) | SavedObjectAttribute | default value to fall back to if a user doesn't provide any | +| [value](./kibana-plugin-core-public.uisettingsparams.value.md) | T | default value to fall back to if a user doesn't provide any | diff --git a/docs/development/core/public/kibana-plugin-core-public.uisettingsparams.schema.md b/docs/development/core/public/kibana-plugin-core-public.uisettingsparams.schema.md new file mode 100644 index 0000000000000..f90d5161f96a9 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.uisettingsparams.schema.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [UiSettingsParams](./kibana-plugin-core-public.uisettingsparams.md) > [schema](./kibana-plugin-core-public.uisettingsparams.schema.md) + +## UiSettingsParams.schema property + +Signature: + +```typescript +schema: Type; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.uisettingsparams.value.md b/docs/development/core/public/kibana-plugin-core-public.uisettingsparams.value.md index 8775588290d70..2740f169eeecb 100644 --- a/docs/development/core/public/kibana-plugin-core-public.uisettingsparams.value.md +++ b/docs/development/core/public/kibana-plugin-core-public.uisettingsparams.value.md @@ -9,5 +9,5 @@ default value to fall back to if a user doesn't provide any Signature: ```typescript -value?: SavedObjectAttribute; +value?: T; ``` diff --git a/docs/development/core/server/kibana-plugin-core-server.iuisettingsclient.getregistered.md b/docs/development/core/server/kibana-plugin-core-server.iuisettingsclient.getregistered.md index 2ca6b4cbe1589..71a2bbf88472e 100644 --- a/docs/development/core/server/kibana-plugin-core-server.iuisettingsclient.getregistered.md +++ b/docs/development/core/server/kibana-plugin-core-server.iuisettingsclient.getregistered.md @@ -9,5 +9,5 @@ Returns registered uiSettings values [UiSettingsParams](./kibana-plugin-core-ser Signature: ```typescript -getRegistered: () => Readonly>; +getRegistered: () => Readonly>; ``` diff --git a/docs/development/core/server/kibana-plugin-core-server.iuisettingsclient.md b/docs/development/core/server/kibana-plugin-core-server.iuisettingsclient.md index 42fcc81419cbe..af99b5e5bb215 100644 --- a/docs/development/core/server/kibana-plugin-core-server.iuisettingsclient.md +++ b/docs/development/core/server/kibana-plugin-core-server.iuisettingsclient.md @@ -18,7 +18,7 @@ export interface IUiSettingsClient | --- | --- | --- | | [get](./kibana-plugin-core-server.iuisettingsclient.get.md) | <T = any>(key: string) => Promise<T> | Retrieves uiSettings values set by the user with fallbacks to default values if not specified. | | [getAll](./kibana-plugin-core-server.iuisettingsclient.getall.md) | <T = any>() => Promise<Record<string, T>> | Retrieves a set of all uiSettings values set by the user with fallbacks to default values if not specified. | -| [getRegistered](./kibana-plugin-core-server.iuisettingsclient.getregistered.md) | () => Readonly<Record<string, UiSettingsParams>> | Returns registered uiSettings values [UiSettingsParams](./kibana-plugin-core-server.uisettingsparams.md) | +| [getRegistered](./kibana-plugin-core-server.iuisettingsclient.getregistered.md) | () => Readonly<Record<string, PublicUiSettingsParams>> | Returns registered uiSettings values [UiSettingsParams](./kibana-plugin-core-server.uisettingsparams.md) | | [getUserProvided](./kibana-plugin-core-server.iuisettingsclient.getuserprovided.md) | <T = any>() => Promise<Record<string, UserProvidedValues<T>>> | Retrieves a set of all uiSettings values set by the user. | | [isOverridden](./kibana-plugin-core-server.iuisettingsclient.isoverridden.md) | (key: string) => boolean | Shows whether the uiSettings value set by the user. | | [remove](./kibana-plugin-core-server.iuisettingsclient.remove.md) | (key: string) => Promise<void> | Removes uiSettings value by key. | diff --git a/docs/development/core/server/kibana-plugin-core-server.md b/docs/development/core/server/kibana-plugin-core-server.md index 8a88329031e1f..54cf496b2d6af 100644 --- a/docs/development/core/server/kibana-plugin-core-server.md +++ b/docs/development/core/server/kibana-plugin-core-server.md @@ -232,6 +232,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [PluginInitializer](./kibana-plugin-core-server.plugininitializer.md) | The plugin export at the root of a plugin's server directory should conform to this interface. | | [PluginName](./kibana-plugin-core-server.pluginname.md) | Dedicated type for plugin name/id that is supposed to make Map/Set/Arrays that use it as a key or value more obvious. | | [PluginOpaqueId](./kibana-plugin-core-server.pluginopaqueid.md) | | +| [PublicUiSettingsParams](./kibana-plugin-core-server.publicuisettingsparams.md) | A sub-set of [UiSettingsParams](./kibana-plugin-core-server.uisettingsparams.md) exposed to the client-side. | | [RecursiveReadonly](./kibana-plugin-core-server.recursivereadonly.md) | | | [RedirectResponseOptions](./kibana-plugin-core-server.redirectresponseoptions.md) | HTTP response parameters for redirection response | | [RequestHandler](./kibana-plugin-core-server.requesthandler.md) | A function executed when route path matched requested resource path. Request handler is expected to return a result of one of [KibanaResponseFactory](./kibana-plugin-core-server.kibanaresponsefactory.md) functions. | diff --git a/docs/development/core/server/kibana-plugin-core-server.publicuisettingsparams.md b/docs/development/core/server/kibana-plugin-core-server.publicuisettingsparams.md new file mode 100644 index 0000000000000..4ccc91fbe1f74 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.publicuisettingsparams.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [PublicUiSettingsParams](./kibana-plugin-core-server.publicuisettingsparams.md) + +## PublicUiSettingsParams type + +A sub-set of [UiSettingsParams](./kibana-plugin-core-server.uisettingsparams.md) exposed to the client-side. + +Signature: + +```typescript +export declare type PublicUiSettingsParams = Omit; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.uisettingsparams.md b/docs/development/core/server/kibana-plugin-core-server.uisettingsparams.md index fe6e5d956f3e2..f134decb5102b 100644 --- a/docs/development/core/server/kibana-plugin-core-server.uisettingsparams.md +++ b/docs/development/core/server/kibana-plugin-core-server.uisettingsparams.md @@ -9,7 +9,7 @@ UiSettings parameters defined by the plugins. Signature: ```typescript -export interface UiSettingsParams +export interface UiSettingsParams ``` ## Properties @@ -24,7 +24,8 @@ export interface UiSettingsParams | [options](./kibana-plugin-core-server.uisettingsparams.options.md) | string[] | array of permitted values for this setting | | [readonly](./kibana-plugin-core-server.uisettingsparams.readonly.md) | boolean | a flag indicating that value cannot be changed | | [requiresPageReload](./kibana-plugin-core-server.uisettingsparams.requirespagereload.md) | boolean | a flag indicating whether new value applying requires page reloading | +| [schema](./kibana-plugin-core-server.uisettingsparams.schema.md) | Type<T> | | | [type](./kibana-plugin-core-server.uisettingsparams.type.md) | UiSettingsType | defines a type of UI element [UiSettingsType](./kibana-plugin-core-server.uisettingstype.md) | | [validation](./kibana-plugin-core-server.uisettingsparams.validation.md) | ImageValidation | StringValidation | | -| [value](./kibana-plugin-core-server.uisettingsparams.value.md) | SavedObjectAttribute | default value to fall back to if a user doesn't provide any | +| [value](./kibana-plugin-core-server.uisettingsparams.value.md) | T | default value to fall back to if a user doesn't provide any | diff --git a/docs/development/core/server/kibana-plugin-core-server.uisettingsparams.schema.md b/docs/development/core/server/kibana-plugin-core-server.uisettingsparams.schema.md new file mode 100644 index 0000000000000..f181fbd309b7f --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.uisettingsparams.schema.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [UiSettingsParams](./kibana-plugin-core-server.uisettingsparams.md) > [schema](./kibana-plugin-core-server.uisettingsparams.schema.md) + +## UiSettingsParams.schema property + +Signature: + +```typescript +schema: Type; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.uisettingsparams.value.md b/docs/development/core/server/kibana-plugin-core-server.uisettingsparams.value.md index ca00cd0cd6396..78c8f0c8fcf8d 100644 --- a/docs/development/core/server/kibana-plugin-core-server.uisettingsparams.value.md +++ b/docs/development/core/server/kibana-plugin-core-server.uisettingsparams.value.md @@ -9,5 +9,5 @@ default value to fall back to if a user doesn't provide any Signature: ```typescript -value?: SavedObjectAttribute; +value?: T; ``` diff --git a/docs/discover/kuery.asciidoc b/docs/discover/kuery.asciidoc index c835c15028074..48a7c65bdbf15 100644 --- a/docs/discover/kuery.asciidoc +++ b/docs/discover/kuery.asciidoc @@ -2,13 +2,13 @@ === Kibana Query Language In Kibana 6.3, we introduced a number of exciting experimental query language enhancements. These -features are now available by default in 7.0. Out of the box, Kibana's query language now includes scripted field support and a -simplified, easier to use syntax. If you have a Basic license or above, autocomplete functionality will also be enabled. +features are now available by default in 7.0. Out of the box, Kibana's query language now includes scripted field support and a +simplified, easier to use syntax. If you have a Basic license or above, autocomplete functionality will also be enabled. ==== Language Syntax -If you're familiar with Kibana's old lucene query syntax, you should feel right at home with the new syntax. The basics -stay the same, we've simply refined things to make the query language easier to use. Read about the changes below. +If you're familiar with Kibana's old Lucene query syntax, you should feel right at home with the new syntax. The basics +stay the same, we've simply refined things to make the query language easier to use. `response:200` will match documents where the response field matches the value 200. @@ -19,8 +19,8 @@ they appear. This means documents with "quick brown fox" will match, but so will to search for a phrase. The query parser will no longer split on whitespace. Multiple search terms must be separated by explicit -boolean operators. Lucene will combine search terms with an `or` by default, so `response:200 extension:php` would -become `response:200 or extension:php` in KQL. This will match documents where response matches 200, extension matches php, or both. +boolean operators. Lucene will combine search terms with an `or` by default, so `response:200 extension:php` would +become `response:200 or extension:php` in KQL. This will match documents where response matches 200, extension matches php, or both. Note that boolean operators are not case sensitive. We can make terms required by using `and`. @@ -48,9 +48,9 @@ Entire groups can also be inverted. `response:200 and not (extension:php or extension:css)` -Ranges are similar to lucene with a small syntactical difference. +Ranges are similar to lucene with a small syntactical difference. -Instead of `bytes:>1000`, we omit the colon: `bytes > 1000`. +Instead of `bytes:>1000`, we omit the colon: `bytes > 1000`. `>, >=, <, <=` are all valid range operators. @@ -76,15 +76,15 @@ in the response field, but a query for just `200` will search for 200 across all 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. +* *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. Each document in the nested +Let's take a look at the first approach. In the following document, `items` is a nested field. Each document in the nested field contains a name, stock, and category. [source,json] @@ -122,7 +122,7 @@ To find stores that have more than 10 bananas in stock, you would write a query `items:{ name:banana and stock > 10 }` -`items` is the "nested path". Everything inside the curly braces (the "nested group") must match a single nested document. +`items` is the "nested path". Everything inside the curly braces (the "nested group") must match a single nested document. The following example returns no matches because no single nested document has bananas with a stock of 9. @@ -138,7 +138,7 @@ The subqueries in this example are in separate nested groups and can match diffe ==== Combine approaches -You can combine these two approaches to create complex queries. What if you wanted to find a store with more than 10 +You can combine these two approaches to create complex queries. What if you wanted to find a store with more than 10 bananas that *also* stocks vegetables? You could do this: `items:{ name:banana and stock > 10 } and items:{ category:vegetable }` diff --git a/docs/discover/search.asciidoc b/docs/discover/search.asciidoc index 9c4e406455c27..21ae4560fba94 100644 --- a/docs/discover/search.asciidoc +++ b/docs/discover/search.asciidoc @@ -56,7 +56,7 @@ query language you can also submit queries using the {ref}/query-dsl.html[Elasti [[save-open-search]] === Saving searches -A saved search persists your current view of Discover for later retrieval and reuse. You can reload a saved search into Discover, add it to a dashboard, and use it as the basis for a <>. +A saved search persists your current view of Discover for later retrieval and reuse. You can reload a saved search into Discover, add it to a dashboard, and use it as the basis for a <>. A saved search includes the query text, filters, and optionally, the time filter. A saved search also includes the selected columns in the document table, the sort order, and the current index pattern. @@ -164,12 +164,9 @@ You can import, export, and delete saved queries from <>. +index pattern are searched. +To change the indices you are searching, click the index pattern and select a +different <>. [[autorefresh]] === Refresh the search results @@ -180,7 +177,7 @@ retrieve the latest results. . Click image:images/time-filter-calendar.png[]. -. In the *Refresh every* field, enter the refresh rate, then select the interval +. In the *Refresh every* field, enter the refresh rate, then select the interval from the dropdown. . Click *Start*. @@ -189,5 +186,5 @@ image::images/autorefresh-intervals.png[] To disable auto refresh, click *Stop*. -If auto refresh is not enabled, click *Refresh* to manually refresh the search +If auto refresh is not enabled, click *Refresh* to manually refresh the search results. diff --git a/docs/epm/index.asciidoc b/docs/epm/index.asciidoc index 46d45b85690e3..d2ebe003afd6b 100644 --- a/docs/epm/index.asciidoc +++ b/docs/epm/index.asciidoc @@ -47,12 +47,12 @@ A user-specified string that will be used to part of the index name in Elasticse ==== Package -A package contains all the assets for the Elastic Stack. A more detailed definition of a package can be found under https://github.com/elastic/package-registry . +A package contains all the assets for the Elastic Stack. A more detailed definition of a package can be found under https://github.com/elastic/package-registry. == Indexing Strategy -Ingest Management enforces an indexing strategy to allow the system to automically detect indices and run queries on it. In short the indexing strategy looks as following: +Ingest Management enforces an indexing strategy to allow the system to automatically detect indices and run queries on it. In short the indexing strategy looks as following: ``` {type}-{dataset}-{namespace} @@ -85,7 +85,7 @@ The version is included in each pipeline to allow upgrades. The pipeline itself === Templates & ILM Policies -To make the above strategy possible, alias templates are required. For each type there is a basic alias template with a default ILM policy. These default templates apply to all indices which follow the indexing strategy and do not have a more specific dataset alias template. +To make the above strategy possible, alias templates are required. For each type there is a basic alias template with a default ILM policy. These default templates apply to all indices which follow the indexing strategy and do not have a more specific dataset alias template. The `metrics` and `logs` alias template contain all the basic fields from ECS. @@ -109,7 +109,7 @@ Filtering for data in queries for example in visualizations or dashboards should === Security permissions -Security permissions can be set on different levels. To set special permissions for the access on the prod namespace an index pattern as below can be used: +Security permissions can be set on different levels. To set special permissions for the access on the prod namespace, use the following index pattern: ``` /(logs|metrics)-[^-]+-prod-$/ @@ -142,5 +142,3 @@ The new ingest pipeline is expected to still work with the data coming from olde In case of a breaking change in the data structure, the new ingest pipeline is also expected to deal with this change. In case there are breaking changes which cannot be dealt with in an ingest pipeline, a new package has to be created. Each package lists its minimal required agent version. In case there are agents enrolled with an older version, the user is notified to upgrade these agents as otherwise the new configs cannot be rolled out. - - diff --git a/docs/management/numeral.asciidoc b/docs/management/numeral.asciidoc index 65dfdab3abd3c..5d4d48ca785e1 100644 --- a/docs/management/numeral.asciidoc +++ b/docs/management/numeral.asciidoc @@ -19,7 +19,7 @@ The numeral pattern syntax expresses: Number of decimal places:: The `.` character turns on the option to show decimal places using a locale-specific decimal separator, most often `.` or `,`. To add trailing zeroes such as `5.00`, use a pattern like `0.00`. -To have optional zeroes, use the `[]` characters. Examples below. +To have optional zeroes, use the `[]` characters. Thousands separator:: The thousands separator `,` turns on the option to group thousands using a locale-specific separator. The separator is most often `,` or `.`, and sometimes ` `. diff --git a/docs/management/rollups/create_and_manage_rollups.asciidoc b/docs/management/rollups/create_and_manage_rollups.asciidoc index 565c179b741f1..6a56970687fd6 100644 --- a/docs/management/rollups/create_and_manage_rollups.asciidoc +++ b/docs/management/rollups/create_and_manage_rollups.asciidoc @@ -70,11 +70,7 @@ This allows for more granular queries, such as 2h and 12h. [float] ==== Create the rollup job -As you walk through the *Create rollup job* UI, enter the data shown in -the table below. The terms, histogram, and metrics fields reflect -the key information to retain in the rolled up data: where visitors are from (geo.src), -what operating system they are using (machine.os.keyword), -and how much data is being sent (bytes). +As you walk through the *Create rollup job* UI, enter the data: |=== |*Field* |*Value* @@ -118,6 +114,10 @@ and how much data is being sent (bytes). |bytes (average) |=== +The terms, histogram, and metrics fields reflect +the key information to retain in the rolled up data: where visitors are from (geo.src), +what operating system they are using (machine.os.keyword), +and how much data is being sent (bytes). You can now use the rolled up data for analysis at a fraction of the storage cost of the original index. The original data can live side by side with the new diff --git a/docs/maps/geojson-upload.asciidoc b/docs/maps/geojson-upload.asciidoc index 8c3cb371b6add..ad20264f56138 100644 --- a/docs/maps/geojson-upload.asciidoc +++ b/docs/maps/geojson-upload.asciidoc @@ -14,7 +14,7 @@ GeoJSON is the most commonly used and flexible option. [float] === Upload a GeoJSON file -Follow the instructions below to upload a GeoJSON data file, or try the +Follow these instructions to upload a GeoJSON data file, or try the <>. . Open *Elastic Maps*, and then click *Add layer*. diff --git a/docs/maps/indexing-geojson-data-tutorial.asciidoc b/docs/maps/indexing-geojson-data-tutorial.asciidoc index 22b736032cb79..a94e5757d5dfa 100644 --- a/docs/maps/indexing-geojson-data-tutorial.asciidoc +++ b/docs/maps/indexing-geojson-data-tutorial.asciidoc @@ -46,7 +46,7 @@ image::maps/images/fu_gs_new_england_map.png[] === Upload and index GeoJSON files For each GeoJSON file you downloaded, complete the following steps: -. Below the map legend, click *Add layer*. +. Click *Add layer*. . From the list of layer types, click *Uploaded GeoJSON*. . Using the File Picker, upload the GeoJSON file. + @@ -86,7 +86,7 @@ hot spots are. An advantage of having indexed {ref}/geo-point.html[geo_point] data for the lightning strikes is that you can perform aggregations on the data. -. Below the map legend, click *Add layer*. +. Click *Add layer*. . From the list of layer types, click *Grid aggregation*. + Because you indexed `lightning_detected.geojson` using the index name and diff --git a/docs/maps/vector-style.asciidoc b/docs/maps/vector-style.asciidoc index 509b1fae4066a..80e4c4ed5f844 100644 --- a/docs/maps/vector-style.asciidoc +++ b/docs/maps/vector-style.asciidoc @@ -12,7 +12,7 @@ For each property, you can specify whether to use a constant or data driven valu Use static styling to specificy a constant value for a style property. -The image below shows an example of static styling using the <> data set. +This image shows an example of static styling using the <> data set. The *kibana_sample_data_logs* layer uses static styling for all properties. [role="screenshot"] @@ -26,7 +26,7 @@ image::maps/images/vector_style_static.png[] Use data driven styling to symbolize features by property values. To enable data driven styling for a style property, change the selected value from *Fixed* or *Solid* to *By value*. -The image below shows an example of data driven styling using the <> data set. +This image shows an example of data driven styling using the <> data set. The *kibana_sample_data_logs* layer uses data driven styling for fill color and symbol size style properties. * The `hour_of_day` property determines the fill color for each feature based on where the value fits on a linear scale. @@ -87,7 +87,7 @@ Qualitative data driven styling is available for the following styling propertie Qualitative data driven styling uses a {ref}/search-aggregations-bucket-terms-aggregation.html[terms aggregation] to retrieve the top nine categories for the property. Feature values within the top categories are assigned a unique color. Feature values outside of the top categories are grouped into the *Other* category. A feature is assigned the *Other* category when the property value is undefined. -The image below shows an example of quantitative data driven styling using the <> data set. +This image shows an example of quantitative data driven styling using the <> data set. The `machine.os.keyword` property determines the color of each symbol based on category. [role="screenshot"] @@ -101,7 +101,7 @@ image::maps/images/quantitative_data_driven_styling.png[] Class styling symbolizes features by class and requires multiple layers. Use <> to define the class for each layer, and <> to symbolize each class. -The image below shows an example of class styling using the <> data set. +This image shows an example of class styling using the <> data set. * The *Mac OS requests* layer applies the filter `machine.os : osx` so the layer only contains Mac OS requests. The fill color is a static value of green. diff --git a/docs/migration/migrate_8_0.asciidoc b/docs/migration/migrate_8_0.asciidoc index a34f956ace263..ce4c97391f1b5 100644 --- a/docs/migration/migrate_8_0.asciidoc +++ b/docs/migration/migrate_8_0.asciidoc @@ -19,16 +19,16 @@ See also <> and <>. [float] [[breaking_80_index_pattern_changes]] -=== Index pattern changes +=== Index pattern changes [float] ==== Removed support for time-based internal index patterns -*Details:* Time-based interval index patterns were deprecated in 5.x. In 6.x, -you could no longer create time-based interval index patterns, but they continued +*Details:* Time-based interval index patterns were deprecated in 5.x. In 6.x, +you could no longer create time-based interval index patterns, but they continued to function as expected. Support for these index patterns has been removed in 8.0. -*Impact:* You must migrate your time_based index patterns to a wildcard pattern, -for example, `logstash-*`. +*Impact:* You must migrate your time_based index patterns to a wildcard pattern, +for example, `logstash-*`. [float] @@ -76,7 +76,7 @@ specified explicitly. [float] ==== `/api/security/v1/saml` endpoint is no longer supported -*Details:* The deprecated `/api/security/v1/saml` endpoint is no longer supported. +*Details:* The deprecated `/api/security/v1/saml` endpoint is no longer supported. *Impact:* Rely on `/api/security/saml/callback` endpoint when using SAML instead. This change should be reflected in Kibana `server.xsrf.whitelist` config as well as in Elasticsearch and Identity Provider SAML settings. @@ -108,7 +108,7 @@ access level. [float] ==== Legacy job parameters are no longer supported -*Details:* POST URL snippets that were copied in Kibana 6.2 or below are no longer supported. These logs have +*Details:* POST URL snippets that were copied in Kibana 6.2 or earlier are no longer supported. These logs have been deprecated with warnings that have been logged throughout 7.x. Please use Kibana UI to re-generate the POST URL snippets if you depend on these for automated PDF reports. diff --git a/docs/settings/apm-settings.asciidoc b/docs/settings/apm-settings.asciidoc index a6eeffec51cb0..91bbef5690fd5 100644 --- a/docs/settings/apm-settings.asciidoc +++ b/docs/settings/apm-settings.asciidoc @@ -32,7 +32,7 @@ image::settings/images/apm-settings.png[APM app settings in Kibana] // tag::general-apm-settings[] If you'd like to change any of the default values, -copy and paste the relevant settings below into your `kibana.yml` configuration file. +copy and paste the relevant settings into your `kibana.yml` configuration file. xpack.apm.enabled:: Set to `false` to disabled the APM plugin {kib}. Defaults to `true`. diff --git a/docs/settings/ssl-settings.asciidoc b/docs/settings/ssl-settings.asciidoc index 5341d3543e7c6..3a0a474d9d597 100644 --- a/docs/settings/ssl-settings.asciidoc +++ b/docs/settings/ssl-settings.asciidoc @@ -44,7 +44,7 @@ Java Cryptography Architecture documentation]. Defaults to the value of The following settings are used to specify a private key, certificate, and the trusted certificates that should be used when communicating over an SSL/TLS connection. -If none of the settings below are specified, the default values are used. +If none of the settings are specified, the default values are used. See {ref}/security-settings.html[Default TLS/SSL settings]. ifdef::server[] @@ -54,8 +54,8 @@ ifndef::server[] A private key and certificate are optional and would be used if the server requires client authentication for PKI authentication. endif::server[] -If none of the settings below are specified, the defaults values are used. -See {ref}/security-settings.html[Default TLS/SSL settings]. +If none of the settings bare specified, the defaults values are used. +See {ref}/security-settings.html[Default TLS/SSL settings]. [float] ===== PEM encoded files diff --git a/docs/uptime-guide/install.asciidoc b/docs/uptime-guide/install.asciidoc index 5d32a26529f86..e7c50bb7604ce 100644 --- a/docs/uptime-guide/install.asciidoc +++ b/docs/uptime-guide/install.asciidoc @@ -20,7 +20,7 @@ then jump straight to <>. === Install the stack yourself If you'd rather install the stack yourself, -first see the https://www.elastic.co/support/matrix[Elastic Support Matrix] for information about supported operating systems and product compatibility. Then, follow the steps below. +first see the https://www.elastic.co/support/matrix[Elastic Support Matrix] for information about supported operating systems and product compatibility. * <> * <> diff --git a/docs/uptime-guide/security.asciidoc b/docs/uptime-guide/security.asciidoc index 6651b33ea0e0e..0c6fa4c6c4f56 100644 --- a/docs/uptime-guide/security.asciidoc +++ b/docs/uptime-guide/security.asciidoc @@ -1,9 +1,8 @@ [[uptime-security]] == Elasticsearch Security -If you use Elasticsearch security, you'll need to enable certain privileges for users -that would like to access the Uptime app. Below is an example of creating -a user and support role to implement those privileges. +If you use Elasticsearch security, you'll need to enable certain privileges for users +that would like to access the Uptime app. For example, create user and support roles to implement the privileges: [float] === Create a role diff --git a/docs/user/graph/getting-started.asciidoc b/docs/user/graph/getting-started.asciidoc index 7b3bd10147966..1749678ace9e3 100644 --- a/docs/user/graph/getting-started.asciidoc +++ b/docs/user/graph/getting-started.asciidoc @@ -2,7 +2,7 @@ [[graph-getting-started]] == Using Graph -You must index data into {es} before you can create a graph. +You must index data into {es} before you can create a graph. <> or get started with a <>. [float] @@ -11,24 +11,24 @@ You must index data into {es} before you can create a graph. . From the side navigation, open *Graph*. + -If this is your first graph, follow the prompts to create it. +If this is your first graph, follow the prompts to create it. For subsequent graphs, click *New*. . Select a data source to explore. . Add one or more multi-value fields that contain the terms you want to -graph. +graph. + The vertices in the graph are selected from these terms. . Enter a search query to discover relationships between terms in the selected -fields. +fields. + -For example, if you are using the {kib} sample web logs data set, and you want +For example, if you are using the {kib} sample web logs data set, and you want to generate a graph of the successful requests to particular pages from different locations, you could search for the 200 response code. The weight of the connection between two vertices indicates how strongly they -are related. +are related. + [role="screenshot"] image::user/graph/images/graph-url-connections.png["URL connections"] @@ -45,11 +45,11 @@ additional connections: image:user/graph/images/graph-expand-button.png[Expand Selection]. * To display additional connections between the displayed vertices, click the link icon -image:user/graph/images/graph-link-button.png[Add links to existing terms]. +image:user/graph/images/graph-link-button.png[Add links to existing terms]. * To explore a particular area of the graph, select the vertices you are interested in, and then click expand or link. * To step back through your changes to the graph, click undo -image:user/graph/images/graph-undo-button.png[Undo] and redo +image:user/graph/images/graph-undo-button.png[Undo] and redo image:user/graph/images/graph-redo-button.png[Redo]. . To see more relationships in your data, submit additional queries. @@ -63,61 +63,61 @@ image::user/graph/images/graph-add-query.png["Adding networks"] [[style-vertex-properties]] === Style vertex properties -Each vertex has a color, icon, and label. To change -the color or icon of all vertices -of a certain field, click the field badge below the search bar, and then +Each vertex has a color, icon, and label. To change +the color or icon of all vertices +of a certain field, click it's badge, and then select *Edit settings*. -To change the color and label of selected vertices, +To change the color and label of selected vertices, click the style icon image:user/graph/images/graph-style-button.png[Style] -in the control bar on the right. +in the control bar on the right. [float] [[edit-graph-settings]] === Edit graph settings -By default, *Graph* is configured to tune out noise in your data. +By default, *Graph* is configured to tune out noise in your data. If this isn't a good fit for your data, use *Settings > Advanced settings* -to adjust the way *Graph* queries your data. You can tune the graph to show -only the results relevant to you and to improve performance. -For more information, see <>. +to adjust the way *Graph* queries your data. You can tune the graph to show +only the results relevant to you and to improve performance. +For more information, see <>. -You can configure the number of vertices that a search or +You can configure the number of vertices that a search or expand operation adds to the graph. -By default, only the five most relevant terms for any given field are added -at a time. This keeps the graph from overflowing. To increase this number, click -a field below the search bar, select *Edit Settings*, and change *Terms per hop*. +By default, only the five most relevant terms for any given field are added +at a time. This keeps the graph from overflowing. To increase this number, click +a field, select *Edit Settings*, and change *Terms per hop*. [float] [[graph-block-terms]] === Block terms from the graph -Documents that match a blocked term are not allowed in the graph. -To block a term, select its vertex and click +Documents that match a blocked term are not allowed in the graph. +To block a term, select its vertex and click the block icon image:user/graph/images/graph-block-button.png[Block selection] -in the control panel. +in the control panel. For a list of blocked terms, go to *Settings > Blocked terms*. [float] [[graph-drill-down]] === Drill down into raw documents -With drilldowns, you can display additional information about a -selected vertex in a new browser window. For example, you might -configure a drilldown URL to perform a web search for the selected vertex term. +With drilldowns, you can display additional information about a +selected vertex in a new browser window. For example, you might +configure a drilldown URL to perform a web search for the selected vertex term. -Use the drilldown icon image:user/graph/images/graph-info-icon.png[Drilldown selection] +Use the drilldown icon image:user/graph/images/graph-info-icon.png[Drilldown selection] in the control panel to show the drilldown buttons for the selected vertices. -To configure drilldowns, go to *Settings > Drilldowns*. See also +To configure drilldowns, go to *Settings > Drilldowns*. See also <>. [float] [[graph-run-layout]] === Run and pause layout -Graph uses a "force layout", where vertices behave like magnets, -pushing off of one another. By default, when you add a new vertex to -the graph, all vertices begin moving. In some cases, the movement might -go on for some time. To freeze the current vertex position, +Graph uses a "force layout", where vertices behave like magnets, +pushing off of one another. By default, when you add a new vertex to +the graph, all vertices begin moving. In some cases, the movement might +go on for some time. To freeze the current vertex position, click the pause icon image:user/graph/images/graph-pause-button.png[Block selection] -in the control panel. +in the control panel. diff --git a/docs/user/monitoring/beats-details.asciidoc b/docs/user/monitoring/beats-details.asciidoc index 672ed6226e427..0b2be4dd9e3d9 100644 --- a/docs/user/monitoring/beats-details.asciidoc +++ b/docs/user/monitoring/beats-details.asciidoc @@ -13,7 +13,7 @@ image::user/monitoring/images/monitoring-beats.jpg["Monitoring Beats",link="imag To view an overview of the Beats data in the cluster, click *Overview*. The overview page has a section for activity in the last day, which is a real-time -sample of data. Below that, a summary bar and charts follow the typical paradigm +sample of data. The summary bar and charts follow the typical paradigm of data in the Monitoring UI, which is bound to the span of the time filter in the top right corner of the page. This overview page can therefore show up-to-date or historical information. diff --git a/docs/user/security/rbac_tutorial.asciidoc b/docs/user/security/rbac_tutorial.asciidoc index e4dbdc2483f70..d45aae86a9ccb 100644 --- a/docs/user/security/rbac_tutorial.asciidoc +++ b/docs/user/security/rbac_tutorial.asciidoc @@ -10,10 +10,10 @@ Kibana spaces. ==== Scenario Our user is a web developer working on a bank's -online mortgage service. The web developer has these +online mortgage service. The web developer has these three requirements: -* Have access to the data for that service +* Have access to the data for that service * Build visualizations and dashboards * Monitor the performance of the system @@ -24,28 +24,28 @@ You'll provide the web developer with the access and privileges to get the job d To complete this tutorial, you'll need the following: -* **Administrative privileges**: You must have a role that grants privileges to create a space, role, and user. This is any role which grants the `manage_security` cluster privilege. By default, the `superuser` role provides this access. See the {ref}/built-in-roles.html[built-in] roles. -* **A space**: In this tutorial, use `Dev Mortgage` as the space +* **Administrative privileges**: You must have a role that grants privileges to create a space, role, and user. This is any role which grants the `manage_security` cluster privilege. By default, the `superuser` role provides this access. See the {ref}/built-in-roles.html[built-in] roles. +* **A space**: In this tutorial, use `Dev Mortgage` as the space name. See <> for details on creating a space. -* **Data**: You can use <> or -live data. In the steps below, Filebeat and Metricbeat data are used. +* **Data**: You can use <> or +live data. In the following steps, Filebeat and Metricbeat data are used. [float] ==== Steps -With the requirements in mind, here are the steps that you will work +With the requirements in mind, here are the steps that you will work through in this tutorial: * Create a role named `mortgage-developer` * Give the role permission to access the data in the relevant indices -* Give the role permission to create visualizations and dashboards +* Give the role permission to create visualizations and dashboards * Create the web developer's user account with the proper roles [float] ==== Create a role -Go to **Management > Roles** +Go to **Management > Roles** for an overview of your roles. This view provides actions for you to create, edit, and delete roles. @@ -53,21 +53,21 @@ for you to create, edit, and delete roles. image::security/images/role-management.png["Role management"] -You can create as many roles as you like. Click *Create role* and -provide a name. Use `dev-mortgage` because this role is for a developer +You can create as many roles as you like. Click *Create role* and +provide a name. Use `dev-mortgage` because this role is for a developer working on the bank's mortgage application. [float] ==== Give the role permission to access the data -Access to data in indices is an index-level privilege, so in -*Index privileges*, add lines for the indices that contain the -data for this role. Two privileges are required: `read` and -`view_index_metadata`. All privileges are detailed in the +Access to data in indices is an index-level privilege, so in +*Index privileges*, add lines for the indices that contain the +data for this role. Two privileges are required: `read` and +`view_index_metadata`. All privileges are detailed in the https://www.elastic.co/guide/en/elasticsearch/reference/current/security-privileges.html[security privileges] documentation. -In the screenshots, Filebeat and Metricbeat data is used, but you +In the screenshots, Filebeat and Metricbeat data is used, but you should use the index patterns for your indices. [role="screenshot"] @@ -76,12 +76,12 @@ image::security/images/role-index-privilege.png["Index privilege"] [float] ==== Give the role permission to create visualizations and dashboards -By default, roles do not give Kibana privileges. Click **Add space +By default, roles do not give Kibana privileges. Click **Add space privilege** and associate this role with the `Dev Mortgage` space. -To enable users with the `dev-mortgage` role to create visualizations -and dashboards, click *All* for *Visualize* and *Dashboard*. Also -assign *All* for *Discover* because it is common for developers +To enable users with the `dev-mortgage` role to create visualizations +and dashboards, click *All* for *Visualize* and *Dashboard*. Also +assign *All* for *Discover* because it is common for developers to create saved searches while designing visualizations. [role="screenshot"] @@ -90,15 +90,14 @@ image::security/images/role-space-visualization.png["Associate space"] [float] ==== Create the developer's user account with the proper roles -Go to **Management > Users** and click on **Create user** to create a -user. Give the user the `dev-mortgage` role +Go to **Management > Users** and click on **Create user** to create a +user. Give the user the `dev-mortgage` role and the `monitoring-user` role, which is required for users of **Stack Monitoring**. [role="screenshot"] image::security/images/role-new-user.png["Developer user"] -Finally, have the developer log in and access the Dev Mortgage space +Finally, have the developer log in and access the Dev Mortgage space and create a new visualization. NOTE: If the user is assigned to only one space, they will automatically enter that space on login. - diff --git a/docs/visualize/vega.asciidoc b/docs/visualize/vega.asciidoc index c9cf1e7aeb820..b8c0d1dbe3dda 100644 --- a/docs/visualize/vega.asciidoc +++ b/docs/visualize/vega.asciidoc @@ -324,7 +324,7 @@ replace `"url": "data/world-110m.json"` with `"url": "https://vega.github.io/editor/data/world-110m.json"`. Also, regular Vega examples use `"autosize": "pad"` layout model, whereas Kibana uses `fit`. Remove all `autosize`, `width`, and `height` -values. See link:#sizing-and-positioning[sizing and positioning] below. +values. See link:#sizing-and-positioning[sizing and positioning]. [[vega-additional-configuration-options]] ==== Additional configuration options diff --git a/packages/kbn-config-schema/src/errors/validation_error.ts b/packages/kbn-config-schema/src/errors/validation_error.ts index d688d022da85c..2a4f887bc4349 100644 --- a/packages/kbn-config-schema/src/errors/validation_error.ts +++ b/packages/kbn-config-schema/src/errors/validation_error.ts @@ -44,5 +44,8 @@ export class ValidationError extends SchemaError { constructor(error: SchemaTypeError, namespace?: string) { super(ValidationError.extractMessage(error, namespace), error); + + // https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work + Object.setPrototypeOf(this, ValidationError.prototype); } } diff --git a/packages/kbn-dev-utils/src/kbn_client/kbn_client_ui_settings.ts b/packages/kbn-dev-utils/src/kbn_client/kbn_client_ui_settings.ts index ad01dea624c3c..dbfa87e70032b 100644 --- a/packages/kbn-dev-utils/src/kbn_client/kbn_client_ui_settings.ts +++ b/packages/kbn-dev-utils/src/kbn_client/kbn_client_ui_settings.ts @@ -67,7 +67,7 @@ export class KbnClientUiSettings { * Replace all uiSettings with the `doc` values, `doc` is merged * with some defaults */ - async replace(doc: UiSettingValues) { + async replace(doc: UiSettingValues, { retries = 5 }: { retries?: number } = {}) { this.log.debug('replacing kibana config doc: %j', doc); const changes: Record = { @@ -85,7 +85,7 @@ export class KbnClientUiSettings { method: 'POST', path: '/api/kibana/settings', body: { changes }, - retries: 5, + retries, }); } diff --git a/packages/kbn-pm/dist/index.js b/packages/kbn-pm/dist/index.js index fe0491870e4bd..338d489300526 100644 --- a/packages/kbn-pm/dist/index.js +++ b/packages/kbn-pm/dist/index.js @@ -43920,7 +43920,7 @@ class KbnClientUiSettings { * Replace all uiSettings with the `doc` values, `doc` is merged * with some defaults */ - async replace(doc) { + async replace(doc, { retries = 5 } = {}) { this.log.debug('replacing kibana config doc: %j', doc); const changes = { ...this.defaults, @@ -43935,7 +43935,7 @@ class KbnClientUiSettings { method: 'POST', path: '/api/kibana/settings', body: { changes }, - retries: 5, + retries, }); } /** diff --git a/rfcs/text/0010_service_status.md b/rfcs/text/0010_service_status.md new file mode 100644 index 0000000000000..ded594930a367 --- /dev/null +++ b/rfcs/text/0010_service_status.md @@ -0,0 +1,373 @@ +- Start Date: 2020-03-07 +- RFC PR: https://github.com/elastic/kibana/pull/59621 +- Kibana Issue: https://github.com/elastic/kibana/issues/41983 + +# Summary + +A set API for describing the current status of a system (Core service or plugin) +in Kibana. + +# Basic example + +```ts +// Override default behavior and only elevate severity when elasticsearch is not available +core.status.set( + core.status.core$.pipe(core => core.elasticsearch); +) +``` + +# Motivation + +Kibana should do as much possible to help users keep their installation in a working state. This includes providing as much detail about components that are not working as well as ensuring that failures in one part of the application do not block using other portions of the application. + +In order to provide the user with as much detail as possible about any systems that are not working correctly, the status mechanism should provide excellent defaults in terms of expressing relationships between services and presenting detailed information to the user. + +# Detailed design + +## Failure Guidelines + +While this RFC primarily describes how status information is signaled from individual services and plugins to Core, it's first important to define how Core expects these services and plugins to behave in the face of failure more broadly. + +Core is designed to be resilient and adaptive to change. When at all possible, Kibana should automatically recover from failure, rather than requiring any kind of intervention by the user or administrator. + +Given this goal, Core expects the following from plugins: +- During initialization, `setup`, and `start` plugins should only throw an exception if a truly unrecoverable issue is encountered. Examples: HTTP port is unavailable, server does not have the appropriate file permissions. +- Temporary error conditions should always be retried automatically. A user should not have to restart Kibana in order to resolve a problem when avoidable. This means all initialization code should include error handling and automated retries. Examples: creating an Elasticsearch index, connecting to an external service. + - It's important to note that some issues do require manual intervention in _other services_ (eg. Elasticsearch). Kibana should still recover without restarting once that external issue is resolved. +- Unhandled promise rejections are not permitted. In the future, Node.js will crash on unhandled promise rejections. It is impossible for Core to be able to properly handle and retry these situations, so all services and plugins should handle all rejected promises and retry when necessary. +- Plugins should only crash the Kibana server when absolutely necessary. Some features are considered "mission-critical" to customers and may need to halt Kibana if they are not functioning correctly. Example: audit logging. + +## API Design + +### Types + +```ts +/** + * The current status of a service at a point in time. + * + * @typeParam Meta - JSON-serializable object. Plugins should export this type to allow other plugins to read the `meta` + * field in a type-safe way. + */ +type ServiceStatus = unknown> = { + /** + * The current availability level of the service. + */ + level: ServiceStatusLevel.available; + /** + * A high-level summary of the service status. + */ + summary?: string; + /** + * A more detailed description of the service status. + */ + detail?: string; + /** + * A URL to open in a new tab about how to resolve or troubleshoot the problem. + */ + documentationUrl?: string; + /** + * Any JSON-serializable data to be included in the HTTP API response. Useful for providing more fine-grained, + * machine-readable information about the service status. May include status information for underlying features. + */ + meta?: Meta; +} | { + level: ServiceStatusLevel; + summary: string; // required when level !== available + detail?: string; + documentationUrl?: string; + meta?: Meta; +} + +/** + * The current "level" of availability of a service. + */ +enum ServiceStatusLevel { + /** + * Everything is working! + */ + available, + /** + * Some features may not be working. + */ + degraded, + /** + * The service is unavailable, but other functions that do not depend on this service should work. + */ + unavailable, + /** + * Block all user functions and display the status page, reserved for Core services only. + * Note: In the real implementation, this will be split out to a different type. Kept as a single type here to make + * the RFC easier to follow. + */ + critical +} + +/** + * Status of core services. Only contains entries for backend services that could have a non-available `status`. + * For example, `context` cannot possibly be broken, so it is not included. + */ +interface CoreStatus { + elasticsearch: ServiceStatus; + http: ServiceStatus; + savedObjects: ServiceStatus; + uiSettings: ServiceStatus; + metrics: ServiceStatus; +} +``` + +### Plugin API + +```ts +/** + * The API exposed to plugins on CoreSetup.status + */ +interface StatusSetup { + /** + * Allows a plugin to specify a custom status dependent on its own criteria. + * Completely overrides the default inherited status. + */ + set(status$: Observable): void; + + /** + * Current status for all Core services. + */ + core$: Observable; + + /** + * Current status for all dependencies of the current plugin. + * Each key of the `Record` is a plugin id. + */ + plugins$: Observable>; + + /** + * The status of this plugin as derived from its dependencies. + * + * @remarks + * By default, plugins inherit this derived status from their dependencies. + * Calling {@link StatusSetup.set} overrides this default status. + */ + derivedStatus$: Observable; +} +``` + +### HTTP API + +The HTTP endpoint should return basic information about the Kibana node as well as the overall system status and the status of each individual system. + +This API does not need to include UI-specific details like the existing API such as `uiColor` and `icon`. + +```ts +/** + * Response type for the endpoint: GET /api/status + */ +interface StatusResponse { + /** server.name */ + name: string; + /** server.uuid */ + uuid: string; + /** Currently exposed by existing status API */ + version: { + number: string; + build_hash: string; + build_number: number; + build_snapshot: boolean; + }; + /** Similar format to existing API, but slightly different shape */ + status: { + /** See "Overall status calculation" section below */ + overall: ServiceStatus; + core: CoreStatus; + plugins: Record; + } +} +``` + +## Behaviors + +### Levels + +Each member of the `ServiceStatusLevel` enum has specific behaviors associated with it: +- **`available`**: + - All endpoints and apps associated with the service are accessible +- **`degraded`**: + - All endpoints and apps are available by default + - Some APIs may return `503 Unavailable` responses. This is not automatic, must be implemented directly by the service. + - Some plugin contract APIs may throw errors. This is not automatic, must be implemented directly by the service. +- **`unavailable`**: + - All endpoints (with some exceptions in Core) in Kibana return a `503 Unavailable` responses by default. This is automatic. + - When trying to access any app associated with the unavailable service, the user is presented with an error UI with detail about the outage. + - Some plugin contract APIs may throw errors. This is not automatic, must be implemented directly by the service. +- **`critical`**: + - All endpoints (with some exceptions in Core) in Kibana return a `503 Unavailable` response by default. This is automatic. + - All applications redirect to the system-wide status page with detail about which services are down and any relevant detail. This is automatic. + - Some plugin contract APIs may throw errors. This is not automatic, must be implemented directly by the service. + - This level is reserved for Core services only. + +### Overall status calculation + +The status level of the overall system is calculated to be the highest severity status of all core services and plugins. + +The `summary` property is calculated as follows: +- If the overall status level is `available`, the `summary` is `"Kibana is operating normally"` +- If a single core service or plugin is not `available`, the `summary` is `Kibana is ${level} due to ${serviceName}. See ${statusPageUrl} for more information.` +- If multiple core services or plugins are not `available`, the `summary` is `Kibana is ${level} due to multiple components. See ${statusPageUrl} for more information.` + +### Status inheritance + +By default, plugins inherit their status from all Core services and their dependencies on other plugins. + +This can be summarized by the following matrix: + +| core | required | optional | inherited | +|----------------|----------------|----------------|-------------| +| critical | _any_ | _any_ | critical | +| unavailable | <= unavailable | <= unavailable | unavailable | +| degraded | <= degraded | <= degraded | degraded | +| <= unavailable | unavailable | <= unavailable | unavailable | +| <= degraded | degraded | <= degraded | degraded | +| <= degraded | <= degraded | unavailable | degraded | +| <= degraded | <= degraded | degraded | degraded | +| available | available | available | available | + +If a plugin calls the `StatusSetup#set` API, the inherited status is completely overridden. They status the plugin specifies is the source of truth. If a plugin wishes to "merge" its custom status with the inherited status calculated by Core, it may do so by using the `StatusSetup#inherited$` property in its calculated status. + +If a plugin never calls the `StatusSetup#set` API, the plugin's status defaults to the inherited status. + +_Disabled_ plugins, that is plugins that are explicitly disabled in Kibana's configuration, do not have any status. They are not present in any status APIs and are **not** considered `unavailable`. Disabled plugins are excluded from the status inheritance calculation, even if a plugin has a optional dependency on a disabled plugin. In summary, if a plugin has an optional dependency on a disabled plugin, the plugin will not be considered `degraded` just because that optional dependency is disabled. + +### HTTP responses + +As specified in the [_Levels section_](#levels), a service's HTTP endpoints will respond with `503 Unavailable` responses in some status levels. + +In both the `critical` and `unavailable` levels, all of a service's endpoints will return 503s. However, in the `degraded` level, it is up to service authors to decide which endpoints should return a 503. This may be implemented directly in the route handler logic or by using any of the [utilities provided](#status-utilities). + +When a 503 is returned either via the default behavior or behavior implemented using the [provided utilities](#status-utilities), the HTTP response will include the following: +- `Retry-After` header, set to `60` seconds +- A body with mime type `application/json` containing the status of the service the HTTP route belongs to: + ```json5 + { + "error": "Unavailable", + // `ServiceStatus#summary` + "message": "Newsfeed API cannot be reached", + "attributes": { + "status": { + // Human readable form of `ServiceStatus#level` + "level": "critical", + // `ServiceStatus#summary` + "summary": "Newsfeed API cannot be reached", + // `ServiceStatus#detail` or null + "detail": null, + // `ServiceStatus#documentationUrl` or null + "documentationUrl": null, + // JSON-serialized from `ServiceStatus#meta` or null + "meta": {} + } + }, + "statusCode": 503 + } + ``` + +## Status Utilities + +Though many plugins should be able to rely on the default status inheritance and associated behaviors, there are common patterns and overrides that some plugins will need. The status service should provide some utilities for these common patterns out-of-the-box. + +```ts +/** + * Extension of the main Status API + */ +interface StatusSetup { + /** + * Helpers for expressing status in HTTP routes. + */ + http: { + /** + * High-order route handler function for wrapping routes with 503 logic based + * on a predicate. + * + * @remarks + * When a 503 is returned, it also includes detailed information from the service's + * current `ServiceStatus` including `meta` information. + * + * @example + * ```ts + * router.get( + * { path: '/my-api' } + * unavailableWhen( + * ServiceStatusLevel.degraded, + * async (context, req, res) => { + * return res.ok({ body: 'done' }); + * } + * ) + * ) + * ``` + * + * @param predicate When a level is specified, if the plugin's current status + * level is >= to the severity of the specified level, route + * returns a 503. When a function is specified, if that + * function returns `true`, a 503 is returned. + * @param handler The route handler to execute when a 503 is not returned. + * @param options.retryAfter Number of seconds to set the `Retry-After` + * header to when the endpoint is unavailable. + * Defaults to `60`. + */ + unavailableWhen( + predicate: ServiceStatusLevel | + (self: ServiceStatus, core: CoreStatus, plugins: Record) => boolean, + handler: RouteHandler, + options?: { retryAfter?: number } + ): RouteHandler; + } +} +``` + +## Additional Examples + +### Combine inherited status with check against external dependency +```ts +const getExternalDepHealth = async () => { + const resp = await window.fetch('https://myexternaldep.com/_healthz'); + return resp.json(); +} + +// Create an observable that checks the status of an external service every every 10s +const myExternalDependency$: Observable = interval(10000).pipe( + mergeMap(() => of(getExternalDepHealth())), + map(health => health.ok ? ServiceStatusLevel.available : ServiceStatusLevel.unavailable), + catchError(() => of(ServiceStatusLevel.unavailable)) +); + +// Merge the inherited status with the external check +core.status.set( + combineLatest( + core.status.inherited$, + myExternalDependency$ + ).pipe( + map(([inherited, external]) => ({ + level: Math.max(inherited.level, external) + })) + ) +); +``` + +# Drawbacks + +1. **The default behaviors and inheritance of statuses may appear to be "magic" to developers who do not read the documentation about how this works.** Compared to the legacy status mechanism, these defaults are much more opinionated and the resulting status is less explicit in plugin code compared to the legacy `mirrorPluginStatus` mechanism. +2. **The default behaviors and inheritance may not fit real-world status very well.** If many plugins must customize their status in order to opt-out of the defaults, this would be a step backwards from the legacy mechanism. + +# Alternatives + +We could somewhat reduce the complexity of the status inheritance by leveraging the dependencies between plugins to enable and disable plugins based on whether or not their upstream dependencies are available. This may simplify plugin code but would greatly complicate how Kibana fundamentally operates, requiring that plugins may get stopped and started multiple times within a single Kibana server process. We would be trading simplicity in one area for complexity in another. + +# Adoption strategy + +By default, most plugins would not need to do much at all. Today, very few plugins leverage the legacy status system. The majority of ones that do, simply call the `mirrorPluginStatus` utility to follow the status of the legacy elasticsearch plugin. + +Plugins that wish to expose more detail about their availability will easily be able to do so, including providing detailed information such as links to documentation to resolve the problem. + +# How we teach this + +This largely follows the same patterns we have used for other Core APIs: Observables, composable utilties, etc. + +This should be taught using the same channels we've leveraged for other Kibana Platform APIs: API documentation, additions to the [Migration Guide](../../src/core/MIGRATION.md) and [Migration Examples](../../src/core/MIGRATION_EXMAPLES.md). + +# Unresolved questions diff --git a/src/core/public/index.ts b/src/core/public/index.ts index 483d4dbfdf7c5..0ff044878afa9 100644 --- a/src/core/public/index.ts +++ b/src/core/public/index.ts @@ -171,7 +171,7 @@ export { ErrorToastOptions, } from './notifications'; -export { MountPoint, UnmountCallback } from './types'; +export { MountPoint, UnmountCallback, PublicUiSettingsParams } from './types'; /** * Core services exposed to the `Plugin` setup lifecycle diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index 69668176a397e..46667230edc3b 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -16,10 +16,11 @@ import { Location } from 'history'; import { LocationDescriptorObject } from 'history'; import { MaybePromise } from '@kbn/utility-types'; import { Observable } from 'rxjs'; +import { PublicUiSettingsParams as PublicUiSettingsParams_2 } from 'src/core/server/types'; import React from 'react'; import * as Rx from 'rxjs'; import { ShallowPromise } from '@kbn/utility-types'; -import { UiSettingsParams as UiSettingsParams_2 } from 'src/core/server/types'; +import { Type } from '@kbn/config-schema'; import { UnregisterCallback } from 'history'; import { UserProvidedValues as UserProvidedValues_2 } from 'src/core/server/types'; @@ -784,7 +785,7 @@ export type IToasts = Pick(key: string, defaultOverride?: T) => Observable; get: (key: string, defaultOverride?: T) => T; - getAll: () => Readonly>; + getAll: () => Readonly>; getSaved$: () => Observable<{ key: string; newValue: T; @@ -933,6 +934,9 @@ export interface PluginInitializerContext // @public (undocumented) export type PluginOpaqueId = symbol; +// @public +export type PublicUiSettingsParams = Omit; + // Warning: (ae-forgotten-export) The symbol "RecursiveReadonlyArray" needs to be exported by the entry point index.d.ts // // @public (undocumented) @@ -1291,7 +1295,7 @@ export type ToastsSetup = IToasts; export type ToastsStart = IToasts; // @public -export interface UiSettingsParams { +export interface UiSettingsParams { category?: string[]; // Warning: (ae-forgotten-export) The symbol "DeprecationSettings" needs to be exported by the entry point index.d.ts deprecation?: DeprecationSettings; @@ -1301,16 +1305,18 @@ export interface UiSettingsParams { options?: string[]; readonly?: boolean; requiresPageReload?: boolean; + // (undocumented) + schema: Type; type?: UiSettingsType; // (undocumented) validation?: ImageValidation | StringValidation; - value?: SavedObjectAttribute; + value?: T; } // @public (undocumented) export interface UiSettingsState { // (undocumented) - [key: string]: UiSettingsParams_2 & UserProvidedValues_2; + [key: string]: PublicUiSettingsParams_2 & UserProvidedValues_2; } // @public diff --git a/src/core/public/types.ts b/src/core/public/types.ts index 267a9e9f7e014..26f1e46836378 100644 --- a/src/core/public/types.ts +++ b/src/core/public/types.ts @@ -19,6 +19,7 @@ export { UiSettingsParams, + PublicUiSettingsParams, UserProvidedValues, UiSettingsType, ImageValidation, diff --git a/src/core/public/ui_settings/__snapshots__/ui_settings_api.test.ts.snap b/src/core/public/ui_settings/__snapshots__/ui_settings_api.test.ts.snap index cd55c77526d52..b737c04a5f269 100644 --- a/src/core/public/ui_settings/__snapshots__/ui_settings_api.test.ts.snap +++ b/src/core/public/ui_settings/__snapshots__/ui_settings_api.test.ts.snap @@ -84,21 +84,21 @@ Array [ exports[`#batchSet rejects all promises for batched requests that fail: promise rejections 1`] = ` Array [ Object { - "error": [Error: Request failed with status code: 400], + "error": [Error: invalid], "isRejected": true, }, Object { - "error": [Error: Request failed with status code: 400], + "error": [Error: invalid], "isRejected": true, }, Object { - "error": [Error: Request failed with status code: 400], + "error": [Error: invalid], "isRejected": true, }, ] `; -exports[`#batchSet rejects on 301 1`] = `"Request failed with status code: 301"`; +exports[`#batchSet rejects on 301 1`] = `"Moved Permanently"`; exports[`#batchSet rejects on 404 response 1`] = `"Request failed with status code: 404"`; diff --git a/src/core/public/ui_settings/types.ts b/src/core/public/ui_settings/types.ts index 19fd91924f247..d92c033ae8c8c 100644 --- a/src/core/public/ui_settings/types.ts +++ b/src/core/public/ui_settings/types.ts @@ -18,11 +18,11 @@ */ import { Observable } from 'rxjs'; -import { UiSettingsParams, UserProvidedValues } from 'src/core/server/types'; +import { PublicUiSettingsParams, UserProvidedValues } from 'src/core/server/types'; /** @public */ export interface UiSettingsState { - [key: string]: UiSettingsParams & UserProvidedValues; + [key: string]: PublicUiSettingsParams & UserProvidedValues; } /** @@ -53,7 +53,7 @@ export interface IUiSettingsClient { * Gets the metadata about all uiSettings, including the type, default value, and user value * for each key. */ - getAll: () => Readonly>; + getAll: () => Readonly>; /** * Sets the value for a uiSetting. If the setting is not registered by any plugin diff --git a/src/core/public/ui_settings/ui_settings_api.test.ts b/src/core/public/ui_settings/ui_settings_api.test.ts index 1170c42cea704..9a462e0541347 100644 --- a/src/core/public/ui_settings/ui_settings_api.test.ts +++ b/src/core/public/ui_settings/ui_settings_api.test.ts @@ -148,7 +148,7 @@ describe('#batchSet', () => { '*', { status: 400, - body: 'invalid', + body: { message: 'invalid' }, }, { overwriteRoutes: false, diff --git a/src/core/public/ui_settings/ui_settings_api.ts b/src/core/public/ui_settings/ui_settings_api.ts index 33b43107acf1b..c5efced0a41e3 100644 --- a/src/core/public/ui_settings/ui_settings_api.ts +++ b/src/core/public/ui_settings/ui_settings_api.ts @@ -152,10 +152,14 @@ export class UiSettingsApi { }, }); } catch (err) { - if (err.response && err.response.status >= 300) { - throw new Error(`Request failed with status code: ${err.response.status}`); + if (err.response) { + if (err.response.status === 400) { + throw new Error(err.body.message); + } + if (err.response.status > 400) { + throw new Error(`Request failed with status code: ${err.response.status}`); + } } - throw err; } finally { this.loadingCount$.next(this.loadingCount$.getValue() - 1); diff --git a/src/core/public/ui_settings/ui_settings_client.ts b/src/core/public/ui_settings/ui_settings_client.ts index f0071ed08435c..f5596b1bc34fc 100644 --- a/src/core/public/ui_settings/ui_settings_client.ts +++ b/src/core/public/ui_settings/ui_settings_client.ts @@ -21,14 +21,14 @@ import { cloneDeep, defaultsDeep } from 'lodash'; import { Observable, Subject, concat, defer, of } from 'rxjs'; import { filter, map } from 'rxjs/operators'; -import { UiSettingsParams, UserProvidedValues } from 'src/core/server/types'; +import { UserProvidedValues, PublicUiSettingsParams } from 'src/core/server/types'; import { IUiSettingsClient, UiSettingsState } from './types'; import { UiSettingsApi } from './ui_settings_api'; interface UiSettingsClientParams { api: UiSettingsApi; - defaults: Record; + defaults: Record; initialSettings?: UiSettingsState; done$: Observable; } @@ -39,8 +39,8 @@ export class UiSettingsClient implements IUiSettingsClient { private readonly updateErrors$ = new Subject(); private readonly api: UiSettingsApi; - private readonly defaults: Record; - private cache: Record; + private readonly defaults: Record; + private cache: Record; constructor(params: UiSettingsClientParams) { this.api = params.api; diff --git a/src/core/server/core_app/core_app.ts b/src/core/server/core_app/core_app.ts new file mode 100644 index 0000000000000..2f8c85f47a76e --- /dev/null +++ b/src/core/server/core_app/core_app.ts @@ -0,0 +1,52 @@ +/* + * 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 { InternalCoreSetup } from '../internal_types'; +import { CoreContext } from '../core_context'; +import { Logger } from '../logging'; + +/** @internal */ +export class CoreApp { + private readonly logger: Logger; + constructor(core: CoreContext) { + this.logger = core.logger.get('core-app'); + } + setup(coreSetup: InternalCoreSetup) { + this.logger.debug('Setting up core app.'); + this.registerDefaultRoutes(coreSetup); + } + + private registerDefaultRoutes(coreSetup: InternalCoreSetup) { + const httpSetup = coreSetup.http; + const router = httpSetup.createRouter('/'); + router.get({ path: '/', validate: false }, async (context, req, res) => { + const defaultRoute = await context.core.uiSettings.client.get('defaultRoute'); + const basePath = httpSetup.basePath.get(req); + const url = `${basePath}${defaultRoute}`; + + return res.redirected({ + headers: { + location: url, + }, + }); + }); + router.get({ path: '/core', validate: false }, async (context, req, res) => + res.ok({ body: { version: '0.0.1' } }) + ); + } +} diff --git a/src/core/server/core_app/index.ts b/src/core/server/core_app/index.ts new file mode 100644 index 0000000000000..342ed43f1ff8a --- /dev/null +++ b/src/core/server/core_app/index.ts @@ -0,0 +1,20 @@ +/* + * 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 { CoreApp } from './core_app'; diff --git a/src/core/server/core_app/integration_tests/default_route_provider_config.test.ts b/src/core/server/core_app/integration_tests/default_route_provider_config.test.ts new file mode 100644 index 0000000000000..221e6fa42471c --- /dev/null +++ b/src/core/server/core_app/integration_tests/default_route_provider_config.test.ts @@ -0,0 +1,90 @@ +/* + * 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 kbnTestServer from '../../../../test_utils/kbn_server'; +import { Root } from '../../root'; + +const { startES } = kbnTestServer.createTestServers({ + adjustTimeout: (t: number) => jest.setTimeout(t), +}); +let esServer: kbnTestServer.TestElasticsearchUtils; + +describe('default route provider', () => { + let root: Root; + + beforeAll(async () => { + esServer = await startES(); + root = kbnTestServer.createRootWithCorePlugins({ + server: { + basePath: '/hello', + }, + }); + + await root.setup(); + await root.start(); + }); + + afterAll(async () => { + await esServer.stop(); + await root.shutdown(); + }); + + it('redirects to the configured default route respecting basePath', async function() { + const { status, header } = await kbnTestServer.request.get(root, '/'); + + expect(status).toEqual(302); + expect(header).toMatchObject({ + location: '/hello/app/kibana', + }); + }); + + it('ignores invalid values', async function() { + const invalidRoutes = [ + 'http://not-your-kibana.com', + '///example.com', + '//example.com', + ' //example.com', + ]; + + for (const url of invalidRoutes) { + await kbnTestServer.request + .post(root, '/api/kibana/settings/defaultRoute') + .send({ value: url }) + .expect(400); + } + + const { status, header } = await kbnTestServer.request.get(root, '/'); + expect(status).toEqual(302); + expect(header).toMatchObject({ + location: '/hello/app/kibana', + }); + }); + + it('consumes valid values', async function() { + await kbnTestServer.request + .post(root, '/api/kibana/settings/defaultRoute') + .send({ value: '/valid' }) + .expect(200); + + const { status, header } = await kbnTestServer.request.get(root, '/'); + expect(status).toEqual(302); + expect(header).toMatchObject({ + location: '/hello/valid', + }); + }); +}); diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 4a1ac8988e4e5..89fee92a7ef02 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -250,6 +250,7 @@ export { export { IUiSettingsClient, UiSettingsParams, + PublicUiSettingsParams, UiSettingsType, UiSettingsServiceSetup, UiSettingsServiceStart, diff --git a/src/core/server/saved_objects/serialization/types.ts b/src/core/server/saved_objects/serialization/types.ts index 524c2c8ffae7a..dfaec127ba159 100644 --- a/src/core/server/saved_objects/serialization/types.ts +++ b/src/core/server/saved_objects/serialization/types.ts @@ -50,7 +50,7 @@ export interface SavedObjectsRawDocSource { * scenario out of the box. */ interface SavedObjectDoc { - attributes: unknown; + attributes: any; id?: string; // NOTE: SavedObjectDoc is used for uncreated objects where `id` is optional type: string; namespace?: string; diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 9cd0c26ea2497..84fe081adaae6 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -998,7 +998,7 @@ export interface IScopedRenderingClient { export interface IUiSettingsClient { get: (key: string) => Promise; getAll: () => Promise>; - getRegistered: () => Readonly>; + getRegistered: () => Readonly>; getUserProvided: () => Promise>>; isOverridden: (key: string) => boolean; remove: (key: string) => Promise; @@ -1443,6 +1443,9 @@ export interface PluginsServiceStart { contracts: Map; } +// @public +export type PublicUiSettingsParams = Omit; + // Warning: (ae-forgotten-export) The symbol "RecursiveReadonlyArray" needs to be exported by the entry point index.d.ts // // @public (undocumented) @@ -2284,7 +2287,7 @@ export interface StringValidationRegexString { } // @public -export interface UiSettingsParams { +export interface UiSettingsParams { category?: string[]; deprecation?: DeprecationSettings; description?: string; @@ -2293,10 +2296,12 @@ export interface UiSettingsParams { options?: string[]; readonly?: boolean; requiresPageReload?: boolean; + // (undocumented) + schema: Type; type?: UiSettingsType; // (undocumented) validation?: ImageValidation | StringValidation; - value?: SavedObjectAttribute; + value?: T; } // @public (undocumented) diff --git a/src/core/server/server.ts b/src/core/server/server.ts index 2402504f717ca..09a1328f346d8 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -26,8 +26,9 @@ import { RawConfigurationProvider, coreDeprecationProvider, } from './config'; +import { CoreApp } from './core_app'; import { ElasticsearchService } from './elasticsearch'; -import { HttpService, InternalHttpServiceSetup } from './http'; +import { HttpService } from './http'; import { RenderingService, RenderingServiceSetup } from './rendering'; import { LegacyService, ensureValidConfiguration } from './legacy'; import { Logger, LoggerFactory } from './logging'; @@ -69,6 +70,7 @@ export class Server { private readonly uiSettings: UiSettingsService; private readonly uuid: UuidService; private readonly metrics: MetricsService; + private readonly coreApp: CoreApp; private coreStart?: InternalCoreStart; @@ -92,6 +94,7 @@ export class Server { this.capabilities = new CapabilitiesService(core); this.uuid = new UuidService(core); this.metrics = new MetricsService(core); + this.coreApp = new CoreApp(core); } public async setup() { @@ -122,8 +125,6 @@ export class Server { context: contextServiceSetup, }); - this.registerDefaultRoute(httpSetup); - const capabilitiesSetup = this.capabilities.setup({ http: httpSetup }); const elasticsearchServiceSetup = await this.elasticsearch.setup({ @@ -168,6 +169,7 @@ export class Server { }); this.registerCoreContext(coreSetup, renderingSetup); + this.coreApp.setup(coreSetup); return coreSetup; } @@ -216,13 +218,6 @@ export class Server { await this.metrics.stop(); } - private registerDefaultRoute(httpSetup: InternalHttpServiceSetup) { - const router = httpSetup.createRouter('/core'); - router.get({ path: '/', validate: false }, async (context, req, res) => - res.ok({ body: { version: '0.0.1' } }) - ); - } - private registerCoreContext(coreSetup: InternalCoreSetup, rendering: RenderingServiceSetup) { coreSetup.http.registerRouteHandlerContext( coreId, diff --git a/src/core/server/ui_settings/index.ts b/src/core/server/ui_settings/index.ts index ddb66df3ffcbe..b11f398d59fa8 100644 --- a/src/core/server/ui_settings/index.ts +++ b/src/core/server/ui_settings/index.ts @@ -27,6 +27,7 @@ export { UiSettingsServiceStart, IUiSettingsClient, UiSettingsParams, + PublicUiSettingsParams, InternalUiSettingsServiceSetup, InternalUiSettingsServiceStart, UiSettingsType, diff --git a/src/core/server/ui_settings/integration_tests/routes.test.ts b/src/core/server/ui_settings/integration_tests/routes.test.ts new file mode 100644 index 0000000000000..c1261bc7c1350 --- /dev/null +++ b/src/core/server/ui_settings/integration_tests/routes.test.ts @@ -0,0 +1,66 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import * as kbnTestServer from '../../../../test_utils/kbn_server'; + +describe('ui settings service', () => { + describe('routes', () => { + let root: ReturnType; + beforeAll(async () => { + root = kbnTestServer.createRoot(); + + const { uiSettings } = await root.setup(); + uiSettings.register({ + custom: { + value: '42', + schema: schema.string(), + }, + }); + + await root.start(); + }, 30000); + afterAll(async () => await root.shutdown()); + + describe('set', () => { + it('validates value', async () => { + const response = await kbnTestServer.request + .post(root, '/api/kibana/settings/custom') + .send({ value: 100 }) + .expect(400); + + expect(response.body.message).toBe( + '[validation [custom]]: expected value of type [string] but got [number]' + ); + }); + }); + describe('set many', () => { + it('validates value', async () => { + const response = await kbnTestServer.request + .post(root, '/api/kibana/settings') + .send({ changes: { custom: 100, foo: 'bar' } }) + .expect(400); + + expect(response.body.message).toBe( + '[validation [custom]]: expected value of type [string] but got [number]' + ); + }); + }); + }); +}); diff --git a/src/core/server/ui_settings/routes/set.ts b/src/core/server/ui_settings/routes/set.ts index 51ad256b51335..e5158e274245c 100644 --- a/src/core/server/ui_settings/routes/set.ts +++ b/src/core/server/ui_settings/routes/set.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { schema } from '@kbn/config-schema'; +import { schema, ValidationError } from '@kbn/config-schema'; import { IRouter } from '../../http'; import { SavedObjectsErrorHelpers } from '../../saved_objects'; @@ -56,7 +56,7 @@ export function registerSetRoute(router: IRouter) { }); } - if (error instanceof CannotOverrideError) { + if (error instanceof CannotOverrideError || error instanceof ValidationError) { return response.badRequest({ body: error }); } diff --git a/src/core/server/ui_settings/routes/set_many.ts b/src/core/server/ui_settings/routes/set_many.ts index 3794eba004bee..5623c3fe11b80 100644 --- a/src/core/server/ui_settings/routes/set_many.ts +++ b/src/core/server/ui_settings/routes/set_many.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { schema } from '@kbn/config-schema'; +import { schema, ValidationError } from '@kbn/config-schema'; import { IRouter } from '../../http'; import { SavedObjectsErrorHelpers } from '../../saved_objects'; @@ -50,7 +50,7 @@ export function registerSetManyRoute(router: IRouter) { }); } - if (error instanceof CannotOverrideError) { + if (error instanceof CannotOverrideError || error instanceof ValidationError) { return response.badRequest({ body: error }); } diff --git a/src/core/server/ui_settings/types.ts b/src/core/server/ui_settings/types.ts index f3eb1f5a6859c..076e1de4458d7 100644 --- a/src/core/server/ui_settings/types.ts +++ b/src/core/server/ui_settings/types.ts @@ -17,9 +17,10 @@ * under the License. */ import { SavedObjectsClientContract } from '../saved_objects/types'; -import { UiSettingsParams, UserProvidedValues } from '../../types'; +import { UiSettingsParams, UserProvidedValues, PublicUiSettingsParams } from '../../types'; export { UiSettingsParams, + PublicUiSettingsParams, StringValidationRegexString, StringValidationRegex, StringValidation, @@ -41,7 +42,7 @@ export interface IUiSettingsClient { /** * Returns registered uiSettings values {@link UiSettingsParams} */ - getRegistered: () => Readonly>; + getRegistered: () => Readonly>; /** * Retrieves uiSettings values set by the user with fallbacks to default values if not specified. */ diff --git a/src/core/server/ui_settings/ui_settings_client.test.ts b/src/core/server/ui_settings/ui_settings_client.test.ts index b8aa57291dccf..4ce33eed267a3 100644 --- a/src/core/server/ui_settings/ui_settings_client.test.ts +++ b/src/core/server/ui_settings/ui_settings_client.test.ts @@ -18,6 +18,7 @@ */ import Chance from 'chance'; +import { schema } from '@kbn/config-schema'; import { loggingServiceMock } from '../logging/logging_service.mock'; import { createOrUpgradeSavedConfigMock } from './create_or_upgrade_saved_config/create_or_upgrade_saved_config.test.mock'; @@ -145,6 +146,22 @@ describe('ui settings', () => { expect(error.message).toBe('Unable to update "foo" because it is overridden'); } }); + + it('validates value if a schema presents', async () => { + const defaults = { foo: { schema: schema.string() } }; + const { uiSettings, savedObjectsClient } = setup({ defaults }); + + await expect( + uiSettings.setMany({ + bar: 2, + foo: 1, + }) + ).rejects.toMatchInlineSnapshot( + `[Error: [validation [foo]]: expected value of type [string] but got [number]]` + ); + + expect(savedObjectsClient.update).toHaveBeenCalledTimes(0); + }); }); describe('#set()', () => { @@ -163,6 +180,17 @@ describe('ui settings', () => { }); }); + it('validates value if a schema presents', async () => { + const defaults = { foo: { schema: schema.string() } }; + const { uiSettings, savedObjectsClient } = setup({ defaults }); + + await expect(uiSettings.set('foo', 1)).rejects.toMatchInlineSnapshot( + `[Error: [validation [foo]]: expected value of type [string] but got [number]]` + ); + + expect(savedObjectsClient.update).toHaveBeenCalledTimes(0); + }); + it('throws CannotOverrideError if the key is overridden', async () => { const { uiSettings } = setup({ overrides: { @@ -193,6 +221,20 @@ describe('ui settings', () => { expect(savedObjectsClient.update).toHaveBeenCalledWith(TYPE, ID, { one: null }); }); + it('does not fail validation', async () => { + const defaults = { + foo: { + schema: schema.string(), + value: '1', + }, + }; + const { uiSettings, savedObjectsClient } = setup({ defaults }); + + await uiSettings.remove('foo'); + + expect(savedObjectsClient.update).toHaveBeenCalledTimes(1); + }); + it('throws CannotOverrideError if the key is overridden', async () => { const { uiSettings } = setup({ overrides: { @@ -235,6 +277,20 @@ describe('ui settings', () => { }); }); + it('does not fail validation', async () => { + const defaults = { + foo: { + schema: schema.string(), + value: '1', + }, + }; + const { uiSettings, savedObjectsClient } = setup({ defaults }); + + await uiSettings.removeMany(['foo', 'bar']); + + expect(savedObjectsClient.update).toHaveBeenCalledTimes(1); + }); + it('throws CannotOverrideError if any key is overridden', async () => { const { uiSettings } = setup({ overrides: { @@ -256,7 +312,13 @@ describe('ui settings', () => { const value = chance.word(); const defaults = { key: { value } }; const { uiSettings } = setup({ defaults }); - expect(uiSettings.getRegistered()).toBe(defaults); + expect(uiSettings.getRegistered()).toEqual(defaults); + }); + it('does not leak validation schema outside', () => { + const value = chance.word(); + const defaults = { key: { value, schema: schema.string() } }; + const { uiSettings } = setup({ defaults }); + expect(uiSettings.getRegistered()).toStrictEqual({ key: { value } }); }); }); @@ -274,7 +336,7 @@ describe('ui settings', () => { const { uiSettings } = setup({ esDocSource }); const result = await uiSettings.getUserProvided(); - expect(result).toEqual({ + expect(result).toStrictEqual({ user: { userValue: 'customized', }, @@ -286,7 +348,7 @@ describe('ui settings', () => { const { uiSettings } = setup({ esDocSource }); const result = await uiSettings.getUserProvided(); - expect(result).toEqual({ + expect(result).toStrictEqual({ user: { userValue: 'customized', }, @@ -296,6 +358,32 @@ describe('ui settings', () => { }); }); + it('ignores user-configured value if it fails validation', async () => { + const esDocSource = { user: 'foo', id: 'bar' }; + const defaults = { + id: { + value: 42, + schema: schema.number(), + }, + }; + const { uiSettings } = setup({ esDocSource, defaults }); + const result = await uiSettings.getUserProvided(); + + expect(result).toStrictEqual({ + user: { + userValue: 'foo', + }, + }); + + expect(loggingServiceMock.collect(logger).warn).toMatchInlineSnapshot(` + Array [ + Array [ + "Ignore invalid UiSettings value. Error: [validation [id]]: expected value of type [number] but got [string].", + ], + ] + `); + }); + it('automatically creates the savedConfig if it is missing and returns empty object', async () => { const { uiSettings, savedObjectsClient, createOrUpgradeSavedConfig } = setup(); savedObjectsClient.get = jest @@ -303,7 +391,7 @@ describe('ui settings', () => { .mockRejectedValueOnce(SavedObjectsClient.errors.createGenericNotFoundError()) .mockResolvedValueOnce({ attributes: {} }); - expect(await uiSettings.getUserProvided()).toEqual({}); + expect(await uiSettings.getUserProvided()).toStrictEqual({}); expect(savedObjectsClient.get).toHaveBeenCalledTimes(2); @@ -320,7 +408,7 @@ describe('ui settings', () => { SavedObjectsClient.errors.createGenericNotFoundError() ); - expect(await uiSettings.getUserProvided()).toEqual({ foo: { userValue: 'bar ' } }); + expect(await uiSettings.getUserProvided()).toStrictEqual({ foo: { userValue: 'bar ' } }); }); it('returns an empty object on Forbidden responses', async () => { @@ -329,7 +417,7 @@ describe('ui settings', () => { const error = SavedObjectsClient.errors.decorateForbiddenError(new Error()); savedObjectsClient.get.mockRejectedValue(error); - expect(await uiSettings.getUserProvided()).toEqual({}); + expect(await uiSettings.getUserProvided()).toStrictEqual({}); expect(createOrUpgradeSavedConfig).toHaveBeenCalledTimes(0); }); @@ -339,7 +427,7 @@ describe('ui settings', () => { const error = SavedObjectsClient.errors.decorateEsUnavailableError(new Error()); savedObjectsClient.get.mockRejectedValue(error); - expect(await uiSettings.getUserProvided()).toEqual({}); + expect(await uiSettings.getUserProvided()).toStrictEqual({}); expect(createOrUpgradeSavedConfig).toHaveBeenCalledTimes(0); }); @@ -382,7 +470,7 @@ describe('ui settings', () => { }; const { uiSettings } = setup({ esDocSource, overrides }); - expect(await uiSettings.getUserProvided()).toEqual({ + expect(await uiSettings.getUserProvided()).toStrictEqual({ user: { userValue: 'customized', }, @@ -404,15 +492,40 @@ describe('ui settings', () => { expect(savedObjectsClient.get).toHaveBeenCalledWith(TYPE, ID); }); - it(`returns defaults when es doc is empty`, async () => { + it('returns defaults when es doc is empty', async () => { const esDocSource = {}; const defaults = { foo: { value: 'bar' } }; const { uiSettings } = setup({ esDocSource, defaults }); - expect(await uiSettings.getAll()).toEqual({ + expect(await uiSettings.getAll()).toStrictEqual({ foo: 'bar', }); }); + it('ignores user-configured value if it fails validation', async () => { + const esDocSource = { user: 'foo', id: 'bar' }; + const defaults = { + id: { + value: 42, + schema: schema.number(), + }, + }; + const { uiSettings } = setup({ esDocSource, defaults }); + const result = await uiSettings.getAll(); + + expect(result).toStrictEqual({ + id: 42, + user: 'foo', + }); + + expect(loggingServiceMock.collect(logger).warn).toMatchInlineSnapshot(` + Array [ + Array [ + "Ignore invalid UiSettings value. Error: [validation [id]]: expected value of type [number] but got [string].", + ], + ] + `); + }); + it(`merges user values, including ones without defaults, into key value pairs`, async () => { const esDocSource = { foo: 'user-override', @@ -427,7 +540,7 @@ describe('ui settings', () => { const { uiSettings } = setup({ esDocSource, defaults }); - expect(await uiSettings.getAll()).toEqual({ + expect(await uiSettings.getAll()).toStrictEqual({ foo: 'user-override', bar: 'user-provided', }); @@ -451,7 +564,7 @@ describe('ui settings', () => { const { uiSettings } = setup({ esDocSource, defaults, overrides }); - expect(await uiSettings.getAll()).toEqual({ + expect(await uiSettings.getAll()).toStrictEqual({ foo: 'bax', bar: 'user-provided', }); @@ -518,6 +631,28 @@ describe('ui settings', () => { expect(await uiSettings.get('dateFormat')).toBe('foo'); }); + + it('returns the default value if user-configured value fails validation', async () => { + const esDocSource = { id: 'bar' }; + const defaults = { + id: { + value: 42, + schema: schema.number(), + }, + }; + + const { uiSettings } = setup({ esDocSource, defaults }); + + expect(await uiSettings.get('id')).toBe(42); + + expect(loggingServiceMock.collect(logger).warn).toMatchInlineSnapshot(` + Array [ + Array [ + "Ignore invalid UiSettings value. Error: [validation [id]]: expected value of type [number] but got [string].", + ], + ] + `); + }); }); describe('#isOverridden()', () => { diff --git a/src/core/server/ui_settings/ui_settings_client.ts b/src/core/server/ui_settings/ui_settings_client.ts index a7e55d2b2da65..76c8284175f11 100644 --- a/src/core/server/ui_settings/ui_settings_client.ts +++ b/src/core/server/ui_settings/ui_settings_client.ts @@ -16,13 +16,13 @@ * specific language governing permissions and limitations * under the License. */ -import { defaultsDeep } from 'lodash'; +import { defaultsDeep, omit } from 'lodash'; import { SavedObjectsErrorHelpers } from '../saved_objects'; import { SavedObjectsClientContract } from '../saved_objects/types'; import { Logger } from '../logging'; import { createOrUpgradeSavedConfig } from './create_or_upgrade_saved_config'; -import { IUiSettingsClient, UiSettingsParams } from './types'; +import { IUiSettingsClient, UiSettingsParams, PublicUiSettingsParams } from './types'; import { CannotOverrideError } from './ui_settings_errors'; export interface UiSettingsServiceOptions { @@ -40,14 +40,14 @@ interface ReadOptions { autoCreateOrUpgradeIfMissing?: boolean; } -interface UserProvidedValue { +interface UserProvidedValue { userValue?: T; isOverridden?: boolean; } type UiSettingsRawValue = UiSettingsParams & UserProvidedValue; -type UserProvided = Record>; +type UserProvided = Record>; type UiSettingsRaw = Record; export class UiSettingsClient implements IUiSettingsClient { @@ -72,7 +72,11 @@ export class UiSettingsClient implements IUiSettingsClient { } getRegistered() { - return this.defaults; + const copiedDefaults: Record = {}; + for (const [key, value] of Object.entries(this.defaults)) { + copiedDefaults[key] = omit(value, 'schema'); + } + return copiedDefaults; } async get(key: string): Promise { @@ -90,29 +94,21 @@ export class UiSettingsClient implements IUiSettingsClient { }, {} as Record); } - async getUserProvided(): Promise> { - const userProvided: UserProvided = {}; - - // write the userValue for each key stored in the saved object that is not overridden - for (const [key, userValue] of Object.entries(await this.read())) { - if (userValue !== null && !this.isOverridden(key)) { - userProvided[key] = { - userValue, - }; - } - } + async getUserProvided(): Promise> { + const userProvided: UserProvided = this.onReadHook(await this.read()); // write all overridden keys, dropping the userValue is override is null and // adding keys for overrides that are not in saved object - for (const [key, userValue] of Object.entries(this.overrides)) { + for (const [key, value] of Object.entries(this.overrides)) { userProvided[key] = - userValue === null ? { isOverridden: true } : { isOverridden: true, userValue }; + value === null ? { isOverridden: true } : { isOverridden: true, userValue: value }; } return userProvided; } async setMany(changes: Record) { + this.onWriteHook(changes); await this.write({ changes }); } @@ -147,6 +143,43 @@ export class UiSettingsClient implements IUiSettingsClient { return defaultsDeep(userProvided, this.defaults); } + private validateKey(key: string, value: unknown) { + const definition = this.defaults[key]; + if (value === null || definition === undefined) return; + if (definition.schema) { + definition.schema.validate(value, {}, `validation [${key}]`); + } + } + + private onWriteHook(changes: Record) { + for (const key of Object.keys(changes)) { + this.assertUpdateAllowed(key); + } + + for (const [key, value] of Object.entries(changes)) { + this.validateKey(key, value); + } + } + + private onReadHook(values: Record) { + // write the userValue for each key stored in the saved object that is not overridden + // validate value read from saved objects as it can be changed via SO API + const filteredValues: UserProvided = {}; + for (const [key, userValue] of Object.entries(values)) { + if (userValue === null || this.isOverridden(key)) continue; + try { + this.validateKey(key, userValue); + filteredValues[key] = { + userValue: userValue as T, + }; + } catch (error) { + this.log.warn(`Ignore invalid UiSettings value. ${error}.`); + } + } + + return filteredValues; + } + private async write({ changes, autoCreateOrUpgradeIfMissing = true, @@ -154,10 +187,6 @@ export class UiSettingsClient implements IUiSettingsClient { changes: Record; autoCreateOrUpgradeIfMissing?: boolean; }) { - for (const key of Object.keys(changes)) { - this.assertUpdateAllowed(key); - } - try { await this.savedObjectsClient.update(this.type, this.id, changes); } catch (error) { diff --git a/src/core/server/ui_settings/ui_settings_service.test.ts b/src/core/server/ui_settings/ui_settings_service.test.ts index 11766713b3be0..08400f56ad281 100644 --- a/src/core/server/ui_settings/ui_settings_service.test.ts +++ b/src/core/server/ui_settings/ui_settings_service.test.ts @@ -17,6 +17,8 @@ * under the License. */ import { BehaviorSubject } from 'rxjs'; +import { schema } from '@kbn/config-schema'; + import { MockUiSettingsClientConstructor } from './ui_settings_service.test.mock'; import { UiSettingsService, SetupDeps } from './ui_settings_service'; import { httpServiceMock } from '../http/http_service.mock'; @@ -35,6 +37,7 @@ const defaults = { value: 'bar', category: [], description: '', + schema: schema.string(), }, }; @@ -104,6 +107,45 @@ describe('uiSettings', () => { }); describe('#start', () => { + describe('validation', () => { + it('validates registered definitions', async () => { + const { register } = await service.setup(setupDeps); + register({ + custom: { + value: 42, + schema: schema.string(), + }, + }); + + await expect(service.start()).rejects.toMatchInlineSnapshot( + `[Error: [ui settings defaults [custom]]: expected value of type [string] but got [number]]` + ); + }); + + it('validates overrides', async () => { + const coreContext = mockCoreContext.create(); + coreContext.configService.atPath.mockReturnValueOnce( + new BehaviorSubject({ + overrides: { + custom: 42, + }, + }) + ); + const customizedService = new UiSettingsService(coreContext); + const { register } = await customizedService.setup(setupDeps); + register({ + custom: { + value: '42', + schema: schema.string(), + }, + }); + + await expect(customizedService.start()).rejects.toMatchInlineSnapshot( + `[Error: [ui settings overrides [custom]]: expected value of type [string] but got [number]]` + ); + }); + }); + describe('#asScopedToClient', () => { it('passes saved object type "config" to UiSettingsClient', async () => { await service.setup(setupDeps); diff --git a/src/core/server/ui_settings/ui_settings_service.ts b/src/core/server/ui_settings/ui_settings_service.ts index de2cc9d510e0c..83e66cf6dd06d 100644 --- a/src/core/server/ui_settings/ui_settings_service.ts +++ b/src/core/server/ui_settings/ui_settings_service.ts @@ -70,6 +70,9 @@ export class UiSettingsService } public async start(): Promise { + this.validatesDefinitions(); + this.validatesOverrides(); + return { asScopedToClient: this.getScopedClientFactory(), }; @@ -101,4 +104,21 @@ export class UiSettingsService this.uiSettingsDefaults.set(key, value); }); } + + private validatesDefinitions() { + for (const [key, definition] of this.uiSettingsDefaults) { + if (definition.schema) { + definition.schema.validate(definition.value, {}, `ui settings defaults [${key}]`); + } + } + } + + private validatesOverrides() { + for (const [key, value] of Object.entries(this.overrides)) { + const definition = this.uiSettingsDefaults.get(key); + if (definition?.schema) { + definition.schema.validate(value, {}, `ui settings overrides [${key}]`); + } + } + } } diff --git a/src/core/types/ui_settings.ts b/src/core/types/ui_settings.ts index eccd3f9616af0..ed1076b571960 100644 --- a/src/core/types/ui_settings.ts +++ b/src/core/types/ui_settings.ts @@ -16,8 +16,7 @@ * specific language governing permissions and limitations * under the License. */ - -import { SavedObjectAttribute } from './saved_objects'; +import { Type } from '@kbn/config-schema'; /** * UI element type to represent the settings. @@ -49,11 +48,11 @@ export interface DeprecationSettings { * UiSettings parameters defined by the plugins. * @public * */ -export interface UiSettingsParams { +export interface UiSettingsParams { /** title in the UI */ name?: string; /** default value to fall back to if a user doesn't provide any */ - value?: SavedObjectAttribute; + value?: T; /** description provided to a user in UI */ description?: string; /** used to group the configured setting in the UI */ @@ -73,10 +72,22 @@ export interface UiSettingsParams { /* * Allows defining a custom validation applicable to value change on the client. * @deprecated + * Use schema instead. */ validation?: ImageValidation | StringValidation; + /* + * Value validation schema + * Used to validate value on write and read. + */ + schema: Type; } +/** + * A sub-set of {@link UiSettingsParams} exposed to the client-side. + * @public + * */ +export type PublicUiSettingsParams = Omit; + /** * Allows regex objects or a regex string * @public diff --git a/src/core/utils/url.test.ts b/src/core/utils/url.test.ts index 3c35ba44455bc..419c0cda2b8cb 100644 --- a/src/core/utils/url.test.ts +++ b/src/core/utils/url.test.ts @@ -17,7 +17,7 @@ * under the License. */ -import { modifyUrl } from './url'; +import { modifyUrl, isRelativeUrl } from './url'; describe('modifyUrl()', () => { test('throws an error with invalid input', () => { @@ -69,3 +69,17 @@ describe('modifyUrl()', () => { ).toEqual('mail:localhost'); }); }); + +describe('isRelativeUrl()', () => { + test('returns "true" for a relative URL', () => { + expect(isRelativeUrl('good')).toBe(true); + expect(isRelativeUrl('/good')).toBe(true); + expect(isRelativeUrl('/good/even/better')).toBe(true); + }); + test('returns "false" for a non-relative URL', () => { + expect(isRelativeUrl('http://evil.com')).toBe(false); + expect(isRelativeUrl('//evil.com')).toBe(false); + expect(isRelativeUrl('///evil.com')).toBe(false); + expect(isRelativeUrl(' //evil.com')).toBe(false); + }); +}); diff --git a/src/core/utils/url.ts b/src/core/utils/url.ts index 31de7e1814038..c2bf80ce3f86f 100644 --- a/src/core/utils/url.ts +++ b/src/core/utils/url.ts @@ -99,3 +99,19 @@ export function modifyUrl( slashes: modifiedParts.slashes, } as UrlObject); } + +export function isRelativeUrl(candidatePath: string) { + // validate that `candidatePath` is not attempting a redirect to somewhere + // outside of this Kibana install + const all = parseUrl(candidatePath, false /* parseQueryString */, true /* slashesDenoteHost */); + const { protocol, hostname, port } = all; + // We should explicitly compare `protocol`, `port` and `hostname` to null to make sure these are not + // detected in the URL at all. For example `hostname` can be empty string for Node URL parser, but + // browser (because of various bwc reasons) processes URL differently (e.g. `///abc.com` - for browser + // hostname is `abc.com`, but for Node hostname is an empty string i.e. everything between schema (`//`) + // and the first slash that belongs to path. + if (protocol !== null || hostname !== null || port !== null) { + return false; + } + return true; +} diff --git a/src/legacy/core_plugins/data/index.ts b/src/legacy/core_plugins/data/index.ts index 813eab00f7258..10c8cf464b82d 100644 --- a/src/legacy/core_plugins/data/index.ts +++ b/src/legacy/core_plugins/data/index.ts @@ -19,8 +19,6 @@ import { resolve } from 'path'; import { Legacy } from '../../../../kibana'; -import { mappings } from './mappings'; -import { SavedQuery } from '../../../plugins/data/public'; // eslint-disable-next-line import/no-default-export export default function DataPlugin(kibana: any) { @@ -36,23 +34,6 @@ export default function DataPlugin(kibana: any) { init: (server: Legacy.Server) => ({}), uiExports: { injectDefaultVars: () => ({}), - mappings, - savedObjectsManagement: { - query: { - icon: 'search', - defaultSearchField: 'title', - isImportableAndExportable: true, - getTitle(obj: SavedQuery) { - return obj.attributes.title; - }, - getInAppUrl(obj: SavedQuery) { - return { - path: `/app/kibana#/discover?_a=(savedQuery:'${encodeURIComponent(obj.id)}')`, - uiCapabilitiesPath: 'discover.show', - }; - }, - }, - }, }, }; diff --git a/src/legacy/core_plugins/kibana/index.js b/src/legacy/core_plugins/kibana/index.js index 092eed924f330..1d772536fa1ea 100644 --- a/src/legacy/core_plugins/kibana/index.js +++ b/src/legacy/core_plugins/kibana/index.js @@ -126,57 +126,6 @@ export default function(kibana) { ], savedObjectsManagement: { - 'index-pattern': { - icon: 'indexPatternApp', - defaultSearchField: 'title', - isImportableAndExportable: true, - getTitle(obj) { - return obj.attributes.title; - }, - getEditUrl(obj) { - return `/management/kibana/index_patterns/${encodeURIComponent(obj.id)}`; - }, - getInAppUrl(obj) { - return { - path: `/app/kibana#/management/kibana/index_patterns/${encodeURIComponent(obj.id)}`, - uiCapabilitiesPath: 'management.kibana.index_patterns', - }; - }, - }, - visualization: { - icon: 'visualizeApp', - defaultSearchField: 'title', - isImportableAndExportable: true, - getTitle(obj) { - return obj.attributes.title; - }, - getEditUrl(obj) { - return `/management/kibana/objects/savedVisualizations/${encodeURIComponent(obj.id)}`; - }, - getInAppUrl(obj) { - return { - path: `/app/kibana#/visualize/edit/${encodeURIComponent(obj.id)}`, - uiCapabilitiesPath: 'visualize.show', - }; - }, - }, - search: { - icon: 'discoverApp', - defaultSearchField: 'title', - isImportableAndExportable: true, - getTitle(obj) { - return obj.attributes.title; - }, - getEditUrl(obj) { - return `/management/kibana/objects/savedSearches/${encodeURIComponent(obj.id)}`; - }, - getInAppUrl(obj) { - return { - path: `/app/kibana#/discover/${encodeURIComponent(obj.id)}`, - uiCapabilitiesPath: 'discover.show', - }; - }, - }, dashboard: { icon: 'dashboardApp', defaultSearchField: 'title', diff --git a/src/legacy/core_plugins/kibana/mappings.json b/src/legacy/core_plugins/kibana/mappings.json index 4cf9ea1d301c0..af3f79588552b 100644 --- a/src/legacy/core_plugins/kibana/mappings.json +++ b/src/legacy/core_plugins/kibana/mappings.json @@ -1,93 +1,4 @@ { - "index-pattern": { - "properties": { - "fieldFormatMap": { - "type": "text" - }, - "fields": { - "type": "text" - }, - "intervalName": { - "type": "keyword" - }, - "notExpandable": { - "type": "boolean" - }, - "sourceFilters": { - "type": "text" - }, - "timeFieldName": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "type": { - "type": "keyword" - }, - "typeMeta": { - "type": "keyword" - } - } - }, - "visualization": { - "properties": { - "description": { - "type": "text" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "savedSearchRefName": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - }, - "visState": { - "type": "text" - } - } - }, - "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" - } - } - }, "dashboard": { "properties": { "description": { diff --git a/src/legacy/core_plugins/kibana/migrations/migrations.js b/src/legacy/core_plugins/kibana/migrations/migrations.js index 29b6e632d19fd..d37887c640b90 100644 --- a/src/legacy/core_plugins/kibana/migrations/migrations.js +++ b/src/legacy/core_plugins/kibana/migrations/migrations.js @@ -17,7 +17,7 @@ * under the License. */ -import { cloneDeep, get, omit, has, flow } from 'lodash'; +import { get } from 'lodash'; import { migrations730 as dashboardMigrations730 } from '../public/dashboard/migrations'; function migrateIndexPattern(doc) { @@ -58,559 +58,7 @@ function migrateIndexPattern(doc) { doc.attributes.kibanaSavedObjectMeta.searchSourceJSON = JSON.stringify(searchSource); } -// [TSVB] Migrate percentile-rank aggregation (value -> values) -const migratePercentileRankAggregation = doc => { - const visStateJSON = get(doc, 'attributes.visState'); - let visState; - - if (visStateJSON) { - try { - visState = JSON.parse(visStateJSON); - } catch (e) { - // Let it go, the data is invalid and we'll leave it as is - } - if (visState && visState.type === 'metrics') { - const series = get(visState, 'params.series') || []; - - series.forEach(part => { - (part.metrics || []).forEach(metric => { - if (metric.type === 'percentile_rank' && has(metric, 'value')) { - metric.values = [metric.value]; - - delete metric.value; - } - }); - }); - return { - ...doc, - attributes: { - ...doc.attributes, - visState: JSON.stringify(visState), - }, - }; - } - } - return doc; -}; - -// Migrate date histogram aggregation (remove customInterval) -const migrateDateHistogramAggregation = doc => { - const visStateJSON = get(doc, 'attributes.visState'); - let visState; - - if (visStateJSON) { - try { - visState = JSON.parse(visStateJSON); - } catch (e) { - // Let it go, the data is invalid and we'll leave it as is - } - - if (visState && visState.aggs) { - visState.aggs.forEach(agg => { - if (agg.type === 'date_histogram' && agg.params) { - if (agg.params.interval === 'custom') { - agg.params.interval = agg.params.customInterval; - } - delete agg.params.customInterval; - } - - if ( - get(agg, 'params.customBucket.type', null) === 'date_histogram' && - agg.params.customBucket.params - ) { - if (agg.params.customBucket.params.interval === 'custom') { - agg.params.customBucket.params.interval = agg.params.customBucket.params.customInterval; - } - delete agg.params.customBucket.params.customInterval; - } - }); - return { - ...doc, - attributes: { - ...doc.attributes, - visState: JSON.stringify(visState), - }, - }; - } - } - return doc; -}; - -function removeDateHistogramTimeZones(doc) { - const visStateJSON = get(doc, 'attributes.visState'); - if (visStateJSON) { - let visState; - try { - visState = JSON.parse(visStateJSON); - } catch (e) { - // Let it go, the data is invalid and we'll leave it as is - } - if (visState && visState.aggs) { - visState.aggs.forEach(agg => { - // We're checking always for the existance of agg.params here. This should always exist, but better - // be safe then sorry during migrations. - if (agg.type === 'date_histogram' && agg.params) { - delete agg.params.time_zone; - } - - if ( - get(agg, 'params.customBucket.type', null) === 'date_histogram' && - agg.params.customBucket.params - ) { - delete agg.params.customBucket.params.time_zone; - } - }); - doc.attributes.visState = JSON.stringify(visState); - } - } - return doc; -} - -// migrate gauge verticalSplit to alignment -// https://github.com/elastic/kibana/issues/34636 -function migrateGaugeVerticalSplitToAlignment(doc, logger) { - const visStateJSON = get(doc, 'attributes.visState'); - - if (visStateJSON) { - try { - const visState = JSON.parse(visStateJSON); - if (visState && visState.type === 'gauge' && !visState.params.gauge.alignment) { - visState.params.gauge.alignment = visState.params.gauge.verticalSplit - ? 'vertical' - : 'horizontal'; - delete visState.params.gauge.verticalSplit; - return { - ...doc, - attributes: { - ...doc.attributes, - visState: JSON.stringify(visState), - }, - }; - } - } catch (e) { - logger.warning(`Exception @ migrateGaugeVerticalSplitToAlignment! ${e}`); - logger.warning(`Exception @ migrateGaugeVerticalSplitToAlignment! Payload: ${visStateJSON}`); - } - } - return doc; -} -// Migrate filters (string -> { query: string, language: lucene }) -/* - Enabling KQL in TSVB causes problems with savedObject visualizations when these are saved with filters. - In a visualisation type of saved object, if the visState param is of type metric, the filter is saved as a string that is not interpretted correctly as a lucene query in the visualization itself. - We need to transform the filter string into an object containing the original string as a query and specify the query language as lucene. - For Metrics visualizations (param.type === "metric"), filters can be applied to each series object in the series array within the SavedObject.visState.params object. - Path to the series array is thus: - attributes.visState. -*/ -function transformFilterStringToQueryObject(doc) { - // Migrate filters - // If any filters exist and they are a string, we assume it to be lucene and transform the filter into an object accordingly - const newDoc = cloneDeep(doc); - const visStateJSON = get(doc, 'attributes.visState'); - if (visStateJSON) { - let visState; - try { - visState = JSON.parse(visStateJSON); - } catch (e) { - // let it go, the data is invalid and we'll leave it as is - } - if (visState) { - const visType = get(visState, 'params.type'); - const tsvbTypes = ['metric', 'markdown', 'top_n', 'gauge', 'table', 'timeseries']; - if (tsvbTypes.indexOf(visType) === -1) { - // skip - return doc; - } - // migrate the params fitler - const params = get(visState, 'params'); - if (params.filter && typeof params.filter === 'string') { - const paramsFilterObject = { - query: params.filter, - language: 'lucene', - }; - params.filter = paramsFilterObject; - } - - // migrate the annotations query string: - const annotations = get(visState, 'params.annotations') || []; - annotations.forEach(item => { - if (!item.query_string) { - // we don't need to transform anything if there isn't a filter at all - return; - } - if (typeof item.query_string === 'string') { - const itemQueryStringObject = { - query: item.query_string, - language: 'lucene', - }; - item.query_string = itemQueryStringObject; - } - }); - // migrate the series filters - const series = get(visState, 'params.series') || []; - series.forEach(item => { - if (!item.filter) { - // we don't need to transform anything if there isn't a filter at all - return; - } - // series item filter - if (typeof item.filter === 'string') { - const itemfilterObject = { - query: item.filter, - language: 'lucene', - }; - item.filter = itemfilterObject; - } - // series item split filters filter - if (item.split_filters) { - const splitFilters = get(item, 'split_filters') || []; - splitFilters.forEach(filter => { - if (!filter.filter) { - // we don't need to transform anything if there isn't a filter at all - return; - } - if (typeof filter.filter === 'string') { - const filterfilterObject = { - query: filter.filter, - language: 'lucene', - }; - filter.filter = filterfilterObject; - } - }); - } - }); - newDoc.attributes.visState = JSON.stringify(visState); - } - } - return newDoc; -} -function transformSplitFiltersStringToQueryObject(doc) { - // Migrate split_filters in TSVB objects that weren't migrated in 7.3 - // If any filters exist and they are a string, we assume them to be lucene syntax and transform the filter into an object accordingly - const newDoc = cloneDeep(doc); - const visStateJSON = get(doc, 'attributes.visState'); - if (visStateJSON) { - let visState; - try { - visState = JSON.parse(visStateJSON); - } catch (e) { - // let it go, the data is invalid and we'll leave it as is - } - if (visState) { - const visType = get(visState, 'params.type'); - const tsvbTypes = ['metric', 'markdown', 'top_n', 'gauge', 'table', 'timeseries']; - if (tsvbTypes.indexOf(visType) === -1) { - // skip - return doc; - } - // migrate the series split_filter filters - const series = get(visState, 'params.series') || []; - series.forEach(item => { - // series item split filters filter - if (item.split_filters) { - const splitFilters = get(item, 'split_filters') || []; - if (splitFilters.length > 0) { - // only transform split_filter filters if we have filters - splitFilters.forEach(filter => { - if (typeof filter.filter === 'string') { - const filterfilterObject = { - query: filter.filter, - language: 'lucene', - }; - filter.filter = filterfilterObject; - } - }); - } - } - }); - newDoc.attributes.visState = JSON.stringify(visState); - } - } - return newDoc; -} - -function migrateFiltersAggQuery(doc) { - const visStateJSON = get(doc, 'attributes.visState'); - - if (visStateJSON) { - try { - const visState = JSON.parse(visStateJSON); - if (visState && visState.aggs) { - visState.aggs.forEach(agg => { - if (agg.type !== 'filters') return; - - agg.params.filters.forEach(filter => { - if (filter.input.language) return filter; - filter.input.language = 'lucene'; - }); - }); - - return { - ...doc, - attributes: { - ...doc.attributes, - visState: JSON.stringify(visState), - }, - }; - } - } catch (e) { - // Let it go, the data is invalid and we'll leave it as is - } - } - return doc; -} - -function replaceMovAvgToMovFn(doc, logger) { - const visStateJSON = get(doc, 'attributes.visState'); - let visState; - - if (visStateJSON) { - try { - visState = JSON.parse(visStateJSON); - - if (visState && visState.type === 'metrics') { - const series = get(visState, 'params.series', []); - - series.forEach(part => { - if (part.metrics && Array.isArray(part.metrics)) { - part.metrics.forEach(metric => { - if (metric.type === 'moving_average') { - metric.model_type = metric.model; - metric.alpha = get(metric, 'settings.alpha', 0.3); - metric.beta = get(metric, 'settings.beta', 0.1); - metric.gamma = get(metric, 'settings.gamma', 0.3); - metric.period = get(metric, 'settings.period', 1); - metric.multiplicative = get(metric, 'settings.type') === 'mult'; - - delete metric.minimize; - delete metric.model; - delete metric.settings; - delete metric.predict; - } - }); - } - }); - - return { - ...doc, - attributes: { - ...doc.attributes, - visState: JSON.stringify(visState), - }, - }; - } - } catch (e) { - logger.warning(`Exception @ replaceMovAvgToMovFn! ${e}`); - logger.warning(`Exception @ replaceMovAvgToMovFn! Payload: ${visStateJSON}`); - } - } - - return doc; -} - -function migrateSearchSortToNestedArray(doc) { - const sort = get(doc, 'attributes.sort'); - if (!sort) return doc; - - // Don't do anything if we already have a two dimensional array - if (Array.isArray(sort) && sort.length > 0 && Array.isArray(sort[0])) { - return doc; - } - - return { - ...doc, - attributes: { - ...doc.attributes, - sort: [doc.attributes.sort], - }, - }; -} - -function migrateFiltersAggQueryStringQueries(doc) { - const visStateJSON = get(doc, 'attributes.visState'); - - if (visStateJSON) { - try { - const visState = JSON.parse(visStateJSON); - if (visState && visState.aggs) { - visState.aggs.forEach(agg => { - if (agg.type !== 'filters') return doc; - - agg.params.filters.forEach(filter => { - if (filter.input.query.query_string) { - filter.input.query = filter.input.query.query_string.query; - } - }); - }); - - return { - ...doc, - attributes: { - ...doc.attributes, - visState: JSON.stringify(visState), - }, - }; - } - } catch (e) { - // Let it go, the data is invalid and we'll leave it as is - } - } - return 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 -); -const executeMigrations730 = flow( - migrateGaugeVerticalSplitToAlignment, - transformFilterStringToQueryObject, - migrateFiltersAggQuery, - replaceMovAvgToMovFn -); - -const executeVisualizationMigrations731 = flow(migrateFiltersAggQueryStringQueries); - -const executeSearchMigrations740 = flow(migrateSearchSortToNestedArray); - -const executeMigrations742 = flow(transformSplitFiltersStringToQueryObject); - export const migrations = { - 'index-pattern': { - '6.5.0': doc => { - doc.attributes.type = doc.attributes.type || undefined; - doc.attributes.typeMeta = doc.attributes.typeMeta || undefined; - return doc; - }, - '7.6.0': flow(migrateSubTypeAndParentFieldProperties), - }, - visualization: { - /** - * We need to have this migration twice, once with a version prior to 7.0.0 once with a version - * after it. The reason for that is, that this migration has been introduced once 7.0.0 was already - * released. Thus a user who already had 7.0.0 installed already got the 7.0.0 migrations below running, - * so we need a version higher than that. But this fix was backported to the 6.7 release, meaning if we - * would only have the 7.0.1 migration in here a user on the 6.7 release will migrate their saved objects - * to the 7.0.1 state, and thus when updating their Kibana to 7.0, will never run the 7.0.0 migrations introduced - * in that version. So we apply this twice, once with 6.7.2 and once with 7.0.1 while the backport to 6.7 - * only contained the 6.7.2 migration and not the 7.0.1 migration. - */ - '6.7.2': removeDateHistogramTimeZones, - '7.0.0': doc => { - // Set new "references" attribute - doc.references = doc.references || []; - - // Migrate index pattern - migrateIndexPattern(doc); - - // Migrate saved search - const savedSearchId = get(doc, 'attributes.savedSearchId'); - if (savedSearchId) { - doc.references.push({ - type: 'search', - name: 'search_0', - id: savedSearchId, - }); - doc.attributes.savedSearchRefName = 'search_0'; - } - delete doc.attributes.savedSearchId; - - // Migrate controls - const visStateJSON = get(doc, 'attributes.visState'); - if (visStateJSON) { - let visState; - try { - visState = JSON.parse(visStateJSON); - } catch (e) { - // Let it go, the data is invalid and we'll leave it as is - } - if (visState) { - const controls = get(visState, 'params.controls') || []; - controls.forEach((control, i) => { - if (!control.indexPattern) { - return; - } - control.indexPatternRefName = `control_${i}_index_pattern`; - doc.references.push({ - name: control.indexPatternRefName, - type: 'index-pattern', - id: control.indexPattern, - }); - delete control.indexPattern; - }); - doc.attributes.visState = JSON.stringify(visState); - } - } - - // Migrate table splits - try { - const visState = JSON.parse(doc.attributes.visState); - if (get(visState, 'type') !== 'table') { - return doc; // do nothing; we only want to touch tables - } - - let splitCount = 0; - visState.aggs = visState.aggs.map(agg => { - if (agg.schema !== 'split') { - return agg; - } - - splitCount++; - if (splitCount === 1) { - return agg; // leave the first split agg unchanged - } - agg.schema = 'bucket'; - // the `row` param is exclusively used by split aggs, so we remove it - agg.params = omit(agg.params, ['row']); - return agg; - }); - - if (splitCount <= 1) { - return doc; // do nothing; we only want to touch tables with multiple split aggs - } - - const newDoc = cloneDeep(doc); - newDoc.attributes.visState = JSON.stringify(visState); - return newDoc; - } catch (e) { - throw new Error( - `Failure attempting to migrate saved object '${doc.attributes.title}' - ${e}` - ); - } - }, - '7.0.1': removeDateHistogramTimeZones, - '7.2.0': doc => executeMigrations720(doc), - '7.3.0': executeMigrations730, - '7.3.1': executeVisualizationMigrations731, - // migrate split_filters that were not migrated in 7.3.0 (transformFilterStringToQueryObject). - '7.4.2': executeMigrations742, - }, dashboard: { '7.0.0': doc => { // Set new "references" attribute @@ -651,14 +99,4 @@ export const migrations = { }, '7.3.0': dashboardMigrations730, }, - search: { - '7.0.0': doc => { - // Set new "references" attribute - doc.references = doc.references || []; - // Migrate index pattern - migrateIndexPattern(doc); - return doc; - }, - '7.4.0': executeSearchMigrations740, - }, }; diff --git a/src/legacy/core_plugins/kibana/migrations/migrations.test.js b/src/legacy/core_plugins/kibana/migrations/migrations.test.js index e39bc59201e7f..b02081128c858 100644 --- a/src/legacy/core_plugins/kibana/migrations/migrations.test.js +++ b/src/legacy/core_plugins/kibana/migrations/migrations.test.js @@ -19,1312 +19,6 @@ import { migrations } from './migrations'; -describe('index-pattern', () => { - describe('6.5.0', () => { - const migrate = doc => migrations['index-pattern']['6.5.0'](doc); - - it('adds "type" and "typeMeta" properties to object when not declared', () => { - expect( - migrate({ - attributes: {}, - }) - ).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "type": undefined, - "typeMeta": undefined, - }, -} -`); - }); - - it('keeps "type" and "typeMeta" properties as is when declared', () => { - expect( - migrate({ - attributes: { - type: '123', - typeMeta: '123', - }, - }) - ).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "type": "123", - "typeMeta": "123", - }, -} -`); - }); - }); - - 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', - 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', - 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', () => { - describe('date histogram time zone removal', () => { - const migrate = doc => migrations.visualization['6.7.2'](doc); - let doc; - beforeEach(() => { - doc = { - attributes: { - visState: JSON.stringify({ - aggs: [ - { - enabled: true, - id: '1', - params: { - // Doesn't make much sense but we want to test it's not removing it from anything else - time_zone: 'Europe/Berlin', - }, - schema: 'metric', - type: 'count', - }, - { - enabled: true, - id: '2', - params: { - customInterval: '2h', - drop_partials: false, - extended_bounds: {}, - field: 'timestamp', - time_zone: 'Europe/Berlin', - interval: 'auto', - min_doc_count: 1, - useNormalizedEsInterval: true, - }, - schema: 'segment', - type: 'date_histogram', - }, - { - enabled: true, - id: '4', - params: { - customInterval: '2h', - drop_partials: false, - extended_bounds: {}, - field: 'timestamp', - interval: 'auto', - min_doc_count: 1, - useNormalizedEsInterval: true, - }, - schema: 'segment', - type: 'date_histogram', - }, - { - enabled: true, - id: '3', - params: { - customBucket: { - enabled: true, - id: '1-bucket', - params: { - customInterval: '2h', - drop_partials: false, - extended_bounds: {}, - field: 'timestamp', - interval: 'auto', - min_doc_count: 1, - time_zone: 'Europe/Berlin', - useNormalizedEsInterval: true, - }, - type: 'date_histogram', - }, - customMetric: { - enabled: true, - id: '1-metric', - params: {}, - type: 'count', - }, - }, - schema: 'metric', - type: 'max_bucket', - }, - ], - }), - }, - }; - }); - - it('should remove time_zone from date_histogram aggregations', () => { - const migratedDoc = migrate(doc); - const aggs = JSON.parse(migratedDoc.attributes.visState).aggs; - expect(aggs[1]).not.toHaveProperty('params.time_zone'); - }); - - it('should not remove time_zone from non date_histogram aggregations', () => { - const migratedDoc = migrate(doc); - const aggs = JSON.parse(migratedDoc.attributes.visState).aggs; - expect(aggs[0]).toHaveProperty('params.time_zone'); - }); - - it('should remove time_zone from nested aggregations', () => { - const migratedDoc = migrate(doc); - const aggs = JSON.parse(migratedDoc.attributes.visState).aggs; - expect(aggs[3]).not.toHaveProperty('params.customBucket.params.time_zone'); - }); - - it('should not fail on date histograms without a time_zone', () => { - const migratedDoc = migrate(doc); - const aggs = JSON.parse(migratedDoc.attributes.visState).aggs; - expect(aggs[2]).not.toHaveProperty('params.time_zone'); - }); - - it('should be able to apply the migration twice, since we need it for 6.7.2 and 7.0.1', () => { - const migratedDoc = migrate(migrate(doc)); - const aggs = JSON.parse(migratedDoc.attributes.visState).aggs; - expect(aggs[1]).not.toHaveProperty('params.time_zone'); - expect(aggs[0]).toHaveProperty('params.time_zone'); - expect(aggs[3]).not.toHaveProperty('params.customBucket.params.time_zone'); - expect(aggs[2]).not.toHaveProperty('params.time_zone'); - }); - }); - - describe('7.0.0', () => { - const migrate = doc => migrations.visualization['7.0.0'](doc); - const generateDoc = ({ type, aggs }) => ({ - attributes: { - title: 'My Vis', - description: 'This is my super cool vis.', - visState: JSON.stringify({ type, aggs }), - uiStateJSON: '{}', - version: 1, - kibanaSavedObjectMeta: { - searchSourceJSON: '{}', - }, - }, - }); - - it('does not throw error on empty object', () => { - const migratedDoc = migrate({ - attributes: { - visState: '{}', - }, - }); - expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "visState": "{}", - }, - "references": Array [], -} -`); - }); - - it('skips errors when searchSourceJSON is null', () => { - const doc = { - id: '1', - type: 'visualization', - attributes: { - visState: '{}', - kibanaSavedObjectMeta: { - searchSourceJSON: null, - }, - savedSearchId: '123', - }, - }; - const migratedDoc = migrate(doc); - expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": null, - }, - "savedSearchRefName": "search_0", - "visState": "{}", - }, - "id": "1", - "references": Array [ - Object { - "id": "123", - "name": "search_0", - "type": "search", - }, - ], - "type": "visualization", -} -`); - }); - - it('skips errors when searchSourceJSON is undefined', () => { - const doc = { - id: '1', - type: 'visualization', - attributes: { - visState: '{}', - kibanaSavedObjectMeta: { - searchSourceJSON: undefined, - }, - savedSearchId: '123', - }, - }; - const migratedDoc = migrate(doc); - expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": undefined, - }, - "savedSearchRefName": "search_0", - "visState": "{}", - }, - "id": "1", - "references": Array [ - Object { - "id": "123", - "name": "search_0", - "type": "search", - }, - ], - "type": "visualization", -} -`); - }); - - it('skips error when searchSourceJSON is not a string', () => { - const doc = { - id: '1', - type: 'visualization', - attributes: { - visState: '{}', - kibanaSavedObjectMeta: { - searchSourceJSON: 123, - }, - savedSearchId: '123', - }, - }; - expect(migrate(doc)).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": 123, - }, - "savedSearchRefName": "search_0", - "visState": "{}", - }, - "id": "1", - "references": Array [ - Object { - "id": "123", - "name": "search_0", - "type": "search", - }, - ], - "type": "visualization", -} -`); - }); - - it('skips error when searchSourceJSON is invalid json', () => { - const doc = { - id: '1', - type: 'visualization', - attributes: { - visState: '{}', - kibanaSavedObjectMeta: { - searchSourceJSON: '{abc123}', - }, - savedSearchId: '123', - }, - }; - expect(migrate(doc)).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": "{abc123}", - }, - "savedSearchRefName": "search_0", - "visState": "{}", - }, - "id": "1", - "references": Array [ - Object { - "id": "123", - "name": "search_0", - "type": "search", - }, - ], - "type": "visualization", -} -`); - }); - - it('skips error when "index" and "filter" is missing from searchSourceJSON', () => { - const doc = { - id: '1', - type: 'visualization', - attributes: { - visState: '{}', - kibanaSavedObjectMeta: { - searchSourceJSON: JSON.stringify({ bar: true }), - }, - savedSearchId: '123', - }, - }; - const migratedDoc = migrate(doc); - expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": "{\\"bar\\":true}", - }, - "savedSearchRefName": "search_0", - "visState": "{}", - }, - "id": "1", - "references": Array [ - Object { - "id": "123", - "name": "search_0", - "type": "search", - }, - ], - "type": "visualization", -} -`); - }); - - it('extracts "index" attribute from doc', () => { - const doc = { - id: '1', - type: 'visualization', - attributes: { - visState: '{}', - kibanaSavedObjectMeta: { - searchSourceJSON: JSON.stringify({ bar: true, index: 'pattern*' }), - }, - savedSearchId: '123', - }, - }; - const migratedDoc = migrate(doc); - expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": "{\\"bar\\":true,\\"indexRefName\\":\\"kibanaSavedObjectMeta.searchSourceJSON.index\\"}", - }, - "savedSearchRefName": "search_0", - "visState": "{}", - }, - "id": "1", - "references": Array [ - Object { - "id": "pattern*", - "name": "kibanaSavedObjectMeta.searchSourceJSON.index", - "type": "index-pattern", - }, - Object { - "id": "123", - "name": "search_0", - "type": "search", - }, - ], - "type": "visualization", -} -`); - }); - - it('extracts index patterns from the filter', () => { - const doc = { - id: '1', - type: 'visualization', - attributes: { - visState: '{}', - kibanaSavedObjectMeta: { - searchSourceJSON: JSON.stringify({ - bar: true, - filter: [ - { - meta: { index: 'my-index', foo: true }, - }, - ], - }), - }, - savedSearchId: '123', - }, - }; - const migratedDoc = migrate(doc); - - expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": "{\\"bar\\":true,\\"filter\\":[{\\"meta\\":{\\"foo\\":true,\\"indexRefName\\":\\"kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index\\"}}]}", - }, - "savedSearchRefName": "search_0", - "visState": "{}", - }, - "id": "1", - "references": Array [ - Object { - "id": "my-index", - "name": "kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index", - "type": "index-pattern", - }, - Object { - "id": "123", - "name": "search_0", - "type": "search", - }, - ], - "type": "visualization", -} -`); - }); - - it('extracts index patterns from controls', () => { - const doc = { - id: '1', - type: 'visualization', - attributes: { - foo: true, - visState: JSON.stringify({ - bar: false, - params: { - controls: [ - { - bar: true, - indexPattern: 'pattern*', - }, - { - foo: true, - }, - ], - }, - }), - }, - }; - const migratedDoc = migrate(doc); - - expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "foo": true, - "visState": "{\\"bar\\":false,\\"params\\":{\\"controls\\":[{\\"bar\\":true,\\"indexPatternRefName\\":\\"control_0_index_pattern\\"},{\\"foo\\":true}]}}", - }, - "id": "1", - "references": Array [ - Object { - "id": "pattern*", - "name": "control_0_index_pattern", - "type": "index-pattern", - }, - ], - "type": "visualization", -} -`); - }); - - it('skips extracting savedSearchId when missing', () => { - const doc = { - id: '1', - attributes: { - visState: '{}', - kibanaSavedObjectMeta: { - searchSourceJSON: '{}', - }, - }, - }; - const migratedDoc = migrate(doc); - expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": "{}", - }, - "visState": "{}", - }, - "id": "1", - "references": Array [], -} -`); - }); - - it('extract savedSearchId from doc', () => { - const doc = { - id: '1', - attributes: { - visState: '{}', - kibanaSavedObjectMeta: { - searchSourceJSON: '{}', - }, - savedSearchId: '123', - }, - }; - const migratedDoc = migrate(doc); - expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": "{}", - }, - "savedSearchRefName": "search_0", - "visState": "{}", - }, - "id": "1", - "references": Array [ - Object { - "id": "123", - "name": "search_0", - "type": "search", - }, - ], -} -`); - }); - - it('delete savedSearchId when empty string in doc', () => { - const doc = { - id: '1', - attributes: { - visState: '{}', - kibanaSavedObjectMeta: { - searchSourceJSON: '{}', - }, - savedSearchId: '', - }, - }; - const migratedDoc = migrate(doc); - expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": "{}", - }, - "visState": "{}", - }, - "id": "1", - "references": Array [], -} -`); - }); - - it('should return a new object if vis is table and has multiple split aggs', () => { - const aggs = [ - { - id: '1', - schema: 'metric', - params: {}, - }, - { - id: '2', - schema: 'split', - params: { foo: 'bar', row: true }, - }, - { - id: '3', - schema: 'split', - params: { hey: 'ya', row: false }, - }, - ]; - const tableDoc = generateDoc({ type: 'table', aggs }); - const expected = tableDoc; - const actual = migrate(tableDoc); - expect(actual).not.toBe(expected); - }); - - it('should not touch any vis that is not table', () => { - const aggs = []; - const pieDoc = generateDoc({ type: 'pie', aggs }); - const expected = pieDoc; - const actual = migrate(pieDoc); - expect(actual).toBe(expected); - }); - - it('should not change values in any vis that is not table', () => { - const aggs = [ - { - id: '1', - schema: 'metric', - params: {}, - }, - { - id: '2', - schema: 'split', - params: { foo: 'bar', row: true }, - }, - { - id: '3', - schema: 'segment', - params: { hey: 'ya' }, - }, - ]; - const pieDoc = generateDoc({ type: 'pie', aggs }); - const expected = pieDoc; - const actual = migrate(pieDoc); - expect(actual).toEqual(expected); - }); - - it('should not touch table vis if there are not multiple split aggs', () => { - const aggs = [ - { - id: '1', - schema: 'metric', - params: {}, - }, - { - id: '2', - schema: 'split', - params: { foo: 'bar', row: true }, - }, - ]; - const tableDoc = generateDoc({ type: 'table', aggs }); - const expected = tableDoc; - const actual = migrate(tableDoc); - expect(actual).toBe(expected); - }); - - it('should change all split aggs to `bucket` except the first', () => { - const aggs = [ - { - id: '1', - schema: 'metric', - params: {}, - }, - { - id: '2', - schema: 'split', - params: { foo: 'bar', row: true }, - }, - { - id: '3', - schema: 'split', - params: { hey: 'ya', row: false }, - }, - { - id: '4', - schema: 'bucket', - params: { heyyy: 'yaaa' }, - }, - ]; - const expected = ['metric', 'split', 'bucket', 'bucket']; - const migrated = migrate(generateDoc({ type: 'table', aggs })); - const actual = JSON.parse(migrated.attributes.visState); - expect(actual.aggs.map(agg => agg.schema)).toEqual(expected); - }); - - it('should remove `rows` param from any aggs that are not `split`', () => { - const aggs = [ - { - id: '1', - schema: 'metric', - params: {}, - }, - { - id: '2', - schema: 'split', - params: { foo: 'bar', row: true }, - }, - { - id: '3', - schema: 'split', - params: { hey: 'ya', row: false }, - }, - ]; - const expected = [{}, { foo: 'bar', row: true }, { hey: 'ya' }]; - const migrated = migrate(generateDoc({ type: 'table', aggs })); - const actual = JSON.parse(migrated.attributes.visState); - expect(actual.aggs.map(agg => agg.params)).toEqual(expected); - }); - - it('should throw with a reference to the doc name if something goes wrong', () => { - const doc = { - attributes: { - title: 'My Vis', - description: 'This is my super cool vis.', - visState: '!/// Intentionally malformed JSON ///!', - uiStateJSON: '{}', - version: 1, - kibanaSavedObjectMeta: { - searchSourceJSON: '{}', - }, - }, - }; - expect(() => migrate(doc)).toThrowError(/My Vis/); - }); - }); - - describe('date histogram custom interval removal', () => { - const migrate = doc => migrations.visualization['7.2.0'](doc); - let doc; - beforeEach(() => { - doc = { - attributes: { - visState: JSON.stringify({ - aggs: [ - { - enabled: true, - id: '1', - params: { - customInterval: '1h', - }, - schema: 'metric', - type: 'count', - }, - { - enabled: true, - id: '2', - params: { - customInterval: '2h', - drop_partials: false, - extended_bounds: {}, - field: 'timestamp', - interval: 'auto', - min_doc_count: 1, - useNormalizedEsInterval: true, - }, - schema: 'segment', - type: 'date_histogram', - }, - { - enabled: true, - id: '4', - params: { - customInterval: '2h', - drop_partials: false, - extended_bounds: {}, - field: 'timestamp', - interval: 'custom', - min_doc_count: 1, - useNormalizedEsInterval: true, - }, - schema: 'segment', - type: 'date_histogram', - }, - { - enabled: true, - id: '3', - params: { - customBucket: { - enabled: true, - id: '1-bucket', - params: { - customInterval: '2h', - drop_partials: false, - extended_bounds: {}, - field: 'timestamp', - interval: 'custom', - min_doc_count: 1, - useNormalizedEsInterval: true, - }, - type: 'date_histogram', - }, - customMetric: { - enabled: true, - id: '1-metric', - params: {}, - type: 'count', - }, - }, - schema: 'metric', - type: 'max_bucket', - }, - ], - }), - }, - }; - }); - - it('should remove customInterval from date_histogram aggregations', () => { - const migratedDoc = migrate(doc); - const aggs = JSON.parse(migratedDoc.attributes.visState).aggs; - expect(aggs[1]).not.toHaveProperty('params.customInterval'); - }); - - it('should not change interval from date_histogram aggregations', () => { - const migratedDoc = migrate(doc); - const aggs = JSON.parse(migratedDoc.attributes.visState).aggs; - expect(aggs[1].params.interval).toBe( - JSON.parse(doc.attributes.visState).aggs[1].params.interval - ); - }); - - it('should not remove customInterval from non date_histogram aggregations', () => { - const migratedDoc = migrate(doc); - const aggs = JSON.parse(migratedDoc.attributes.visState).aggs; - expect(aggs[0]).toHaveProperty('params.customInterval'); - }); - - it('should set interval with customInterval value and remove customInterval when interval equals "custom"', () => { - const migratedDoc = migrate(doc); - const aggs = JSON.parse(migratedDoc.attributes.visState).aggs; - expect(aggs[2].params.interval).toBe( - JSON.parse(doc.attributes.visState).aggs[2].params.customInterval - ); - expect(aggs[2]).not.toHaveProperty('params.customInterval'); - }); - - it('should remove customInterval from nested aggregations', () => { - const migratedDoc = migrate(doc); - const aggs = JSON.parse(migratedDoc.attributes.visState).aggs; - expect(aggs[3]).not.toHaveProperty('params.customBucket.params.customInterval'); - }); - - it('should remove customInterval from nested aggregations and set interval with customInterval value', () => { - const migratedDoc = migrate(doc); - const aggs = JSON.parse(migratedDoc.attributes.visState).aggs; - expect(aggs[3].params.customBucket.params.interval).toBe( - JSON.parse(doc.attributes.visState).aggs[3].params.customBucket.params.customInterval - ); - expect(aggs[3]).not.toHaveProperty('params.customBucket.params.customInterval'); - }); - - it('should not fail on date histograms without a customInterval', () => { - const migratedDoc = migrate(doc); - const aggs = JSON.parse(migratedDoc.attributes.visState).aggs; - expect(aggs[3]).not.toHaveProperty('params.customInterval'); - }); - }); - describe('7.3.0', () => { - const logMsgArr = []; - const logger = { - warning: msg => logMsgArr.push(msg), - }; - const migrate = doc => migrations.visualization['7.3.0'](doc, logger); - - it('migrates type = gauge verticalSplit: false to alignment: vertical', () => { - const migratedDoc = migrate({ - attributes: { - visState: JSON.stringify({ type: 'gauge', params: { gauge: { verticalSplit: false } } }), - }, - }); - expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "visState": "{\\"type\\":\\"gauge\\",\\"params\\":{\\"gauge\\":{\\"alignment\\":\\"horizontal\\"}}}", - }, -} -`); - }); - - it('migrates type = gauge verticalSplit: false to alignment: horizontal', () => { - const migratedDoc = migrate({ - attributes: { - visState: JSON.stringify({ type: 'gauge', params: { gauge: { verticalSplit: true } } }), - }, - }); - expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "visState": "{\\"type\\":\\"gauge\\",\\"params\\":{\\"gauge\\":{\\"alignment\\":\\"vertical\\"}}}", - }, -} -`); - }); - - it('doesnt migrate type = gauge containing invalid visState object, adds message to log', () => { - const migratedDoc = migrate({ - attributes: { - visState: JSON.stringify({ type: 'gauge' }), - }, - }); - expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "visState": "{\\"type\\":\\"gauge\\"}", - }, -} -`); - expect(logMsgArr).toMatchInlineSnapshot(` -Array [ - "Exception @ migrateGaugeVerticalSplitToAlignment! TypeError: Cannot read property 'gauge' of undefined", - "Exception @ migrateGaugeVerticalSplitToAlignment! Payload: {\\"type\\":\\"gauge\\"}", -] -`); - }); - - describe('filters agg query migration', () => { - const doc = { - attributes: { - visState: JSON.stringify({ - aggs: [ - { - type: 'filters', - params: { - filters: [ - { - input: { - query: 'response:200', - }, - label: '', - }, - { - input: { - query: 'response:404', - }, - label: 'bad response', - }, - { - input: { - query: { - exists: { - field: 'phpmemory', - }, - }, - }, - label: '', - }, - ], - }, - }, - ], - }), - }, - }; - - it('should add language property to filters without one, assuming lucene', () => { - const migrationResult = migrate(doc); - expect(migrationResult).toEqual({ - attributes: { - visState: JSON.stringify({ - aggs: [ - { - type: 'filters', - params: { - filters: [ - { - input: { - query: 'response:200', - language: 'lucene', - }, - label: '', - }, - { - input: { - query: 'response:404', - language: 'lucene', - }, - label: 'bad response', - }, - { - input: { - query: { - exists: { - field: 'phpmemory', - }, - }, - language: 'lucene', - }, - label: '', - }, - ], - }, - }, - ], - }), - }, - }); - }); - }); - - describe('replaceMovAvgToMovFn()', () => { - let doc; - - beforeEach(() => { - doc = { - attributes: { - title: 'VIS', - visState: `{"title":"VIS","type":"metrics","params":{"id":"61ca57f0-469d-11e7-af02-69e470af7417", - "type":"timeseries","series":[{"id":"61ca57f1-469d-11e7-af02-69e470af7417","color":"rgba(0,156,224,1)", - "split_mode":"terms","metrics":[{"id":"61ca57f2-469d-11e7-af02-69e470af7417","type":"count", - "numerator":"FlightDelay:true"},{"settings":"","minimize":0,"window":5,"model": - "holt_winters","id":"23054fe0-8915-11e9-9b86-d3f94982620f","type":"moving_average","field": - "61ca57f2-469d-11e7-af02-69e470af7417","predict":1}],"separate_axis":0,"axis_position":"right", - "formatter":"number","chart_type":"line","line_width":"2","point_size":"0","fill":0.5,"stacked":"none", - "label":"Percent Delays","terms_size":"2","terms_field":"OriginCityName"}],"time_field":"timestamp", - "index_pattern":"kibana_sample_data_flights","interval":">=12h","axis_position":"left","axis_formatter": - "number","show_legend":1,"show_grid":1,"annotations":[{"fields":"FlightDelay,Cancelled,Carrier", - "template":"{{Carrier}}: Flight Delayed and Cancelled!","index_pattern":"kibana_sample_data_flights", - "query_string":"FlightDelay:true AND Cancelled:true","id":"53b7dff0-4c89-11e8-a66a-6989ad5a0a39", - "color":"rgba(0,98,177,1)","time_field":"timestamp","icon":"fa-exclamation-triangle", - "ignore_global_filters":1,"ignore_panel_filters":1,"hidden":true}],"legend_position":"bottom", - "axis_scale":"normal","default_index_pattern":"kibana_sample_data_flights","default_timefield":"timestamp"}, - "aggs":[]}`, - }, - migrationVersion: { - visualization: '7.2.0', - }, - type: 'visualization', - }; - }); - - test('should add some necessary moving_fn fields', () => { - const migratedDoc = migrate(doc); - const visState = JSON.parse(migratedDoc.attributes.visState); - const metric = visState.params.series[0].metrics[1]; - - expect(metric).toHaveProperty('model_type'); - expect(metric).toHaveProperty('alpha'); - expect(metric).toHaveProperty('beta'); - expect(metric).toHaveProperty('gamma'); - expect(metric).toHaveProperty('period'); - expect(metric).toHaveProperty('multiplicative'); - }); - }); - }); - describe('7.3.0 tsvb', () => { - const migrate = doc => migrations.visualization['7.3.0'](doc); - const generateDoc = ({ params }) => ({ - attributes: { - title: 'My Vis', - description: 'This is my super cool vis.', - visState: JSON.stringify({ params }), - uiStateJSON: '{}', - version: 1, - kibanaSavedObjectMeta: { - searchSourceJSON: '{}', - }, - }, - }); - it('should change series item filters from a string into an object', () => { - const params = { type: 'metric', series: [{ filter: 'Filter Bytes Test:>1000' }] }; - const testDoc1 = generateDoc({ params }); - const migratedTestDoc1 = migrate(testDoc1); - const series = JSON.parse(migratedTestDoc1.attributes.visState).params.series; - expect(series[0].filter).toHaveProperty('query'); - expect(series[0].filter).toHaveProperty('language'); - }); - it('should not change a series item filter string in the object after migration', () => { - const markdownParams = { - type: 'markdown', - series: [ - { - filter: 'Filter Bytes Test:>1000', - split_filters: [{ filter: 'bytes:>1000' }], - }, - ], - }; - const markdownDoc = generateDoc({ params: markdownParams }); - const migratedMarkdownDoc = migrate(markdownDoc); - const markdownSeries = JSON.parse(migratedMarkdownDoc.attributes.visState).params.series; - expect(markdownSeries[0].filter.query).toBe( - JSON.parse(markdownDoc.attributes.visState).params.series[0].filter - ); - expect(markdownSeries[0].split_filters[0].filter.query).toBe( - JSON.parse(markdownDoc.attributes.visState).params.series[0].split_filters[0].filter - ); - }); - it('should change series item filters from a string into an object for all filters', () => { - const params = { - type: 'timeseries', - filter: 'bytes:>1000', - series: [ - { - filter: 'Filter Bytes Test:>1000', - split_filters: [{ filter: 'bytes:>1000' }], - }, - ], - annotations: [{ query_string: 'bytes:>1000' }], - }; - const timeSeriesDoc = generateDoc({ params: params }); - const migratedtimeSeriesDoc = migrate(timeSeriesDoc); - const timeSeriesParams = JSON.parse(migratedtimeSeriesDoc.attributes.visState).params; - expect(Object.keys(timeSeriesParams.series[0].filter)).toEqual( - expect.arrayContaining(['query', 'language']) - ); - expect(Object.keys(timeSeriesParams.series[0].split_filters[0].filter)).toEqual( - expect.arrayContaining(['query', 'language']) - ); - expect(Object.keys(timeSeriesParams.annotations[0].query_string)).toEqual( - expect.arrayContaining(['query', 'language']) - ); - }); - it('should not fail on a metric visualization without a filter in a series item', () => { - const params = { type: 'metric', series: [{}, {}, {}] }; - const testDoc1 = generateDoc({ params }); - const migratedTestDoc1 = migrate(testDoc1); - const series = JSON.parse(migratedTestDoc1.attributes.visState).params.series; - expect(series[2]).not.toHaveProperty('filter.query'); - }); - it('should not migrate a visualization of unknown type', () => { - const params = { type: 'unknown', series: [{ filter: 'foo:bar' }] }; - const doc = generateDoc({ params }); - const migratedDoc = migrate(doc); - const series = JSON.parse(migratedDoc.attributes.visState).params.series; - expect(series[0].filter).toEqual(params.series[0].filter); - }); - }); - - describe('7.3.1', () => { - const migrate = migrations.visualization['7.3.1']; - - it('should migrate filters agg query string queries', () => { - const state = { - aggs: [ - { type: 'count', params: {} }, - { - type: 'filters', - params: { - filters: [ - { - input: { - query: { - query_string: { query: 'machine.os.keyword:"win 8"' }, - }, - }, - }, - ], - }, - }, - ], - }; - const expected = { - aggs: [ - { type: 'count', params: {} }, - { - type: 'filters', - params: { - filters: [{ input: { query: 'machine.os.keyword:"win 8"' } }], - }, - }, - ], - }; - const migratedDoc = migrate({ attributes: { visState: JSON.stringify(state) } }); - expect(migratedDoc).toEqual({ attributes: { visState: JSON.stringify(expected) } }); - }); - }); - describe('7.4.2 tsvb split_filters migration', () => { - const migrate = doc => migrations.visualization['7.4.2'](doc); - const generateDoc = ({ params }) => ({ - attributes: { - title: 'My Vis', - description: 'This is my super cool vis.', - visState: JSON.stringify({ params }), - uiStateJSON: '{}', - version: 1, - kibanaSavedObjectMeta: { - searchSourceJSON: '{}', - }, - }, - }); - it('should change series item filters from a string into an object for all filters', () => { - const params = { - type: 'timeseries', - filter: { - query: 'bytes:>1000', - language: 'lucene', - }, - series: [ - { - split_filters: [{ filter: 'bytes:>1000' }], - }, - ], - }; - const timeSeriesDoc = generateDoc({ params: params }); - const migratedtimeSeriesDoc = migrate(timeSeriesDoc); - const timeSeriesParams = JSON.parse(migratedtimeSeriesDoc.attributes.visState).params; - expect(Object.keys(timeSeriesParams.filter)).toEqual( - expect.arrayContaining(['query', 'language']) - ); - expect(timeSeriesParams.series[0].split_filters[0].filter).toEqual({ - query: 'bytes:>1000', - language: 'lucene', - }); - }); - it('should change series item split filters when there is no filter item', () => { - const params = { - type: 'timeseries', - filter: { - query: 'bytes:>1000', - language: 'lucene', - }, - series: [ - { - split_filters: [{ filter: 'bytes:>1000' }], - }, - ], - annotations: [ - { - query_string: { - query: 'bytes:>1000', - language: 'lucene', - }, - }, - ], - }; - const timeSeriesDoc = generateDoc({ params: params }); - const migratedtimeSeriesDoc = migrate(timeSeriesDoc); - const timeSeriesParams = JSON.parse(migratedtimeSeriesDoc.attributes.visState).params; - expect(timeSeriesParams.series[0].split_filters[0].filter).toEqual({ - query: 'bytes:>1000', - language: 'lucene', - }); - }); - it('should not convert split_filters to objects if there are no split filter filters', () => { - const params = { - type: 'timeseries', - filter: { - query: 'bytes:>1000', - language: 'lucene', - }, - series: [ - { - split_filters: [], - }, - ], - }; - const timeSeriesDoc = generateDoc({ params: params }); - const migratedtimeSeriesDoc = migrate(timeSeriesDoc); - const timeSeriesParams = JSON.parse(migratedtimeSeriesDoc.attributes.visState).params; - expect(timeSeriesParams.series[0].split_filters).not.toHaveProperty('query'); - }); - it('should do nothing if a split_filter is already a query:language object', () => { - const params = { - type: 'timeseries', - filter: { - query: 'bytes:>1000', - language: 'lucene', - }, - series: [ - { - split_filters: [ - { - filter: { - query: 'bytes:>1000', - language: 'lucene', - }, - }, - ], - }, - ], - annotations: [ - { - query_string: { - query: 'bytes:>1000', - language: 'lucene', - }, - }, - ], - }; - const timeSeriesDoc = generateDoc({ params: params }); - const migratedtimeSeriesDoc = migrate(timeSeriesDoc); - const timeSeriesParams = JSON.parse(migratedtimeSeriesDoc.attributes.visState).params; - expect(timeSeriesParams.series[0].split_filters[0].filter.query).toEqual('bytes:>1000'); - expect(timeSeriesParams.series[0].split_filters[0].filter.language).toEqual('lucene'); - }); - }); -}); - describe('dashboard', () => { describe('7.0.0', () => { const migration = migrations.dashboard['7.0.0']; @@ -1751,271 +445,3 @@ Object { }); }); }); - -describe('search', () => { - describe('7.0.0', () => { - const migration = migrations.search['7.0.0']; - - test('skips errors when searchSourceJSON is null', () => { - const doc = { - id: '123', - type: 'search', - attributes: { - foo: true, - kibanaSavedObjectMeta: { - searchSourceJSON: null, - }, - }, - }; - const migratedDoc = migration(doc); - expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "foo": true, - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": null, - }, - }, - "id": "123", - "references": Array [], - "type": "search", -} -`); - }); - - test('skips errors when searchSourceJSON is undefined', () => { - const doc = { - id: '123', - type: 'search', - attributes: { - foo: true, - kibanaSavedObjectMeta: { - searchSourceJSON: undefined, - }, - }, - }; - const migratedDoc = migration(doc); - expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "foo": true, - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": undefined, - }, - }, - "id": "123", - "references": Array [], - "type": "search", -} -`); - }); - - test('skips error when searchSourceJSON is not a string', () => { - const doc = { - id: '123', - type: 'search', - attributes: { - foo: true, - kibanaSavedObjectMeta: { - searchSourceJSON: 123, - }, - }, - }; - expect(migration(doc)).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "foo": true, - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": 123, - }, - }, - "id": "123", - "references": Array [], - "type": "search", -} -`); - }); - - test('skips error when searchSourceJSON is invalid json', () => { - const doc = { - id: '123', - type: 'search', - attributes: { - foo: true, - kibanaSavedObjectMeta: { - searchSourceJSON: '{abc123}', - }, - }, - }; - expect(migration(doc)).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "foo": true, - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": "{abc123}", - }, - }, - "id": "123", - "references": Array [], - "type": "search", -} -`); - }); - - test('skips error when "index" and "filter" is missing from searchSourceJSON', () => { - const doc = { - id: '123', - type: 'search', - attributes: { - foo: true, - kibanaSavedObjectMeta: { - searchSourceJSON: JSON.stringify({ bar: true }), - }, - }, - }; - const migratedDoc = migration(doc); - expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "foo": true, - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": "{\\"bar\\":true}", - }, - }, - "id": "123", - "references": Array [], - "type": "search", -} -`); - }); - - test('extracts "index" attribute from doc', () => { - const doc = { - id: '123', - type: 'search', - attributes: { - foo: true, - kibanaSavedObjectMeta: { - searchSourceJSON: JSON.stringify({ bar: true, index: 'pattern*' }), - }, - }, - }; - const migratedDoc = migration(doc); - expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "foo": true, - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": "{\\"bar\\":true,\\"indexRefName\\":\\"kibanaSavedObjectMeta.searchSourceJSON.index\\"}", - }, - }, - "id": "123", - "references": Array [ - Object { - "id": "pattern*", - "name": "kibanaSavedObjectMeta.searchSourceJSON.index", - "type": "index-pattern", - }, - ], - "type": "search", -} -`); - }); - - test('extracts index patterns from filter', () => { - const doc = { - id: '123', - type: 'search', - attributes: { - foo: true, - kibanaSavedObjectMeta: { - searchSourceJSON: JSON.stringify({ - bar: true, - filter: [ - { - meta: { - foo: true, - index: 'my-index', - }, - }, - ], - }), - }, - }, - }; - const migratedDoc = migration(doc); - - expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "foo": true, - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": "{\\"bar\\":true,\\"filter\\":[{\\"meta\\":{\\"foo\\":true,\\"indexRefName\\":\\"kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index\\"}}]}", - }, - }, - "id": "123", - "references": Array [ - Object { - "id": "my-index", - "name": "kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index", - "type": "index-pattern", - }, - ], - "type": "search", -} -`); - }); - }); - - describe('7.4.0', function() { - const migration = migrations.search['7.4.0']; - - test('transforms one dimensional sort arrays into two dimensional arrays', () => { - const doc = { - id: '123', - type: 'search', - attributes: { - sort: ['bytes', 'desc'], - }, - }; - - const expected = { - id: '123', - type: 'search', - attributes: { - sort: [['bytes', 'desc']], - }, - }; - - const migratedDoc = migration(doc); - - expect(migratedDoc).toEqual(expected); - }); - - test("doesn't modify search docs that already have two dimensional sort arrays", () => { - const doc = { - id: '123', - type: 'search', - attributes: { - sort: [['bytes', 'desc']], - }, - }; - - const migratedDoc = migration(doc); - - expect(migratedDoc).toEqual(doc); - }); - - test("doesn't modify search docs that have no sort array", () => { - const doc = { - id: '123', - type: 'search', - attributes: {}, - }; - - const migratedDoc = migration(doc); - - expect(migratedDoc).toEqual(doc); - }); - }); -}); diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/embeddable/search_embeddable.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/embeddable/search_embeddable.ts index 91726c69189f3..d09b7612af49c 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/embeddable/search_embeddable.ts +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/embeddable/search_embeddable.ts @@ -20,7 +20,10 @@ import _ from 'lodash'; import * as Rx from 'rxjs'; import { Subscription } from 'rxjs'; import { i18n } from '@kbn/i18n'; -import { UiActionsStart } from 'src/plugins/ui_actions/public'; +import { + UiActionsStart, + APPLY_FILTER_TRIGGER, +} from '../../../../../../..//plugins/ui_actions/public'; import { RequestAdapter, Adapters } from '../../../../../../../plugins/inspector/public'; import { esFilters, @@ -31,11 +34,7 @@ import { Query, IFieldType, } from '../../../../../../../plugins/data/public'; -import { - APPLY_FILTER_TRIGGER, - Container, - Embeddable, -} from '../../../../../embeddable_api/public/np_ready/public'; +import { Container, Embeddable } from '../../../../../embeddable_api/public/np_ready/public'; import * as columnActions from '../angular/doc_table/actions/columns'; import searchTemplate from './search_template.html'; import { ISearchEmbeddable, SearchInput, SearchOutput } from './types'; diff --git a/src/legacy/core_plugins/kibana/ui_setting_defaults.js b/src/legacy/core_plugins/kibana/ui_setting_defaults.js index c0628b72c2ce7..85b1956f45333 100644 --- a/src/legacy/core_plugins/kibana/ui_setting_defaults.js +++ b/src/legacy/core_plugins/kibana/ui_setting_defaults.js @@ -16,11 +16,13 @@ * specific language governing permissions and limitations * under the License. */ - import moment from 'moment-timezone'; import numeralLanguages from '@elastic/numeral/languages'; import { i18n } from '@kbn/i18n'; +import { schema } from '@kbn/config-schema'; + import { DEFAULT_QUERY_LANGUAGE } from '../../../plugins/data/common'; +import { isRelativeUrl } from '../../../core/utils'; export function getUiSettingDefaults() { const weekdays = moment.weekdays().slice(); @@ -67,17 +69,23 @@ export function getUiSettingDefaults() { defaultMessage: 'Default route', }), value: '/app/kibana', - validation: { - regexString: '^/', - message: i18n.translate('kbn.advancedSettings.defaultRoute.defaultRouteValidationMessage', { - defaultMessage: 'The route must start with a slash ("/")', - }), - }, + schema: schema.string({ + validate(value) { + if (!value.startsWith('/') || !isRelativeUrl(value)) { + return i18n.translate( + 'kbn.advancedSettings.defaultRoute.defaultRouteIsRelativeValidationMessage', + { + defaultMessage: 'Must be a relative URL.', + } + ); + } + }, + }), description: i18n.translate('kbn.advancedSettings.defaultRoute.defaultRouteText', { defaultMessage: 'This setting specifies the default route when opening Kibana. ' + 'You can use this setting to modify the landing page when opening Kibana. ' + - 'The route must start with a slash ("/").', + 'The route must be a relative URL.', }), }, 'query:queryString:options': { diff --git a/src/legacy/core_plugins/tests_bundle/tests_entry_template.js b/src/legacy/core_plugins/tests_bundle/tests_entry_template.js index 57adf730f3dd9..3e3dc284671da 100644 --- a/src/legacy/core_plugins/tests_bundle/tests_entry_template.js +++ b/src/legacy/core_plugins/tests_bundle/tests_entry_template.js @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ - +import { Type } from '@kbn/config-schema'; import pkg from '../../../../package.json'; export const createTestEntryTemplate = defaultUiSettings => bundle => ` @@ -87,7 +87,14 @@ const coreSystem = new CoreSystem({ buildNum: 1234, devMode: true, uiSettings: { - defaults: ${JSON.stringify(defaultUiSettings, null, 2) + defaults: ${JSON.stringify( + defaultUiSettings, + (key, value) => { + if (value instanceof Type) return null; + return value; + }, + 2 + ) .split('\n') .join('\n ')}, user: {} diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/visualize_embeddable.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/visualize_embeddable.ts index 7525345ccfe1b..474912ed508f8 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/visualize_embeddable.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/embeddable/visualize_embeddable.ts @@ -34,10 +34,12 @@ import { EmbeddableOutput, Embeddable, Container, - selectRangeTrigger, - valueClickTrigger, EmbeddableVisTriggerContext, } from '../../../../../../../plugins/embeddable/public'; +import { + selectRangeTrigger, + valueClickTrigger, +} from '../../../../../../../plugins/ui_actions/public'; import { dispatchRenderComplete } from '../../../../../../../plugins/kibana_utils/public'; import { IExpressionLoaderParams, diff --git a/src/legacy/server/http/index.js b/src/legacy/server/http/index.js index 265d71e95b301..d616afb533d0a 100644 --- a/src/legacy/server/http/index.js +++ b/src/legacy/server/http/index.js @@ -24,15 +24,12 @@ import Boom from 'boom'; import { registerHapiPlugins } from './register_hapi_plugins'; import { setupBasePathProvider } from './setup_base_path_provider'; -import { setupDefaultRouteProvider } from './setup_default_route_provider'; export default async function(kbnServer, server, config) { server = kbnServer.server; setupBasePathProvider(kbnServer); - setupDefaultRouteProvider(server); - await registerHapiPlugins(server); // provide a simple way to expose static directories @@ -60,14 +57,6 @@ export default async function(kbnServer, server, config) { }); }); - server.route({ - path: '/', - method: 'GET', - async handler(req, h) { - return h.redirect(await req.getDefaultRoute()); - }, - }); - server.route({ method: 'GET', path: '/{p*}', diff --git a/src/legacy/server/http/integration_tests/default_route_provider.test.ts b/src/legacy/server/http/integration_tests/default_route_provider.test.ts deleted file mode 100644 index d91438d904558..0000000000000 --- a/src/legacy/server/http/integration_tests/default_route_provider.test.ts +++ /dev/null @@ -1,87 +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/ui_settings/ui_settings_mixin', () => { - return jest.fn(); -}); - -import * as kbnTestServer from '../../../../test_utils/kbn_server'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { Root } from '../../../../core/server/root'; - -let mockDefaultRouteSetting: any = ''; - -describe('default route provider', () => { - let root: Root; - beforeAll(async () => { - root = kbnTestServer.createRoot({ migrations: { skip: true } }); - - await root.setup(); - await root.start(); - - const kbnServer = kbnTestServer.getKbnServer(root); - - kbnServer.server.decorate('request', 'getUiSettingsService', function() { - return { - get: (key: string) => { - if (key === 'defaultRoute') { - return Promise.resolve(mockDefaultRouteSetting); - } - throw Error(`unsupported ui setting: ${key}`); - }, - getRegistered: () => { - return { - defaultRoute: { - value: '/app/kibana', - }, - }; - }, - }; - }); - }, 30000); - - afterAll(async () => await root.shutdown()); - - it('redirects to the configured default route', async function() { - mockDefaultRouteSetting = '/app/some/default/route'; - - const { status, header } = await kbnTestServer.request.get(root, '/'); - expect(status).toEqual(302); - expect(header).toMatchObject({ - location: '/app/some/default/route', - }); - }); - - const invalidRoutes = [ - 'http://not-your-kibana.com', - '///example.com', - '//example.com', - ' //example.com', - ]; - for (const route of invalidRoutes) { - it(`falls back to /app/kibana when the configured route (${route}) is not a valid relative path`, async function() { - mockDefaultRouteSetting = route; - - const { status, header } = await kbnTestServer.request.get(root, '/'); - expect(status).toEqual(302); - expect(header).toMatchObject({ - location: '/app/kibana', - }); - }); - } -}); diff --git a/src/legacy/server/http/integration_tests/default_route_provider_config.test.ts b/src/legacy/server/http/integration_tests/default_route_provider_config.test.ts deleted file mode 100644 index 8365941cbeb10..0000000000000 --- a/src/legacy/server/http/integration_tests/default_route_provider_config.test.ts +++ /dev/null @@ -1,54 +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 * as kbnTestServer from '../../../../test_utils/kbn_server'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { Root } from '../../../../core/server/root'; - -describe('default route provider', () => { - let root: Root; - - afterEach(async () => await root.shutdown()); - - it('redirects to the configured default route', async function() { - root = kbnTestServer.createRoot({ - server: { - defaultRoute: '/app/some/default/route', - }, - migrations: { skip: true }, - }); - - await root.setup(); - await root.start(); - - const kbnServer = kbnTestServer.getKbnServer(root); - - kbnServer.server.decorate('request', 'getSavedObjectsClient', function() { - return { - get: (type: string, id: string) => ({ attributes: {} }), - }; - }); - - const { status, header } = await kbnTestServer.request.get(root, '/'); - - expect(status).toEqual(302); - expect(header).toMatchObject({ - location: '/app/some/default/route', - }); - }); -}); diff --git a/src/legacy/server/http/setup_default_route_provider.ts b/src/legacy/server/http/setup_default_route_provider.ts deleted file mode 100644 index 9a580dd1c59bd..0000000000000 --- a/src/legacy/server/http/setup_default_route_provider.ts +++ /dev/null @@ -1,74 +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 { Legacy } from 'kibana'; -import { parse } from 'url'; - -export function setupDefaultRouteProvider(server: Legacy.Server) { - server.decorate('request', 'getDefaultRoute', async function() { - // @ts-ignore - const request: Legacy.Request = this; - - const serverBasePath: string = server.config().get('server.basePath'); - - const uiSettings = request.getUiSettingsService(); - - const defaultRoute = await uiSettings.get('defaultRoute'); - const qualifiedDefaultRoute = `${request.getBasePath()}${defaultRoute}`; - - if (isRelativePath(qualifiedDefaultRoute, serverBasePath)) { - return qualifiedDefaultRoute; - } else { - server.log( - ['http', 'warn'], - `Ignoring configured default route of '${defaultRoute}', as it is malformed.` - ); - - const fallbackRoute = uiSettings.getRegistered().defaultRoute.value; - - const qualifiedFallbackRoute = `${request.getBasePath()}${fallbackRoute}`; - return qualifiedFallbackRoute; - } - }); - - function isRelativePath(candidatePath: string, basePath = '') { - // validate that `candidatePath` is not attempting a redirect to somewhere - // outside of this Kibana install - const { protocol, hostname, port, pathname } = parse( - candidatePath, - false /* parseQueryString */, - true /* slashesDenoteHost */ - ); - - // We should explicitly compare `protocol`, `port` and `hostname` to null to make sure these are not - // detected in the URL at all. For example `hostname` can be empty string for Node URL parser, but - // browser (because of various bwc reasons) processes URL differently (e.g. `///abc.com` - for browser - // hostname is `abc.com`, but for Node hostname is an empty string i.e. everything between schema (`//`) - // and the first slash that belongs to path. - if (protocol !== null || hostname !== null || port !== null) { - return false; - } - - if (!String(pathname).startsWith(basePath)) { - return false; - } - - return true; - } -} diff --git a/src/legacy/server/kbn_server.d.ts b/src/legacy/server/kbn_server.d.ts index 68b5a63871372..9952b345fa06f 100644 --- a/src/legacy/server/kbn_server.d.ts +++ b/src/legacy/server/kbn_server.d.ts @@ -92,7 +92,6 @@ declare module 'hapi' { interface Request { getSavedObjectsClient(options?: SavedObjectsClientProviderOptions): SavedObjectsClientContract; getBasePath(): string; - getDefaultRoute(): Promise; getUiSettingsService(): IUiSettingsClient; } diff --git a/src/plugins/advanced_settings/public/management_app/advanced_settings.test.tsx b/src/plugins/advanced_settings/public/management_app/advanced_settings.test.tsx index 7a2ab648ec258..6103041cf0a4c 100644 --- a/src/plugins/advanced_settings/public/management_app/advanced_settings.test.tsx +++ b/src/plugins/advanced_settings/public/management_app/advanced_settings.test.tsx @@ -22,7 +22,11 @@ import { Observable } from 'rxjs'; import { ReactWrapper } from 'enzyme'; import { mountWithI18nProvider } from 'test_utils/enzyme_helpers'; import dedent from 'dedent'; -import { UiSettingsParams, UserProvidedValues, UiSettingsType } from '../../../../core/public'; +import { + PublicUiSettingsParams, + UserProvidedValues, + UiSettingsType, +} from '../../../../core/public'; import { FieldSetting } from './types'; import { AdvancedSettingsComponent } from './advanced_settings'; import { notificationServiceMock, docLinksServiceMock } from '../../../../core/public/mocks'; @@ -68,7 +72,7 @@ function mockConfig() { remove: (key: string) => Promise.resolve(true), isCustom: (key: string) => false, isOverridden: (key: string) => Boolean(config.getAll()[key].isOverridden), - getRegistered: () => ({} as Readonly>), + getRegistered: () => ({} as Readonly>), overrideLocalDefault: (key: string, value: any) => {}, getUpdate$: () => new Observable<{ @@ -89,7 +93,7 @@ function mockConfig() { getUpdateErrors$: () => new Observable(), get: (key: string, defaultOverride?: any): any => config.getAll()[key] || defaultOverride, get$: (key: string) => new Observable(config.get(key)), - getAll: (): Readonly> => { + getAll: (): Readonly> => { return { 'test:array:setting': { ...defaultConfig, diff --git a/src/plugins/advanced_settings/public/management_app/lib/to_editable_config.test.ts b/src/plugins/advanced_settings/public/management_app/lib/to_editable_config.test.ts index 881a2eb003cc8..7ac9b281eb99a 100644 --- a/src/plugins/advanced_settings/public/management_app/lib/to_editable_config.test.ts +++ b/src/plugins/advanced_settings/public/management_app/lib/to_editable_config.test.ts @@ -17,7 +17,7 @@ * under the License. */ -import { UiSettingsParams, StringValidationRegex } from 'src/core/public'; +import { PublicUiSettingsParams, StringValidationRegex } from 'src/core/public'; import expect from '@kbn/expect'; import { toEditableConfig } from './to_editable_config'; @@ -30,7 +30,7 @@ function invoke({ name = 'woah', value = 'forreal', }: { - def?: UiSettingsParams & { isOverridden?: boolean }; + def?: PublicUiSettingsParams & { isOverridden?: boolean }; name?: string; value?: any; }) { @@ -55,7 +55,7 @@ describe('Settings', function() { }); describe('when given a setting definition object', function() { - let def: UiSettingsParams & { isOverridden?: boolean }; + let def: PublicUiSettingsParams & { isOverridden?: boolean }; beforeEach(function() { def = { value: 'the original', diff --git a/src/plugins/advanced_settings/public/management_app/lib/to_editable_config.ts b/src/plugins/advanced_settings/public/management_app/lib/to_editable_config.ts index 2c27d72f7f645..406bc35f826e8 100644 --- a/src/plugins/advanced_settings/public/management_app/lib/to_editable_config.ts +++ b/src/plugins/advanced_settings/public/management_app/lib/to_editable_config.ts @@ -18,7 +18,7 @@ */ import { - UiSettingsParams, + PublicUiSettingsParams, UserProvidedValues, StringValidationRegexString, SavedObjectAttribute, @@ -40,7 +40,7 @@ export function toEditableConfig({ isCustom, isOverridden, }: { - def: UiSettingsParams & UserProvidedValues; + def: PublicUiSettingsParams & UserProvidedValues; name: string; value: SavedObjectAttribute; isCustom: boolean; diff --git a/src/plugins/advanced_settings/public/management_app/types.ts b/src/plugins/advanced_settings/public/management_app/types.ts index d44a05ce36f5d..ee9b9b0535b79 100644 --- a/src/plugins/advanced_settings/public/management_app/types.ts +++ b/src/plugins/advanced_settings/public/management_app/types.ts @@ -17,17 +17,12 @@ * under the License. */ -import { - UiSettingsType, - StringValidation, - ImageValidation, - SavedObjectAttribute, -} from '../../../../core/public'; +import { UiSettingsType, StringValidation, ImageValidation } from '../../../../core/public'; export interface FieldSetting { displayName: string; name: string; - value: SavedObjectAttribute; + value: unknown; description?: string; options?: string[]; optionLabels?: Record; @@ -36,7 +31,7 @@ export interface FieldSetting { category: string[]; ariaName: string; isOverridden: boolean; - defVal: SavedObjectAttribute; + defVal: unknown; isCustom: boolean; validation?: StringValidation | ImageValidation; readOnly?: boolean; diff --git a/src/plugins/data/public/plugin.ts b/src/plugins/data/public/plugin.ts index 183ef23e25f7c..a01c133712206 100644 --- a/src/plugins/data/public/plugin.ts +++ b/src/plugins/data/public/plugin.ts @@ -50,10 +50,10 @@ import { import { createSearchBar } from './ui/search_bar/create_search_bar'; import { esaggs } from './search/expressions'; import { - APPLY_FILTER_TRIGGER, SELECT_RANGE_TRIGGER, VALUE_CLICK_TRIGGER, -} from '../../embeddable/public'; + APPLY_FILTER_TRIGGER, +} from '../../ui_actions/public'; import { ACTION_GLOBAL_APPLY_FILTER, createFilterAction, createFiltersFromEvent } from './actions'; import { ApplyGlobalFilterActionContext } from './actions/apply_filter_action'; import { diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 333b13eedc17a..783411bbf27e2 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -38,6 +38,7 @@ import { Observable } from 'rxjs'; import { Plugin as Plugin_2 } from 'src/core/public'; import { PluginInitializerContext as PluginInitializerContext_2 } from 'src/core/public'; import { PopoverAnchorPosition } from '@elastic/eui'; +import { PublicUiSettingsParams } from 'src/core/server/types'; import React from 'react'; import * as React_2 from 'react'; import { Required } from '@kbn/utility-types'; @@ -49,7 +50,6 @@ import { SearchResponse as SearchResponse_2 } from 'elasticsearch'; import { SimpleSavedObject } from 'src/core/public'; import { UiActionsSetup } from 'src/plugins/ui_actions/public'; import { UiActionsStart } from 'src/plugins/ui_actions/public'; -import { UiSettingsParams } from 'src/core/server/types'; import { Unit } from '@elastic/datemath'; import { UnregisterCallback } from 'history'; import { UserProvidedValues } from 'src/core/server/types'; diff --git a/src/plugins/data/public/query/filter_manager/lib/compare_filters.test.ts b/src/plugins/data/public/query/filter_manager/lib/compare_filters.test.ts index 5d6c25b0d96c1..da8f5b3564948 100644 --- a/src/plugins/data/public/query/filter_manager/lib/compare_filters.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/compare_filters.test.ts @@ -48,6 +48,22 @@ describe('filter manager utilities', () => { expect(compareFilters(f1, f2)).toBeTruthy(); }); + test('should compare filters, where one filter is null', () => { + const f1 = buildQueryFilter( + { _type: { match: { query: 'apache', type: 'phrase' } } }, + 'index', + '' + ); + const f2 = null; + expect(compareFilters(f1, f2 as any)).toBeFalsy(); + }); + + test('should compare a null filter with an empty filter', () => { + const f1 = null; + const f2 = buildEmptyFilter(true); + expect(compareFilters(f1 as any, f2)).toBeFalsy(); + }); + test('should compare duplicates, ignoring meta attributes', () => { const f1 = buildQueryFilter( { _type: { match: { query: 'apache', type: 'phrase' } } }, diff --git a/src/plugins/data/public/query/filter_manager/lib/compare_filters.ts b/src/plugins/data/public/query/filter_manager/lib/compare_filters.ts index b4402885bc0be..a2105fdc1d3ef 100644 --- a/src/plugins/data/public/query/filter_manager/lib/compare_filters.ts +++ b/src/plugins/data/public/query/filter_manager/lib/compare_filters.ts @@ -74,6 +74,8 @@ export const compareFilters = ( second: Filter | Filter[], comparatorOptions: FilterCompareOptions = {} ) => { + if (!first || !second) return false; + let comparators: FilterCompareOptions = {}; const excludedAttributes: string[] = ['$$hashKey', 'meta']; diff --git a/src/plugins/data/server/index_patterns/index_patterns_service.ts b/src/plugins/data/server/index_patterns/index_patterns_service.ts index 78f34e21b9e41..58e8fbae9f9e2 100644 --- a/src/plugins/data/server/index_patterns/index_patterns_service.ts +++ b/src/plugins/data/server/index_patterns/index_patterns_service.ts @@ -19,10 +19,13 @@ import { CoreSetup, Plugin } from 'kibana/server'; import { registerRoutes } from './routes'; +import { indexPatternSavedObjectType } from '../saved_objects'; export class IndexPatternsService implements Plugin { - public setup({ http }: CoreSetup) { - registerRoutes(http); + public setup(core: CoreSetup) { + core.savedObjects.registerType(indexPatternSavedObjectType); + + registerRoutes(core.http); } public start() {} diff --git a/src/plugins/data/server/plugin.ts b/src/plugins/data/server/plugin.ts index 616e65ad872ab..efb8759e7bead 100644 --- a/src/plugins/data/server/plugin.ts +++ b/src/plugins/data/server/plugin.ts @@ -21,6 +21,7 @@ import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../.. import { IndexPatternsService } from './index_patterns'; import { ISearchSetup } from './search'; import { SearchService } from './search/search_service'; +import { QueryService } from './query/query_service'; import { ScriptsService } from './scripts'; import { KqlTelemetryService } from './kql_telemetry'; import { UsageCollectionSetup } from '../../usage_collection/server'; @@ -47,6 +48,7 @@ export class DataServerPlugin implements Plugin { + public setup(core: CoreSetup) { + core.savedObjects.registerType(querySavedObjectType); + } + + public start() {} +} diff --git a/src/plugins/data/server/saved_objects/index.ts b/src/plugins/data/server/saved_objects/index.ts new file mode 100644 index 0000000000000..5d980974474de --- /dev/null +++ b/src/plugins/data/server/saved_objects/index.ts @@ -0,0 +1,22 @@ +/* + * 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 { searchSavedObjectType } from './search'; +export { querySavedObjectType } from './query'; +export { indexPatternSavedObjectType } from './index_patterns'; diff --git a/src/plugins/data/server/saved_objects/index_pattern_migrations.test.ts b/src/plugins/data/server/saved_objects/index_pattern_migrations.test.ts new file mode 100644 index 0000000000000..b1410e2498667 --- /dev/null +++ b/src/plugins/data/server/saved_objects/index_pattern_migrations.test.ts @@ -0,0 +1,97 @@ +/* + * 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 { SavedObjectMigrationContext } from 'kibana/server'; +import { indexPatternSavedObjectTypeMigrations } from './index_pattern_migrations'; + +const savedObjectMigrationContext = (null as unknown) as SavedObjectMigrationContext; + +describe('migration index-pattern', () => { + describe('6.5.0', () => { + const migrationFn = indexPatternSavedObjectTypeMigrations['6.5.0']; + + test('adds "type" and "typeMeta" properties to object when not declared', () => { + expect( + migrationFn( + { + type: 'index-pattern', + attributes: {}, + }, + savedObjectMigrationContext + ) + ).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "type": undefined, + "typeMeta": undefined, + }, + "type": "index-pattern", +} +`); + }); + + test('keeps "type" and "typeMeta" properties as is when declared', () => { + expect( + migrationFn( + { + type: 'index-pattern', + attributes: { + type: '123', + typeMeta: '123', + }, + }, + savedObjectMigrationContext + ) + ).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "type": "123", + "typeMeta": "123", + }, + "type": "index-pattern", +} +`); + }); + }); + + describe('7.6.0', () => { + const migrationFn = indexPatternSavedObjectTypeMigrations['7.6.0']; + + test('should remove the parent property and update the subType prop on every field that has them', () => { + const input = { + type: 'index-pattern', + attributes: { + title: 'test', + 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 = { + type: 'index-pattern', + attributes: { + title: 'test', + 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(migrationFn(input, savedObjectMigrationContext)).toEqual(expected); + }); + }); +}); diff --git a/src/plugins/data/server/saved_objects/index_pattern_migrations.ts b/src/plugins/data/server/saved_objects/index_pattern_migrations.ts new file mode 100644 index 0000000000000..7a16386ea484c --- /dev/null +++ b/src/plugins/data/server/saved_objects/index_pattern_migrations.ts @@ -0,0 +1,60 @@ +/* + * 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 { flow, omit } from 'lodash'; +import { SavedObjectMigrationFn } from 'kibana/server'; + +const migrateAttributeTypeAndAttributeTypeMeta: SavedObjectMigrationFn = doc => ({ + ...doc, + attributes: { + ...doc.attributes, + type: doc.attributes.type || undefined, + typeMeta: doc.attributes.typeMeta || undefined, + }, +}); + +const migrateSubTypeAndParentFieldProperties: SavedObjectMigrationFn = doc => { + if (!doc.attributes.fields) return doc; + + const fieldsString = doc.attributes.fields; + const fields = JSON.parse(fieldsString) as any[]; + 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), + }, + }; +}; + +export const indexPatternSavedObjectTypeMigrations = { + '6.5.0': flow(migrateAttributeTypeAndAttributeTypeMeta), + '7.6.0': flow(migrateSubTypeAndParentFieldProperties), +}; diff --git a/src/plugins/data/server/saved_objects/index_patterns.ts b/src/plugins/data/server/saved_objects/index_patterns.ts new file mode 100644 index 0000000000000..9838071eee5a4 --- /dev/null +++ b/src/plugins/data/server/saved_objects/index_patterns.ts @@ -0,0 +1,58 @@ +/* + * 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 { SavedObjectsType } from 'kibana/server'; +import { indexPatternSavedObjectTypeMigrations } from './index_pattern_migrations'; + +export const indexPatternSavedObjectType: SavedObjectsType = { + name: 'index-pattern', + hidden: false, + namespaceAgnostic: false, + management: { + icon: 'indexPatternApp', + defaultSearchField: 'title', + importableAndExportable: true, + getTitle(obj) { + return obj.attributes.title; + }, + getEditUrl(obj) { + return `/management/kibana/index_patterns/${encodeURIComponent(obj.id)}`; + }, + getInAppUrl(obj) { + return { + path: `/app/kibana#/management/kibana/index_patterns/${encodeURIComponent(obj.id)}`, + uiCapabilitiesPath: 'management.kibana.index_patterns', + }; + }, + }, + mappings: { + properties: { + fieldFormatMap: { type: 'text' }, + fields: { type: 'text' }, + intervalName: { type: 'keyword' }, + notExpandable: { type: 'boolean' }, + sourceFilters: { type: 'text' }, + timeFieldName: { type: 'keyword' }, + title: { type: 'text' }, + type: { type: 'keyword' }, + typeMeta: { type: 'keyword' }, + }, + }, + migrations: indexPatternSavedObjectTypeMigrations, +}; diff --git a/src/plugins/data/server/saved_objects/query.ts b/src/plugins/data/server/saved_objects/query.ts new file mode 100644 index 0000000000000..ff0a6cfde8113 --- /dev/null +++ b/src/plugins/data/server/saved_objects/query.ts @@ -0,0 +1,52 @@ +/* + * 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 { SavedObjectsType } from 'kibana/server'; + +export const querySavedObjectType: SavedObjectsType = { + name: 'query', + hidden: false, + namespaceAgnostic: false, + management: { + icon: 'search', + defaultSearchField: 'title', + importableAndExportable: true, + getTitle(obj) { + return obj.attributes.title; + }, + getInAppUrl(obj) { + return { + path: `/app/kibana#/discover?_a=(savedQuery:'${encodeURIComponent(obj.id)}')`, + uiCapabilitiesPath: 'discover.show', + }; + }, + }, + mappings: { + properties: { + title: { type: 'text' }, + description: { type: 'text' }, + query: { + properties: { language: { type: 'keyword' }, query: { type: 'keyword', index: false } }, + }, + filters: { type: 'object', enabled: false }, + timefilter: { type: 'object', enabled: false }, + }, + }, + migrations: {}, +}; diff --git a/src/plugins/data/server/saved_objects/search.ts b/src/plugins/data/server/saved_objects/search.ts new file mode 100644 index 0000000000000..8b30ff7d08201 --- /dev/null +++ b/src/plugins/data/server/saved_objects/search.ts @@ -0,0 +1,60 @@ +/* + * 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 { SavedObjectsType } from 'kibana/server'; +import { searchSavedObjectTypeMigrations } from './search_migrations'; + +export const searchSavedObjectType: SavedObjectsType = { + name: 'search', + hidden: false, + namespaceAgnostic: false, + management: { + icon: 'discoverApp', + defaultSearchField: 'title', + importableAndExportable: true, + getTitle(obj) { + return obj.attributes.title; + }, + getEditUrl(obj) { + return `/management/kibana/objects/savedSearches/${encodeURIComponent(obj.id)}`; + }, + getInAppUrl(obj) { + return { + path: `/app/kibana#/discover/${encodeURIComponent(obj.id)}`, + uiCapabilitiesPath: 'discover.show', + }; + }, + }, + mappings: { + properties: { + columns: { type: 'keyword' }, + description: { type: 'text' }, + hits: { type: 'integer' }, + kibanaSavedObjectMeta: { + properties: { + searchSourceJSON: { type: 'text' }, + }, + }, + sort: { type: 'keyword' }, + title: { type: 'text' }, + version: { type: 'integer' }, + }, + }, + migrations: searchSavedObjectTypeMigrations, +}; diff --git a/src/plugins/data/server/saved_objects/search_migrations.test.ts b/src/plugins/data/server/saved_objects/search_migrations.test.ts new file mode 100644 index 0000000000000..7fdf2e14aefed --- /dev/null +++ b/src/plugins/data/server/saved_objects/search_migrations.test.ts @@ -0,0 +1,299 @@ +/* + * 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 { SavedObjectMigrationContext } from 'kibana/server'; +import { searchSavedObjectTypeMigrations } from './search_migrations'; + +const savedObjectMigrationContext = (null as unknown) as SavedObjectMigrationContext; + +describe('migration search', () => { + describe('7.0.0', () => { + const migrationFn = searchSavedObjectTypeMigrations['7.0.0']; + + test('skips errors when searchSourceJSON is null', () => { + const doc = { + id: '123', + type: 'search', + attributes: { + foo: true, + kibanaSavedObjectMeta: { + searchSourceJSON: null, + }, + }, + }; + const migratedDoc = migrationFn(doc, savedObjectMigrationContext); + + expect(migratedDoc).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "foo": true, + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": null, + }, + }, + "id": "123", + "references": Array [], + "type": "search", +} +`); + }); + + test('skips errors when searchSourceJSON is undefined', () => { + const doc = { + id: '123', + type: 'search', + attributes: { + foo: true, + kibanaSavedObjectMeta: { + searchSourceJSON: undefined, + }, + }, + }; + const migratedDoc = migrationFn(doc, savedObjectMigrationContext); + + expect(migratedDoc).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "foo": true, + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": undefined, + }, + }, + "id": "123", + "references": Array [], + "type": "search", +} +`); + }); + + test('skips error when searchSourceJSON is not a string', () => { + const doc = { + id: '123', + type: 'search', + attributes: { + foo: true, + kibanaSavedObjectMeta: { + searchSourceJSON: 123, + }, + }, + }; + const migratedDoc = migrationFn(doc, savedObjectMigrationContext); + + expect(migratedDoc).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "foo": true, + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": 123, + }, + }, + "id": "123", + "references": Array [], + "type": "search", +} +`); + }); + + test('skips error when searchSourceJSON is invalid json', () => { + const doc = { + id: '123', + type: 'search', + attributes: { + foo: true, + kibanaSavedObjectMeta: { + searchSourceJSON: '{abc123}', + }, + }, + }; + const migratedDoc = migrationFn(doc, savedObjectMigrationContext); + + expect(migratedDoc).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "foo": true, + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": "{abc123}", + }, + }, + "id": "123", + "references": Array [], + "type": "search", +} +`); + }); + + test('skips error when "index" and "filter" is missing from searchSourceJSON', () => { + const doc = { + id: '123', + type: 'search', + attributes: { + foo: true, + kibanaSavedObjectMeta: { + searchSourceJSON: JSON.stringify({ bar: true }), + }, + }, + }; + const migratedDoc = migrationFn(doc, savedObjectMigrationContext); + + expect(migratedDoc).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "foo": true, + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": "{\\"bar\\":true}", + }, + }, + "id": "123", + "references": Array [], + "type": "search", +} +`); + }); + + test('extracts "index" attribute from doc', () => { + const doc = { + id: '123', + type: 'search', + attributes: { + foo: true, + kibanaSavedObjectMeta: { + searchSourceJSON: JSON.stringify({ bar: true, index: 'pattern*' }), + }, + }, + }; + const migratedDoc = migrationFn(doc, savedObjectMigrationContext); + + expect(migratedDoc).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "foo": true, + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": "{\\"bar\\":true,\\"indexRefName\\":\\"kibanaSavedObjectMeta.searchSourceJSON.index\\"}", + }, + }, + "id": "123", + "references": Array [ + Object { + "id": "pattern*", + "name": "kibanaSavedObjectMeta.searchSourceJSON.index", + "type": "index-pattern", + }, + ], + "type": "search", +} +`); + }); + + test('extracts index patterns from filter', () => { + const doc = { + id: '123', + type: 'search', + attributes: { + foo: true, + kibanaSavedObjectMeta: { + searchSourceJSON: JSON.stringify({ + bar: true, + filter: [ + { + meta: { + foo: true, + index: 'my-index', + }, + }, + ], + }), + }, + }, + }; + const migratedDoc = migrationFn(doc, savedObjectMigrationContext); + + expect(migratedDoc).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "foo": true, + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": "{\\"bar\\":true,\\"filter\\":[{\\"meta\\":{\\"foo\\":true,\\"indexRefName\\":\\"kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index\\"}}]}", + }, + }, + "id": "123", + "references": Array [ + Object { + "id": "my-index", + "name": "kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index", + "type": "index-pattern", + }, + ], + "type": "search", +} +`); + }); + }); + + describe('7.4.0', function() { + const migrationFn = searchSavedObjectTypeMigrations['7.4.0']; + + test('transforms one dimensional sort arrays into two dimensional arrays', () => { + const doc = { + id: '123', + type: 'search', + attributes: { + sort: ['bytes', 'desc'], + }, + }; + + const expected = { + id: '123', + type: 'search', + attributes: { + sort: [['bytes', 'desc']], + }, + }; + + const migratedDoc = migrationFn(doc, savedObjectMigrationContext); + + expect(migratedDoc).toEqual(expected); + }); + + test("doesn't modify search docs that already have two dimensional sort arrays", () => { + const doc = { + id: '123', + type: 'search', + attributes: { + sort: [['bytes', 'desc']], + }, + }; + + const migratedDoc = migrationFn(doc, savedObjectMigrationContext); + + expect(migratedDoc).toEqual(doc); + }); + + test("doesn't modify search docs that have no sort array", () => { + const doc = { + id: '123', + type: 'search', + attributes: {}, + }; + + const migratedDoc = migrationFn(doc, savedObjectMigrationContext); + + expect(migratedDoc).toEqual(doc); + }); + }); +}); diff --git a/src/plugins/data/server/saved_objects/search_migrations.ts b/src/plugins/data/server/saved_objects/search_migrations.ts new file mode 100644 index 0000000000000..db545e52ce170 --- /dev/null +++ b/src/plugins/data/server/saved_objects/search_migrations.ts @@ -0,0 +1,92 @@ +/* + * 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 { flow, get } from 'lodash'; +import { SavedObjectMigrationFn } from 'kibana/server'; + +const migrateIndexPattern: SavedObjectMigrationFn = doc => { + const searchSourceJSON = get(doc, 'attributes.kibanaSavedObjectMeta.searchSourceJSON'); + if (typeof searchSourceJSON !== 'string') { + return doc; + } + let searchSource; + try { + searchSource = JSON.parse(searchSourceJSON); + } catch (e) { + // Let it go, the data is invalid and we'll leave it as is + return doc; + } + + if (searchSource.index && Array.isArray(doc.references)) { + searchSource.indexRefName = 'kibanaSavedObjectMeta.searchSourceJSON.index'; + doc.references.push({ + name: searchSource.indexRefName, + type: 'index-pattern', + id: searchSource.index, + }); + delete searchSource.index; + } + if (searchSource.filter) { + searchSource.filter.forEach((filterRow: any, i: number) => { + if (!filterRow.meta || !filterRow.meta.index || !Array.isArray(doc.references)) { + return; + } + filterRow.meta.indexRefName = `kibanaSavedObjectMeta.searchSourceJSON.filter[${i}].meta.index`; + doc.references.push({ + name: filterRow.meta.indexRefName, + type: 'index-pattern', + id: filterRow.meta.index, + }); + delete filterRow.meta.index; + }); + } + + doc.attributes.kibanaSavedObjectMeta.searchSourceJSON = JSON.stringify(searchSource); + + return doc; +}; + +const setNewReferences: SavedObjectMigrationFn = (doc, context) => { + doc.references = doc.references || []; + // Migrate index pattern + return migrateIndexPattern(doc, context); +}; + +const migrateSearchSortToNestedArray: SavedObjectMigrationFn = doc => { + const sort = get(doc, 'attributes.sort'); + if (!sort) return doc; + + // Don't do anything if we already have a two dimensional array + if (Array.isArray(sort) && sort.length > 0 && Array.isArray(sort[0])) { + return doc; + } + + return { + ...doc, + attributes: { + ...doc.attributes, + sort: [doc.attributes.sort], + }, + }; +}; + +export const searchSavedObjectTypeMigrations = { + '7.0.0': flow(setNewReferences), + '7.4.0': flow(migrateSearchSortToNestedArray), +}; diff --git a/src/plugins/data/server/search/search_service.ts b/src/plugins/data/server/search/search_service.ts index 46f90e3c6fc62..5ee19cd3df19f 100644 --- a/src/plugins/data/server/search/search_service.ts +++ b/src/plugins/data/server/search/search_service.ts @@ -34,6 +34,8 @@ import { import { IRouteHandlerSearchContext } from './i_route_handler_search_context'; import { ES_SEARCH_STRATEGY, esSearchStrategyProvider } from './es_search'; +import { searchSavedObjectType } from '../saved_objects'; + declare module 'kibana/server' { interface RequestHandlerContext { search?: IRouteHandlerSearchContext; @@ -53,6 +55,8 @@ export class SearchService implements Plugin { this.contextContainer = core.context.createContextContainer(); + core.savedObjects.registerType(searchSavedObjectType); + core.http.registerRouteHandlerContext<'search'>('search', context => { return createApi({ caller: context.core.elasticsearch.dataClient.callAsCurrentUser, diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index 666df2900c2c3..2a2d9bb414c14 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -733,7 +733,7 @@ export type TSearchStrategyProvider = (context: ISearc // src/plugins/data/server/index.ts:184:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:185:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:188:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/plugin.ts:62:14 - (ae-forgotten-export) The symbol "ISearchSetup" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/plugin.ts:64:14 - (ae-forgotten-export) The symbol "ISearchSetup" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/src/plugins/embeddable/public/bootstrap.ts b/src/plugins/embeddable/public/bootstrap.ts index e69361178eeba..c8c4f0b95c458 100644 --- a/src/plugins/embeddable/public/bootstrap.ts +++ b/src/plugins/embeddable/public/bootstrap.ts @@ -17,20 +17,11 @@ * under the License. */ import { UiActionsSetup } from '../../ui_actions/public'; -import { Filter } from '../../data/public'; import { - applyFilterTrigger, contextMenuTrigger, createFilterAction, panelBadgeTrigger, - selectRangeTrigger, - valueClickTrigger, - EmbeddableVisTriggerContext, - IEmbeddable, EmbeddableContext, - APPLY_FILTER_TRIGGER, - VALUE_CLICK_TRIGGER, - SELECT_RANGE_TRIGGER, CONTEXT_MENU_TRIGGER, PANEL_BADGE_TRIGGER, ACTION_ADD_PANEL, @@ -44,12 +35,6 @@ import { declare module '../../ui_actions/public' { export interface TriggerContextMapping { - [SELECT_RANGE_TRIGGER]: EmbeddableVisTriggerContext; - [VALUE_CLICK_TRIGGER]: EmbeddableVisTriggerContext; - [APPLY_FILTER_TRIGGER]: { - embeddable: IEmbeddable; - filters: Filter[]; - }; [CONTEXT_MENU_TRIGGER]: EmbeddableContext; [PANEL_BADGE_TRIGGER]: EmbeddableContext; } @@ -70,10 +55,7 @@ declare module '../../ui_actions/public' { */ export const bootstrap = (uiActions: UiActionsSetup) => { uiActions.registerTrigger(contextMenuTrigger); - uiActions.registerTrigger(applyFilterTrigger); uiActions.registerTrigger(panelBadgeTrigger); - uiActions.registerTrigger(selectRangeTrigger); - uiActions.registerTrigger(valueClickTrigger); const actionApplyFilter = createFilterAction(); diff --git a/src/plugins/embeddable/public/index.ts b/src/plugins/embeddable/public/index.ts index 0b5fd8184deb1..1474f9ed63052 100644 --- a/src/plugins/embeddable/public/index.ts +++ b/src/plugins/embeddable/public/index.ts @@ -27,8 +27,6 @@ export { ACTION_ADD_PANEL, AddPanelAction, ACTION_APPLY_FILTER, - APPLY_FILTER_TRIGGER, - applyFilterTrigger, Container, ContainerInput, ContainerOutput, @@ -62,10 +60,6 @@ export { PanelNotFoundError, PanelState, PropertySpec, - SELECT_RANGE_TRIGGER, - selectRangeTrigger, - VALUE_CLICK_TRIGGER, - valueClickTrigger, ViewMode, withEmbeddableSubscription, } from './lib'; diff --git a/src/plugins/embeddable/public/lib/triggers/triggers.ts b/src/plugins/embeddable/public/lib/triggers/triggers.ts index a348e1ed79d8d..0052403816eb8 100644 --- a/src/plugins/embeddable/public/lib/triggers/triggers.ts +++ b/src/plugins/embeddable/public/lib/triggers/triggers.ts @@ -33,20 +33,6 @@ export interface EmbeddableVisTriggerContext { }; } -export const SELECT_RANGE_TRIGGER = 'SELECT_RANGE_TRIGGER'; -export const selectRangeTrigger: Trigger<'SELECT_RANGE_TRIGGER'> = { - id: SELECT_RANGE_TRIGGER, - title: 'Select range', - description: 'Applies a range filter', -}; - -export const VALUE_CLICK_TRIGGER = 'VALUE_CLICK_TRIGGER'; -export const valueClickTrigger: Trigger<'VALUE_CLICK_TRIGGER'> = { - id: VALUE_CLICK_TRIGGER, - title: 'Value clicked', - description: 'Value was clicked', -}; - export const CONTEXT_MENU_TRIGGER = 'CONTEXT_MENU_TRIGGER'; export const contextMenuTrigger: Trigger<'CONTEXT_MENU_TRIGGER'> = { id: CONTEXT_MENU_TRIGGER, @@ -54,13 +40,6 @@ export const contextMenuTrigger: Trigger<'CONTEXT_MENU_TRIGGER'> = { description: 'Triggered on top-right corner context-menu select.', }; -export const APPLY_FILTER_TRIGGER = 'FILTER_TRIGGER'; -export const applyFilterTrigger: Trigger<'FILTER_TRIGGER'> = { - id: APPLY_FILTER_TRIGGER, - title: 'Filter click', - description: 'Triggered when user applies filter to an embeddable.', -}; - export const PANEL_BADGE_TRIGGER = 'PANEL_BADGE_TRIGGER'; export const panelBadgeTrigger: Trigger<'PANEL_BADGE_TRIGGER'> = { id: PANEL_BADGE_TRIGGER, diff --git a/src/plugins/ui_actions/public/index.ts b/src/plugins/ui_actions/public/index.ts index 79b8e1474f6c2..49b6bd5e17699 100644 --- a/src/plugins/ui_actions/public/index.ts +++ b/src/plugins/ui_actions/public/index.ts @@ -28,6 +28,15 @@ export { UiActionsSetup, UiActionsStart } from './plugin'; export { UiActionsServiceParams, UiActionsService } from './service'; export { Action, createAction, IncompatibleActionError } from './actions'; export { buildContextMenuForActions } from './context_menu'; -export { Trigger, TriggerContext } from './triggers'; +export { + Trigger, + TriggerContext, + SELECT_RANGE_TRIGGER, + selectRangeTrigger, + VALUE_CLICK_TRIGGER, + valueClickTrigger, + APPLY_FILTER_TRIGGER, + applyFilterTrigger, +} from './triggers'; export { TriggerContextMapping, TriggerId, ActionContextMapping, ActionType } from './types'; export { ActionByType } from './actions'; diff --git a/src/plugins/ui_actions/public/plugin.ts b/src/plugins/ui_actions/public/plugin.ts index 0874803db7d37..928e57937a9b5 100644 --- a/src/plugins/ui_actions/public/plugin.ts +++ b/src/plugins/ui_actions/public/plugin.ts @@ -19,6 +19,7 @@ import { CoreStart, CoreSetup, Plugin, PluginInitializerContext } from 'src/core/public'; import { UiActionsService } from './service'; +import { selectRangeTrigger, valueClickTrigger, applyFilterTrigger } from './triggers'; export type UiActionsSetup = Pick< UiActionsService, @@ -33,6 +34,9 @@ export class UiActionsPlugin implements Plugin { constructor(initializerContext: PluginInitializerContext) {} public setup(core: CoreSetup): UiActionsSetup { + this.service.registerTrigger(selectRangeTrigger); + this.service.registerTrigger(valueClickTrigger); + this.service.registerTrigger(applyFilterTrigger); return this.service; } diff --git a/src/legacy/core_plugins/data/mappings.ts b/src/plugins/ui_actions/public/triggers/apply_filter_trigger.ts similarity index 60% rename from src/legacy/core_plugins/data/mappings.ts rename to src/plugins/ui_actions/public/triggers/apply_filter_trigger.ts index 90777ec8e3651..7a95709ac28ba 100644 --- a/src/legacy/core_plugins/data/mappings.ts +++ b/src/plugins/ui_actions/public/triggers/apply_filter_trigger.ts @@ -17,34 +17,11 @@ * under the License. */ -export const mappings = { - query: { - properties: { - title: { - type: 'text', - }, - description: { - type: 'text', - }, - query: { - properties: { - language: { - type: 'keyword', - }, - query: { - type: 'keyword', - index: false, - }, - }, - }, - filters: { - type: 'object', - enabled: false, - }, - timefilter: { - type: 'object', - enabled: false, - }, - }, - }, +import { Trigger } from '.'; + +export const APPLY_FILTER_TRIGGER = 'FILTER_TRIGGER'; +export const applyFilterTrigger: Trigger<'FILTER_TRIGGER'> = { + id: APPLY_FILTER_TRIGGER, + title: 'Filter click', + description: 'Triggered when user applies filter to an embeddable.', }; diff --git a/src/plugins/ui_actions/public/triggers/index.ts b/src/plugins/ui_actions/public/triggers/index.ts index 1ae2a19c4001f..a5bf9e1822941 100644 --- a/src/plugins/ui_actions/public/triggers/index.ts +++ b/src/plugins/ui_actions/public/triggers/index.ts @@ -20,3 +20,6 @@ export * from './trigger'; export * from './trigger_contract'; export * from './trigger_internal'; +export * from './select_range_trigger'; +export * from './value_click_trigger'; +export * from './apply_filter_trigger'; diff --git a/src/plugins/ui_actions/public/triggers/select_range_trigger.ts b/src/plugins/ui_actions/public/triggers/select_range_trigger.ts new file mode 100644 index 0000000000000..c638db0ce9dab --- /dev/null +++ b/src/plugins/ui_actions/public/triggers/select_range_trigger.ts @@ -0,0 +1,27 @@ +/* + * 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 { Trigger } from '.'; + +export const SELECT_RANGE_TRIGGER = 'SELECT_RANGE_TRIGGER'; +export const selectRangeTrigger: Trigger<'SELECT_RANGE_TRIGGER'> = { + id: SELECT_RANGE_TRIGGER, + title: 'Select range', + description: 'Applies a range filter', +}; diff --git a/src/plugins/ui_actions/public/triggers/value_click_trigger.ts b/src/plugins/ui_actions/public/triggers/value_click_trigger.ts new file mode 100644 index 0000000000000..ad32bdc1b564e --- /dev/null +++ b/src/plugins/ui_actions/public/triggers/value_click_trigger.ts @@ -0,0 +1,27 @@ +/* + * 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 { Trigger } from '.'; + +export const VALUE_CLICK_TRIGGER = 'VALUE_CLICK_TRIGGER'; +export const valueClickTrigger: Trigger<'VALUE_CLICK_TRIGGER'> = { + id: VALUE_CLICK_TRIGGER, + title: 'Value clicked', + description: 'Value was clicked', +}; diff --git a/src/plugins/ui_actions/public/types.ts b/src/plugins/ui_actions/public/types.ts index d443ce0e592cb..c7e6d61e15f31 100644 --- a/src/plugins/ui_actions/public/types.ts +++ b/src/plugins/ui_actions/public/types.ts @@ -19,6 +19,9 @@ import { ActionByType } from './actions/action'; import { TriggerInternal } from './triggers/trigger_internal'; +import { EmbeddableVisTriggerContext, IEmbeddable } from '../../embeddable/public'; +import { Filter } from '../../data/public'; +import { SELECT_RANGE_TRIGGER, VALUE_CLICK_TRIGGER, APPLY_FILTER_TRIGGER } from './triggers'; export type TriggerRegistry = Map>; export type ActionRegistry = Map>; @@ -33,6 +36,12 @@ export type TriggerContext = BaseContext; export interface TriggerContextMapping { [DEFAULT_TRIGGER]: TriggerContext; + [SELECT_RANGE_TRIGGER]: EmbeddableVisTriggerContext; + [VALUE_CLICK_TRIGGER]: EmbeddableVisTriggerContext; + [APPLY_FILTER_TRIGGER]: { + embeddable: IEmbeddable; + filters: Filter[]; + }; } const DEFAULT_ACTION = ''; diff --git a/src/plugins/visualizations/kibana.json b/src/plugins/visualizations/kibana.json index cf79ce17293d6..8e63ea7833327 100644 --- a/src/plugins/visualizations/kibana.json +++ b/src/plugins/visualizations/kibana.json @@ -1,7 +1,7 @@ { "id": "visualizations", "version": "kibana", - "server": false, + "server": true, "ui": true, "requiredPlugins": [ "expressions" diff --git a/src/plugins/visualizations/server/index.ts b/src/plugins/visualizations/server/index.ts new file mode 100644 index 0000000000000..80c10c3945d4c --- /dev/null +++ b/src/plugins/visualizations/server/index.ts @@ -0,0 +1,30 @@ +/* + * 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 } from '../../../core/server'; +import { VisualizationsPlugin } from './plugin'; + +// This exports static code and TypeScript types, +// as well as, Kibana Platform `plugin()` initializer. + +export function plugin(initializerContext: PluginInitializerContext) { + return new VisualizationsPlugin(initializerContext); +} + +export { VisualizationsPluginSetup, VisualizationsPluginStart } from './types'; diff --git a/src/plugins/visualizations/server/plugin.ts b/src/plugins/visualizations/server/plugin.ts new file mode 100644 index 0000000000000..79cce6b5867a1 --- /dev/null +++ b/src/plugins/visualizations/server/plugin.ts @@ -0,0 +1,54 @@ +/* + * 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, + Logger, +} from '../../../core/server'; + +import { visualizationSavedObjectType } from './saved_objects'; + +import { VisualizationsPluginSetup, VisualizationsPluginStart } from './types'; + +export class VisualizationsPlugin + implements Plugin { + private readonly logger: Logger; + + constructor(initializerContext: PluginInitializerContext) { + this.logger = initializerContext.logger.get(); + } + + public setup(core: CoreSetup) { + this.logger.debug('visualizations: Setup'); + + core.savedObjects.registerType(visualizationSavedObjectType); + + return {}; + } + + public start(core: CoreStart) { + this.logger.debug('visualizations: Started'); + return {}; + } + + public stop() {} +} diff --git a/src/plugins/visualizations/server/saved_objects/index.ts b/src/plugins/visualizations/server/saved_objects/index.ts new file mode 100644 index 0000000000000..be75f63582450 --- /dev/null +++ b/src/plugins/visualizations/server/saved_objects/index.ts @@ -0,0 +1,20 @@ +/* + * 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 { visualizationSavedObjectType } from './visualization'; diff --git a/src/plugins/visualizations/server/saved_objects/visualization.ts b/src/plugins/visualizations/server/saved_objects/visualization.ts new file mode 100644 index 0000000000000..9f4782f3ec730 --- /dev/null +++ b/src/plugins/visualizations/server/saved_objects/visualization.ts @@ -0,0 +1,56 @@ +/* + * 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 { SavedObjectsType } from 'kibana/server'; +import { visualizationSavedObjectTypeMigrations } from './visualization_migrations'; + +export const visualizationSavedObjectType: SavedObjectsType = { + name: 'visualization', + hidden: false, + namespaceAgnostic: false, + management: { + icon: 'visualizeApp', + defaultSearchField: 'title', + importableAndExportable: true, + getTitle(obj) { + return obj.attributes.title; + }, + getEditUrl(obj) { + return `/management/kibana/objects/savedVisualizations/${encodeURIComponent(obj.id)}`; + }, + getInAppUrl(obj) { + return { + path: `/app/kibana#/visualize/edit/${encodeURIComponent(obj.id)}`, + uiCapabilitiesPath: 'visualize.show', + }; + }, + }, + mappings: { + properties: { + description: { type: 'text' }, + kibanaSavedObjectMeta: { properties: { searchSourceJSON: { type: 'text' } } }, + savedSearchRefName: { type: 'keyword' }, + title: { type: 'text' }, + uiStateJSON: { type: 'text' }, + version: { type: 'integer' }, + visState: { type: 'text' }, + }, + }, + migrations: visualizationSavedObjectTypeMigrations, +}; diff --git a/src/plugins/visualizations/server/saved_objects/visualization_migrations.test.ts b/src/plugins/visualizations/server/saved_objects/visualization_migrations.test.ts new file mode 100644 index 0000000000000..02c114bad4e72 --- /dev/null +++ b/src/plugins/visualizations/server/saved_objects/visualization_migrations.test.ts @@ -0,0 +1,1356 @@ +/* + * 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 { visualizationSavedObjectTypeMigrations } from './visualization_migrations'; +import { SavedObjectMigrationContext, SavedObjectMigrationFn } from 'kibana/server'; + +const savedObjectMigrationContext = (null as unknown) as SavedObjectMigrationContext; + +describe('migration visualization', () => { + describe('6.7.2', () => { + const migrate = (doc: any) => + visualizationSavedObjectTypeMigrations['6.7.2']( + doc as Parameters[0], + savedObjectMigrationContext + ); + let doc: any; + + describe('date histogram time zone removal', () => { + beforeEach(() => { + doc = { + attributes: { + visState: JSON.stringify({ + aggs: [ + { + enabled: true, + id: '1', + params: { + // Doesn't make much sense but we want to test it's not removing it from anything else + time_zone: 'Europe/Berlin', + }, + schema: 'metric', + type: 'count', + }, + { + enabled: true, + id: '2', + params: { + customInterval: '2h', + drop_partials: false, + extended_bounds: {}, + field: 'timestamp', + time_zone: 'Europe/Berlin', + interval: 'auto', + min_doc_count: 1, + useNormalizedEsInterval: true, + }, + schema: 'segment', + type: 'date_histogram', + }, + { + enabled: true, + id: '4', + params: { + customInterval: '2h', + drop_partials: false, + extended_bounds: {}, + field: 'timestamp', + interval: 'auto', + min_doc_count: 1, + useNormalizedEsInterval: true, + }, + schema: 'segment', + type: 'date_histogram', + }, + { + enabled: true, + id: '3', + params: { + customBucket: { + enabled: true, + id: '1-bucket', + params: { + customInterval: '2h', + drop_partials: false, + extended_bounds: {}, + field: 'timestamp', + interval: 'auto', + min_doc_count: 1, + time_zone: 'Europe/Berlin', + useNormalizedEsInterval: true, + }, + type: 'date_histogram', + }, + customMetric: { + enabled: true, + id: '1-metric', + params: {}, + type: 'count', + }, + }, + schema: 'metric', + type: 'max_bucket', + }, + ], + }), + }, + } as Parameters[0]; + }); + + it('should remove time_zone from date_histogram aggregations', () => { + const migratedDoc = migrate(doc); + const aggs = JSON.parse(migratedDoc.attributes.visState).aggs; + + expect(aggs[1]).not.toHaveProperty('params.time_zone'); + }); + + it('should not remove time_zone from non date_histogram aggregations', () => { + const migratedDoc = migrate(doc); + const aggs = JSON.parse(migratedDoc.attributes.visState).aggs; + + expect(aggs[0]).toHaveProperty('params.time_zone'); + }); + + it('should remove time_zone from nested aggregations', () => { + const migratedDoc = migrate(doc); + const aggs = JSON.parse(migratedDoc.attributes.visState).aggs; + + expect(aggs[3]).not.toHaveProperty('params.customBucket.params.time_zone'); + }); + + it('should not fail on date histograms without a time_zone', () => { + const migratedDoc = migrate(doc); + const aggs = JSON.parse(migratedDoc.attributes.visState).aggs; + + expect(aggs[2]).not.toHaveProperty('params.time_zone'); + }); + + it('should be able to apply the migration twice, since we need it for 6.7.2 and 7.0.1', () => { + const migratedDoc = migrate(doc); + const aggs = JSON.parse(migratedDoc.attributes.visState).aggs; + + expect(aggs[1]).not.toHaveProperty('params.time_zone'); + expect(aggs[0]).toHaveProperty('params.time_zone'); + expect(aggs[3]).not.toHaveProperty('params.customBucket.params.time_zone'); + expect(aggs[2]).not.toHaveProperty('params.time_zone'); + }); + }); + }); + + describe('7.0.0', () => { + const migrate = (doc: any) => + visualizationSavedObjectTypeMigrations['7.0.0']( + doc as Parameters[0], + savedObjectMigrationContext + ); + + const generateDoc = (type: any, aggs: any) => ({ + attributes: { + title: 'My Vis', + description: 'This is my super cool vis.', + visState: JSON.stringify({ type, aggs }), + uiStateJSON: '{}', + version: 1, + kibanaSavedObjectMeta: { + searchSourceJSON: '{}', + }, + }, + references: [], + }); + + it('does not throw error on empty object', () => { + const migratedDoc = migrate({ + attributes: { + visState: '{}', + }, + }); + expect(migratedDoc).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "visState": "{}", + }, + "references": Array [], +} +`); + }); + + it('skips errors when searchSourceJSON is null', () => { + const doc = { + id: '1', + type: 'visualization', + attributes: { + visState: '{}', + kibanaSavedObjectMeta: { + searchSourceJSON: null, + }, + savedSearchId: '123', + }, + }; + const migratedDoc = migrate(doc); + + expect(migratedDoc).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": null, + }, + "savedSearchRefName": "search_0", + "visState": "{}", + }, + "id": "1", + "references": Array [ + Object { + "id": "123", + "name": "search_0", + "type": "search", + }, + ], + "type": "visualization", +} +`); + }); + + it('skips errors when searchSourceJSON is undefined', () => { + const doc = { + id: '1', + type: 'visualization', + attributes: { + visState: '{}', + kibanaSavedObjectMeta: { + searchSourceJSON: undefined, + }, + savedSearchId: '123', + }, + }; + const migratedDoc = migrate(doc); + + expect(migratedDoc).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": undefined, + }, + "savedSearchRefName": "search_0", + "visState": "{}", + }, + "id": "1", + "references": Array [ + Object { + "id": "123", + "name": "search_0", + "type": "search", + }, + ], + "type": "visualization", +} +`); + }); + + it('skips error when searchSourceJSON is not a string', () => { + const doc = { + id: '1', + type: 'visualization', + attributes: { + visState: '{}', + kibanaSavedObjectMeta: { + searchSourceJSON: 123, + }, + savedSearchId: '123', + }, + }; + + expect(migrate(doc)).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": 123, + }, + "savedSearchRefName": "search_0", + "visState": "{}", + }, + "id": "1", + "references": Array [ + Object { + "id": "123", + "name": "search_0", + "type": "search", + }, + ], + "type": "visualization", +} +`); + }); + + it('skips error when searchSourceJSON is invalid json', () => { + const doc = { + id: '1', + type: 'visualization', + attributes: { + visState: '{}', + kibanaSavedObjectMeta: { + searchSourceJSON: '{abc123}', + }, + savedSearchId: '123', + }, + }; + + expect(migrate(doc)).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": "{abc123}", + }, + "savedSearchRefName": "search_0", + "visState": "{}", + }, + "id": "1", + "references": Array [ + Object { + "id": "123", + "name": "search_0", + "type": "search", + }, + ], + "type": "visualization", +} +`); + }); + + it('skips error when "index" and "filter" is missing from searchSourceJSON', () => { + const doc = { + id: '1', + type: 'visualization', + attributes: { + visState: '{}', + kibanaSavedObjectMeta: { + searchSourceJSON: JSON.stringify({ bar: true }), + }, + savedSearchId: '123', + }, + }; + const migratedDoc = migrate(doc); + + expect(migratedDoc).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": "{\\"bar\\":true}", + }, + "savedSearchRefName": "search_0", + "visState": "{}", + }, + "id": "1", + "references": Array [ + Object { + "id": "123", + "name": "search_0", + "type": "search", + }, + ], + "type": "visualization", +} +`); + }); + + it('extracts "index" attribute from doc', () => { + const doc = { + id: '1', + type: 'visualization', + attributes: { + visState: '{}', + kibanaSavedObjectMeta: { + searchSourceJSON: JSON.stringify({ bar: true, index: 'pattern*' }), + }, + savedSearchId: '123', + }, + }; + const migratedDoc = migrate(doc); + + expect(migratedDoc).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": "{\\"bar\\":true,\\"indexRefName\\":\\"kibanaSavedObjectMeta.searchSourceJSON.index\\"}", + }, + "savedSearchRefName": "search_0", + "visState": "{}", + }, + "id": "1", + "references": Array [ + Object { + "id": "pattern*", + "name": "kibanaSavedObjectMeta.searchSourceJSON.index", + "type": "index-pattern", + }, + Object { + "id": "123", + "name": "search_0", + "type": "search", + }, + ], + "type": "visualization", +} +`); + }); + + it('extracts index patterns from the filter', () => { + const doc = { + id: '1', + type: 'visualization', + attributes: { + visState: '{}', + kibanaSavedObjectMeta: { + searchSourceJSON: JSON.stringify({ + bar: true, + filter: [ + { + meta: { index: 'my-index', foo: true }, + }, + ], + }), + }, + savedSearchId: '123', + }, + }; + const migratedDoc = migrate(doc); + + expect(migratedDoc).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": "{\\"bar\\":true,\\"filter\\":[{\\"meta\\":{\\"foo\\":true,\\"indexRefName\\":\\"kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index\\"}}]}", + }, + "savedSearchRefName": "search_0", + "visState": "{}", + }, + "id": "1", + "references": Array [ + Object { + "id": "my-index", + "name": "kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index", + "type": "index-pattern", + }, + Object { + "id": "123", + "name": "search_0", + "type": "search", + }, + ], + "type": "visualization", +} +`); + }); + + it('extracts index patterns from controls', () => { + const doc = { + id: '1', + type: 'visualization', + attributes: { + foo: true, + visState: JSON.stringify({ + bar: false, + params: { + controls: [ + { + bar: true, + indexPattern: 'pattern*', + }, + { + foo: true, + }, + ], + }, + }), + }, + }; + const migratedDoc = migrate(doc); + + expect(migratedDoc).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "foo": true, + "visState": "{\\"bar\\":false,\\"params\\":{\\"controls\\":[{\\"bar\\":true,\\"indexPatternRefName\\":\\"control_0_index_pattern\\"},{\\"foo\\":true}]}}", + }, + "id": "1", + "references": Array [ + Object { + "id": "pattern*", + "name": "control_0_index_pattern", + "type": "index-pattern", + }, + ], + "type": "visualization", +} +`); + }); + + it('skips extracting savedSearchId when missing', () => { + const doc = { + id: '1', + attributes: { + visState: '{}', + kibanaSavedObjectMeta: { + searchSourceJSON: '{}', + }, + }, + }; + const migratedDoc = migrate(doc); + + expect(migratedDoc).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": "{}", + }, + "visState": "{}", + }, + "id": "1", + "references": Array [], +} +`); + }); + + it('extract savedSearchId from doc', () => { + const doc = { + id: '1', + attributes: { + visState: '{}', + kibanaSavedObjectMeta: { + searchSourceJSON: '{}', + }, + savedSearchId: '123', + }, + }; + const migratedDoc = migrate(doc); + + expect(migratedDoc).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": "{}", + }, + "savedSearchRefName": "search_0", + "visState": "{}", + }, + "id": "1", + "references": Array [ + Object { + "id": "123", + "name": "search_0", + "type": "search", + }, + ], +} +`); + }); + + it('delete savedSearchId when empty string in doc', () => { + const doc = { + id: '1', + attributes: { + visState: '{}', + kibanaSavedObjectMeta: { + searchSourceJSON: '{}', + }, + savedSearchId: '', + }, + }; + const migratedDoc = migrate(doc); + + expect(migratedDoc).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": "{}", + }, + "visState": "{}", + }, + "id": "1", + "references": Array [], +} +`); + }); + + it('should return a new object if vis is table and has multiple split aggs', () => { + const aggs = [ + { + id: '1', + schema: 'metric', + params: {}, + }, + { + id: '2', + schema: 'split', + params: { foo: 'bar', row: true }, + }, + { + id: '3', + schema: 'split', + params: { hey: 'ya', row: false }, + }, + ]; + const tableDoc = generateDoc('table', aggs); + const expected = tableDoc; + const actual = migrate(tableDoc); + + expect(actual).not.toEqual(expected); + }); + + it('should not touch any vis that is not table', () => { + const pieDoc = generateDoc('pie', []); + const expected = pieDoc; + const actual = migrate(pieDoc); + + expect(actual).toEqual(expected); + }); + + it('should not change values in any vis that is not table', () => { + const aggs = [ + { + id: '1', + schema: 'metric', + params: {}, + }, + { + id: '2', + schema: 'split', + params: { foo: 'bar', row: true }, + }, + { + id: '3', + schema: 'segment', + params: { hey: 'ya' }, + }, + ]; + const pieDoc = generateDoc('pie', aggs); + const expected = pieDoc; + const actual = migrate(pieDoc); + + expect(actual).toEqual(expected); + }); + + it('should not touch table vis if there are not multiple split aggs', () => { + const aggs = [ + { + id: '1', + schema: 'metric', + params: {}, + }, + { + id: '2', + schema: 'split', + params: { foo: 'bar', row: true }, + }, + ]; + const tableDoc = generateDoc('table', aggs); + const expected = tableDoc; + const actual = migrate(tableDoc); + + expect(actual).toEqual(expected); + }); + + it('should change all split aggs to `bucket` except the first', () => { + const aggs = [ + { + id: '1', + schema: 'metric', + params: {}, + }, + { + id: '2', + schema: 'split', + params: { foo: 'bar', row: true }, + }, + { + id: '3', + schema: 'split', + params: { hey: 'ya', row: false }, + }, + { + id: '4', + schema: 'bucket', + params: { heyyy: 'yaaa' }, + }, + ]; + const expected = ['metric', 'split', 'bucket', 'bucket']; + const migrated = migrate(generateDoc('table', aggs)); + const actual = JSON.parse(migrated.attributes.visState); + + expect(actual.aggs.map((agg: any) => agg.schema)).toEqual(expected); + }); + + it('should remove `rows` param from any aggs that are not `split`', () => { + const aggs = [ + { + id: '1', + schema: 'metric', + params: {}, + }, + { + id: '2', + schema: 'split', + params: { foo: 'bar', row: true }, + }, + { + id: '3', + schema: 'split', + params: { hey: 'ya', row: false }, + }, + ]; + const expected = [{}, { foo: 'bar', row: true }, { hey: 'ya' }]; + const migrated = migrate(generateDoc('table', aggs)); + const actual = JSON.parse(migrated.attributes.visState); + + expect(actual.aggs.map((agg: any) => agg.params)).toEqual(expected); + }); + + it('should throw with a reference to the doc name if something goes wrong', () => { + const doc = { + attributes: { + title: 'My Vis', + description: 'This is my super cool vis.', + visState: '!/// Intentionally malformed JSON ///!', + uiStateJSON: '{}', + version: 1, + kibanaSavedObjectMeta: { + searchSourceJSON: '{}', + }, + }, + }; + expect(() => migrate(doc)).toThrowError(/My Vis/); + }); + }); + + describe('7.2.0', () => { + describe('date histogram custom interval removal', () => { + const migrate = (doc: any) => + visualizationSavedObjectTypeMigrations['7.2.0']( + doc as Parameters[0], + savedObjectMigrationContext + ); + let doc: any; + + beforeEach(() => { + doc = { + attributes: { + visState: JSON.stringify({ + aggs: [ + { + enabled: true, + id: '1', + params: { + customInterval: '1h', + }, + schema: 'metric', + type: 'count', + }, + { + enabled: true, + id: '2', + params: { + customInterval: '2h', + drop_partials: false, + extended_bounds: {}, + field: 'timestamp', + interval: 'auto', + min_doc_count: 1, + useNormalizedEsInterval: true, + }, + schema: 'segment', + type: 'date_histogram', + }, + { + enabled: true, + id: '4', + params: { + customInterval: '2h', + drop_partials: false, + extended_bounds: {}, + field: 'timestamp', + interval: 'custom', + min_doc_count: 1, + useNormalizedEsInterval: true, + }, + schema: 'segment', + type: 'date_histogram', + }, + { + enabled: true, + id: '3', + params: { + customBucket: { + enabled: true, + id: '1-bucket', + params: { + customInterval: '2h', + drop_partials: false, + extended_bounds: {}, + field: 'timestamp', + interval: 'custom', + min_doc_count: 1, + useNormalizedEsInterval: true, + }, + type: 'date_histogram', + }, + customMetric: { + enabled: true, + id: '1-metric', + params: {}, + type: 'count', + }, + }, + schema: 'metric', + type: 'max_bucket', + }, + ], + }), + }, + }; + }); + + it('should remove customInterval from date_histogram aggregations', () => { + const migratedDoc = migrate(doc); + const { aggs } = JSON.parse(migratedDoc.attributes.visState); + + expect(aggs[1]).not.toHaveProperty('params.customInterval'); + }); + + it('should not change interval from date_histogram aggregations', () => { + const migratedDoc = migrate(doc); + const { aggs } = JSON.parse(migratedDoc.attributes.visState); + + expect(aggs[1].params.interval).toBe( + JSON.parse(doc.attributes.visState).aggs[1].params.interval + ); + }); + + it('should not remove customInterval from non date_histogram aggregations', () => { + const migratedDoc = migrate(doc); + const { aggs } = JSON.parse(migratedDoc.attributes.visState); + + expect(aggs[0]).toHaveProperty('params.customInterval'); + }); + + it('should set interval with customInterval value and remove customInterval when interval equals "custom"', () => { + const migratedDoc = migrate(doc); + const { aggs } = JSON.parse(migratedDoc.attributes.visState); + + expect(aggs[2].params.interval).toBe( + JSON.parse(doc.attributes.visState).aggs[2].params.customInterval + ); + expect(aggs[2]).not.toHaveProperty('params.customInterval'); + }); + + it('should remove customInterval from nested aggregations', () => { + const migratedDoc = migrate(doc); + const { aggs } = JSON.parse(migratedDoc.attributes.visState); + + expect(aggs[3]).not.toHaveProperty('params.customBucket.params.customInterval'); + }); + + it('should remove customInterval from nested aggregations and set interval with customInterval value', () => { + const migratedDoc = migrate(doc); + const { aggs } = JSON.parse(migratedDoc.attributes.visState); + + expect(aggs[3].params.customBucket.params.interval).toBe( + JSON.parse(doc.attributes.visState).aggs[3].params.customBucket.params.customInterval + ); + expect(aggs[3]).not.toHaveProperty('params.customBucket.params.customInterval'); + }); + + it('should not fail on date histograms without a customInterval', () => { + const migratedDoc = migrate(doc); + const { aggs } = JSON.parse(migratedDoc.attributes.visState); + + expect(aggs[3]).not.toHaveProperty('params.customInterval'); + }); + }); + }); + + describe('7.3.0', () => { + const logMsgArr: string[] = []; + const logger = ({ + log: { + warn: (msg: string) => logMsgArr.push(msg), + }, + } as unknown) as SavedObjectMigrationContext; + + const migrate = (doc: any) => + visualizationSavedObjectTypeMigrations['7.3.0']( + doc as Parameters[0], + logger + ); + + it('migrates type = gauge verticalSplit: false to alignment: vertical', () => { + const migratedDoc = migrate({ + attributes: { + visState: JSON.stringify({ type: 'gauge', params: { gauge: { verticalSplit: false } } }), + }, + }); + expect(migratedDoc).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "visState": "{\\"type\\":\\"gauge\\",\\"params\\":{\\"gauge\\":{\\"alignment\\":\\"horizontal\\"}}}", + }, +} +`); + }); + + it('migrates type = gauge verticalSplit: false to alignment: horizontal', () => { + const migratedDoc = migrate({ + attributes: { + visState: JSON.stringify({ type: 'gauge', params: { gauge: { verticalSplit: true } } }), + }, + }); + + expect(migratedDoc).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "visState": "{\\"type\\":\\"gauge\\",\\"params\\":{\\"gauge\\":{\\"alignment\\":\\"vertical\\"}}}", + }, +} +`); + }); + + it('doesnt migrate type = gauge containing invalid visState object, adds message to log', () => { + const migratedDoc = migrate({ + attributes: { + visState: JSON.stringify({ type: 'gauge' }), + }, + }); + + expect(migratedDoc).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "visState": "{\\"type\\":\\"gauge\\"}", + }, +} +`); + expect(logMsgArr).toMatchInlineSnapshot(` +Array [ + "Exception @ migrateGaugeVerticalSplitToAlignment! TypeError: Cannot read property 'gauge' of undefined", + "Exception @ migrateGaugeVerticalSplitToAlignment! Payload: {\\"type\\":\\"gauge\\"}", +] +`); + }); + + describe('filters agg query migration', () => { + const doc = { + attributes: { + visState: JSON.stringify({ + aggs: [ + { + type: 'filters', + params: { + filters: [ + { + input: { + query: 'response:200', + }, + label: '', + }, + { + input: { + query: 'response:404', + }, + label: 'bad response', + }, + { + input: { + query: { + exists: { + field: 'phpmemory', + }, + }, + }, + label: '', + }, + ], + }, + }, + ], + }), + }, + }; + + it('should add language property to filters without one, assuming lucene', () => { + const migrationResult = migrate(doc); + + expect(migrationResult).toEqual({ + attributes: { + visState: JSON.stringify({ + aggs: [ + { + type: 'filters', + params: { + filters: [ + { + input: { + query: 'response:200', + language: 'lucene', + }, + label: '', + }, + { + input: { + query: 'response:404', + language: 'lucene', + }, + label: 'bad response', + }, + { + input: { + query: { + exists: { + field: 'phpmemory', + }, + }, + language: 'lucene', + }, + label: '', + }, + ], + }, + }, + ], + }), + }, + }); + }); + }); + + describe('replaceMovAvgToMovFn()', () => { + let doc: any; + + beforeEach(() => { + doc = { + attributes: { + title: 'VIS', + visState: `{"title":"VIS","type":"metrics","params":{"id":"61ca57f0-469d-11e7-af02-69e470af7417", + "type":"timeseries","series":[{"id":"61ca57f1-469d-11e7-af02-69e470af7417","color":"rgba(0,156,224,1)", + "split_mode":"terms","metrics":[{"id":"61ca57f2-469d-11e7-af02-69e470af7417","type":"count", + "numerator":"FlightDelay:true"},{"settings":"","minimize":0,"window":5,"model": + "holt_winters","id":"23054fe0-8915-11e9-9b86-d3f94982620f","type":"moving_average","field": + "61ca57f2-469d-11e7-af02-69e470af7417","predict":1}],"separate_axis":0,"axis_position":"right", + "formatter":"number","chart_type":"line","line_width":"2","point_size":"0","fill":0.5,"stacked":"none", + "label":"Percent Delays","terms_size":"2","terms_field":"OriginCityName"}],"time_field":"timestamp", + "index_pattern":"kibana_sample_data_flights","interval":">=12h","axis_position":"left","axis_formatter": + "number","show_legend":1,"show_grid":1,"annotations":[{"fields":"FlightDelay,Cancelled,Carrier", + "template":"{{Carrier}}: Flight Delayed and Cancelled!","index_pattern":"kibana_sample_data_flights", + "query_string":"FlightDelay:true AND Cancelled:true","id":"53b7dff0-4c89-11e8-a66a-6989ad5a0a39", + "color":"rgba(0,98,177,1)","time_field":"timestamp","icon":"fa-exclamation-triangle", + "ignore_global_filters":1,"ignore_panel_filters":1,"hidden":true}],"legend_position":"bottom", + "axis_scale":"normal","default_index_pattern":"kibana_sample_data_flights","default_timefield":"timestamp"}, + "aggs":[]}`, + }, + migrationVersion: { + visualization: '7.2.0', + }, + type: 'visualization', + }; + }); + + test('should add some necessary moving_fn fields', () => { + const migratedDoc = migrate(doc); + const visState = JSON.parse(migratedDoc.attributes.visState); + const metric = visState.params.series[0].metrics[1]; + + expect(metric).toHaveProperty('model_type'); + expect(metric).toHaveProperty('alpha'); + expect(metric).toHaveProperty('beta'); + expect(metric).toHaveProperty('gamma'); + expect(metric).toHaveProperty('period'); + expect(metric).toHaveProperty('multiplicative'); + }); + }); + }); + + describe('7.3.0 tsvb', () => { + const migrate = (doc: any) => + visualizationSavedObjectTypeMigrations['7.3.0']( + doc as Parameters[0], + savedObjectMigrationContext + ); + + const generateDoc = (params: any) => ({ + attributes: { + title: 'My Vis', + description: 'This is my super cool vis.', + visState: JSON.stringify({ params }), + uiStateJSON: '{}', + version: 1, + kibanaSavedObjectMeta: { + searchSourceJSON: '{}', + }, + }, + }); + it('should change series item filters from a string into an object', () => { + const params = { type: 'metric', series: [{ filter: 'Filter Bytes Test:>1000' }] }; + const testDoc1 = generateDoc(params); + const migratedTestDoc1 = migrate(testDoc1); + const series = JSON.parse(migratedTestDoc1.attributes.visState).params.series; + + expect(series[0].filter).toHaveProperty('query'); + expect(series[0].filter).toHaveProperty('language'); + }); + it('should not change a series item filter string in the object after migration', () => { + const markdownParams = { + type: 'markdown', + series: [ + { + filter: 'Filter Bytes Test:>1000', + split_filters: [{ filter: 'bytes:>1000' }], + }, + ], + }; + const markdownDoc = generateDoc(markdownParams); + const migratedMarkdownDoc = migrate(markdownDoc); + const markdownSeries = JSON.parse(migratedMarkdownDoc.attributes.visState).params.series; + + expect(markdownSeries[0].filter.query).toBe( + JSON.parse(markdownDoc.attributes.visState).params.series[0].filter + ); + expect(markdownSeries[0].split_filters[0].filter.query).toBe( + JSON.parse(markdownDoc.attributes.visState).params.series[0].split_filters[0].filter + ); + }); + + it('should change series item filters from a string into an object for all filters', () => { + const params = { + type: 'timeseries', + filter: 'bytes:>1000', + series: [ + { + filter: 'Filter Bytes Test:>1000', + split_filters: [{ filter: 'bytes:>1000' }], + }, + ], + annotations: [{ query_string: 'bytes:>1000' }], + }; + const timeSeriesDoc = generateDoc(params); + const migratedtimeSeriesDoc = migrate(timeSeriesDoc); + const timeSeriesParams = JSON.parse(migratedtimeSeriesDoc.attributes.visState).params; + + expect(Object.keys(timeSeriesParams.series[0].filter)).toEqual( + expect.arrayContaining(['query', 'language']) + ); + expect(Object.keys(timeSeriesParams.series[0].split_filters[0].filter)).toEqual( + expect.arrayContaining(['query', 'language']) + ); + expect(Object.keys(timeSeriesParams.annotations[0].query_string)).toEqual( + expect.arrayContaining(['query', 'language']) + ); + }); + + it('should not fail on a metric visualization without a filter in a series item', () => { + const params = { type: 'metric', series: [{}, {}, {}] }; + const testDoc1 = generateDoc(params); + const migratedTestDoc1 = migrate(testDoc1); + const series = JSON.parse(migratedTestDoc1.attributes.visState).params.series; + + expect(series[2]).not.toHaveProperty('filter.query'); + }); + + it('should not migrate a visualization of unknown type', () => { + const params = { type: 'unknown', series: [{ filter: 'foo:bar' }] }; + const doc = generateDoc(params); + const migratedDoc = migrate(doc); + const series = JSON.parse(migratedDoc.attributes.visState).params.series; + + expect(series[0].filter).toEqual(params.series[0].filter); + }); + }); + + describe('7.3.1', () => { + const migrate = (doc: any) => + visualizationSavedObjectTypeMigrations['7.3.1']( + doc as Parameters[0], + savedObjectMigrationContext + ); + + it('should migrate filters agg query string queries', () => { + const state = { + aggs: [ + { type: 'count', params: {} }, + { + type: 'filters', + params: { + filters: [ + { + input: { + query: { + query_string: { query: 'machine.os.keyword:"win 8"' }, + }, + }, + }, + ], + }, + }, + ], + }; + const expected = { + aggs: [ + { type: 'count', params: {} }, + { + type: 'filters', + params: { + filters: [{ input: { query: 'machine.os.keyword:"win 8"' } }], + }, + }, + ], + }; + const migratedDoc = migrate({ attributes: { visState: JSON.stringify(state) } }); + + expect(migratedDoc).toEqual({ attributes: { visState: JSON.stringify(expected) } }); + }); + }); + + describe('7.4.2 tsvb split_filters migration', () => { + const migrate = (doc: any) => + visualizationSavedObjectTypeMigrations['7.4.2']( + doc as Parameters[0], + savedObjectMigrationContext + ); + const generateDoc = (params: any) => ({ + attributes: { + title: 'My Vis', + description: 'This is my super cool vis.', + visState: JSON.stringify({ params }), + uiStateJSON: '{}', + version: 1, + kibanaSavedObjectMeta: { + searchSourceJSON: '{}', + }, + }, + }); + + it('should change series item filters from a string into an object for all filters', () => { + const params = { + type: 'timeseries', + filter: { + query: 'bytes:>1000', + language: 'lucene', + }, + series: [ + { + split_filters: [{ filter: 'bytes:>1000' }], + }, + ], + }; + const timeSeriesDoc = generateDoc(params); + const migratedtimeSeriesDoc = migrate(timeSeriesDoc); + const timeSeriesParams = JSON.parse(migratedtimeSeriesDoc.attributes.visState).params; + + expect(Object.keys(timeSeriesParams.filter)).toEqual( + expect.arrayContaining(['query', 'language']) + ); + expect(timeSeriesParams.series[0].split_filters[0].filter).toEqual({ + query: 'bytes:>1000', + language: 'lucene', + }); + }); + + it('should change series item split filters when there is no filter item', () => { + const params = { + type: 'timeseries', + filter: { + query: 'bytes:>1000', + language: 'lucene', + }, + series: [ + { + split_filters: [{ filter: 'bytes:>1000' }], + }, + ], + annotations: [ + { + query_string: { + query: 'bytes:>1000', + language: 'lucene', + }, + }, + ], + }; + const timeSeriesDoc = generateDoc(params); + const migratedtimeSeriesDoc = migrate(timeSeriesDoc); + const timeSeriesParams = JSON.parse(migratedtimeSeriesDoc.attributes.visState).params; + + expect(timeSeriesParams.series[0].split_filters[0].filter).toEqual({ + query: 'bytes:>1000', + language: 'lucene', + }); + }); + + it('should not convert split_filters to objects if there are no split filter filters', () => { + const params = { + type: 'timeseries', + filter: { + query: 'bytes:>1000', + language: 'lucene', + }, + series: [ + { + split_filters: [], + }, + ], + }; + const timeSeriesDoc = generateDoc(params); + const migratedtimeSeriesDoc = migrate(timeSeriesDoc); + const timeSeriesParams = JSON.parse(migratedtimeSeriesDoc.attributes.visState).params; + + expect(timeSeriesParams.series[0].split_filters).not.toHaveProperty('query'); + }); + + it('should do nothing if a split_filter is already a query:language object', () => { + const params = { + type: 'timeseries', + filter: { + query: 'bytes:>1000', + language: 'lucene', + }, + series: [ + { + split_filters: [ + { + filter: { + query: 'bytes:>1000', + language: 'lucene', + }, + }, + ], + }, + ], + annotations: [ + { + query_string: { + query: 'bytes:>1000', + language: 'lucene', + }, + }, + ], + }; + const timeSeriesDoc = generateDoc(params); + const migratedtimeSeriesDoc = migrate(timeSeriesDoc); + const timeSeriesParams = JSON.parse(migratedtimeSeriesDoc.attributes.visState).params; + + expect(timeSeriesParams.series[0].split_filters[0].filter.query).toEqual('bytes:>1000'); + expect(timeSeriesParams.series[0].split_filters[0].filter.language).toEqual('lucene'); + }); + }); +}); diff --git a/src/plugins/visualizations/server/saved_objects/visualization_migrations.ts b/src/plugins/visualizations/server/saved_objects/visualization_migrations.ts new file mode 100644 index 0000000000000..9ee355cbb23cf --- /dev/null +++ b/src/plugins/visualizations/server/saved_objects/visualization_migrations.ts @@ -0,0 +1,574 @@ +/* + * 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 { SavedObjectMigrationFn } from 'kibana/server'; +import { cloneDeep, get, omit, has, flow } from 'lodash'; + +const migrateIndexPattern: SavedObjectMigrationFn = doc => { + const searchSourceJSON = get(doc, 'attributes.kibanaSavedObjectMeta.searchSourceJSON'); + if (typeof searchSourceJSON !== 'string') { + return doc; + } + let searchSource; + try { + searchSource = JSON.parse(searchSourceJSON); + } catch (e) { + // Let it go, the data is invalid and we'll leave it as is + return doc; + } + + if (searchSource.index && Array.isArray(doc.references)) { + searchSource.indexRefName = 'kibanaSavedObjectMeta.searchSourceJSON.index'; + doc.references.push({ + name: searchSource.indexRefName, + type: 'index-pattern', + id: searchSource.index, + }); + delete searchSource.index; + } + if (searchSource.filter) { + searchSource.filter.forEach((filterRow: any, i: number) => { + if (!filterRow.meta || !filterRow.meta.index || !Array.isArray(doc.references)) { + return; + } + filterRow.meta.indexRefName = `kibanaSavedObjectMeta.searchSourceJSON.filter[${i}].meta.index`; + doc.references.push({ + name: filterRow.meta.indexRefName, + type: 'index-pattern', + id: filterRow.meta.index, + }); + delete filterRow.meta.index; + }); + } + + doc.attributes.kibanaSavedObjectMeta.searchSourceJSON = JSON.stringify(searchSource); + + return doc; +}; + +// [TSVB] Migrate percentile-rank aggregation (value -> values) +const migratePercentileRankAggregation: SavedObjectMigrationFn = doc => { + const visStateJSON = get(doc, 'attributes.visState'); + let visState; + + if (visStateJSON) { + try { + visState = JSON.parse(visStateJSON); + } catch (e) { + // Let it go, the data is invalid and we'll leave it as is + } + if (visState && visState.type === 'metrics') { + const series: any[] = get(visState, 'params.series') || []; + + series.forEach(part => { + (part.metrics || []).forEach((metric: any) => { + if (metric.type === 'percentile_rank' && has(metric, 'value')) { + metric.values = [metric.value]; + + delete metric.value; + } + }); + }); + + return { + ...doc, + attributes: { + ...doc.attributes, + visState: JSON.stringify(visState), + }, + }; + } + } + return doc; +}; + +// Migrate date histogram aggregation (remove customInterval) +const migrateDateHistogramAggregation: SavedObjectMigrationFn = doc => { + const visStateJSON = get(doc, 'attributes.visState'); + let visState; + + if (visStateJSON) { + try { + visState = JSON.parse(visStateJSON); + } catch (e) { + // Let it go, the data is invalid and we'll leave it as is + } + + if (visState && visState.aggs) { + visState.aggs.forEach((agg: any) => { + if (agg.type === 'date_histogram' && agg.params) { + if (agg.params.interval === 'custom') { + agg.params.interval = agg.params.customInterval; + } + delete agg.params.customInterval; + } + + if ( + get(agg, 'params.customBucket.type', null) === 'date_histogram' && + agg.params.customBucket.params + ) { + if (agg.params.customBucket.params.interval === 'custom') { + agg.params.customBucket.params.interval = agg.params.customBucket.params.customInterval; + } + delete agg.params.customBucket.params.customInterval; + } + }); + return { + ...doc, + attributes: { + ...doc.attributes, + visState: JSON.stringify(visState), + }, + }; + } + } + return doc; +}; + +const removeDateHistogramTimeZones: SavedObjectMigrationFn = doc => { + const visStateJSON = get(doc, 'attributes.visState'); + if (visStateJSON) { + let visState; + try { + visState = JSON.parse(visStateJSON); + } catch (e) { + // Let it go, the data is invalid and we'll leave it as is + } + if (visState && visState.aggs) { + visState.aggs.forEach((agg: any) => { + // We're checking always for the existance of agg.params here. This should always exist, but better + // be safe then sorry during migrations. + if (agg.type === 'date_histogram' && agg.params) { + delete agg.params.time_zone; + } + + if ( + get(agg, 'params.customBucket.type', null) === 'date_histogram' && + agg.params.customBucket.params + ) { + delete agg.params.customBucket.params.time_zone; + } + }); + doc.attributes.visState = JSON.stringify(visState); + } + } + return doc; +}; + +// migrate gauge verticalSplit to alignment +// https://github.com/elastic/kibana/issues/34636 +const migrateGaugeVerticalSplitToAlignment: SavedObjectMigrationFn = (doc, logger) => { + const visStateJSON = get(doc, 'attributes.visState'); + + if (visStateJSON) { + try { + const visState = JSON.parse(visStateJSON); + if (visState && visState.type === 'gauge' && !visState.params.gauge.alignment) { + visState.params.gauge.alignment = visState.params.gauge.verticalSplit + ? 'vertical' + : 'horizontal'; + delete visState.params.gauge.verticalSplit; + return { + ...doc, + attributes: { + ...doc.attributes, + visState: JSON.stringify(visState), + }, + }; + } + } catch (e) { + logger.log.warn(`Exception @ migrateGaugeVerticalSplitToAlignment! ${e}`); + logger.log.warn(`Exception @ migrateGaugeVerticalSplitToAlignment! Payload: ${visStateJSON}`); + } + } + return doc; +}; +// Migrate filters (string -> { query: string, language: lucene }) +/* + Enabling KQL in TSVB causes problems with savedObject visualizations when these are saved with filters. + In a visualisation type of saved object, if the visState param is of type metric, the filter is saved as a string that is not interpretted correctly as a lucene query in the visualization itself. + We need to transform the filter string into an object containing the original string as a query and specify the query language as lucene. + For Metrics visualizations (param.type === "metric"), filters can be applied to each series object in the series array within the SavedObject.visState.params object. + Path to the series array is thus: + attributes.visState. +*/ +const transformFilterStringToQueryObject: SavedObjectMigrationFn = (doc, logger) => { + // Migrate filters + // If any filters exist and they are a string, we assume it to be lucene and transform the filter into an object accordingly + const newDoc = cloneDeep(doc); + const visStateJSON = get(doc, 'attributes.visState'); + if (visStateJSON) { + let visState; + try { + visState = JSON.parse(visStateJSON); + } catch (e) { + // let it go, the data is invalid and we'll leave it as is + } + if (visState) { + const visType = get(visState, 'params.type'); + const tsvbTypes = ['metric', 'markdown', 'top_n', 'gauge', 'table', 'timeseries']; + if (tsvbTypes.indexOf(visType) === -1) { + // skip + return doc; + } + // migrate the params fitler + const params: any = get(visState, 'params'); + if (params.filter && typeof params.filter === 'string') { + const paramsFilterObject = { + query: params.filter, + language: 'lucene', + }; + params.filter = paramsFilterObject; + } + + // migrate the annotations query string: + const annotations: any[] = get(visState, 'params.annotations') || []; + annotations.forEach(item => { + if (!item.query_string) { + // we don't need to transform anything if there isn't a filter at all + return; + } + if (typeof item.query_string === 'string') { + const itemQueryStringObject = { + query: item.query_string, + language: 'lucene', + }; + item.query_string = itemQueryStringObject; + } + }); + // migrate the series filters + const series: any[] = get(visState, 'params.series') || []; + + series.forEach(item => { + if (!item.filter) { + // we don't need to transform anything if there isn't a filter at all + return; + } + // series item filter + if (typeof item.filter === 'string') { + const itemfilterObject = { + query: item.filter, + language: 'lucene', + }; + item.filter = itemfilterObject; + } + // series item split filters filter + if (item.split_filters) { + const splitFilters: any[] = get(item, 'split_filters') || []; + splitFilters.forEach(filter => { + if (!filter.filter) { + // we don't need to transform anything if there isn't a filter at all + return; + } + if (typeof filter.filter === 'string') { + const filterfilterObject = { + query: filter.filter, + language: 'lucene', + }; + filter.filter = filterfilterObject; + } + }); + } + }); + newDoc.attributes.visState = JSON.stringify(visState); + } + } + return newDoc; +}; + +const transformSplitFiltersStringToQueryObject: SavedObjectMigrationFn = doc => { + // Migrate split_filters in TSVB objects that weren't migrated in 7.3 + // If any filters exist and they are a string, we assume them to be lucene syntax and transform the filter into an object accordingly + const newDoc = cloneDeep(doc); + const visStateJSON = get(doc, 'attributes.visState'); + if (visStateJSON) { + let visState; + try { + visState = JSON.parse(visStateJSON); + } catch (e) { + // let it go, the data is invalid and we'll leave it as is + } + if (visState) { + const visType = get(visState, 'params.type'); + const tsvbTypes = ['metric', 'markdown', 'top_n', 'gauge', 'table', 'timeseries']; + if (tsvbTypes.indexOf(visType) === -1) { + // skip + return doc; + } + // migrate the series split_filter filters + const series: any[] = get(visState, 'params.series') || []; + series.forEach(item => { + // series item split filters filter + if (item.split_filters) { + const splitFilters: any[] = get(item, 'split_filters') || []; + if (splitFilters.length > 0) { + // only transform split_filter filters if we have filters + splitFilters.forEach(filter => { + if (typeof filter.filter === 'string') { + const filterfilterObject = { + query: filter.filter, + language: 'lucene', + }; + filter.filter = filterfilterObject; + } + }); + } + } + }); + newDoc.attributes.visState = JSON.stringify(visState); + } + } + return newDoc; +}; + +const migrateFiltersAggQuery: SavedObjectMigrationFn = doc => { + const visStateJSON = get(doc, 'attributes.visState'); + + if (visStateJSON) { + try { + const visState = JSON.parse(visStateJSON); + + if (visState && visState.aggs) { + visState.aggs.forEach((agg: any) => { + if (agg.type !== 'filters') return; + + agg.params.filters.forEach((filter: any) => { + if (filter.input.language) return filter; + filter.input.language = 'lucene'; + }); + }); + + return { + ...doc, + attributes: { + ...doc.attributes, + visState: JSON.stringify(visState), + }, + }; + } + } catch (e) { + // Let it go, the data is invalid and we'll leave it as is + } + } + return doc; +}; + +const replaceMovAvgToMovFn: SavedObjectMigrationFn = (doc, logger) => { + const visStateJSON = get(doc, 'attributes.visState'); + let visState; + + if (visStateJSON) { + try { + visState = JSON.parse(visStateJSON); + + if (visState && visState.type === 'metrics') { + const series: any[] = get(visState, 'params.series', []); + + series.forEach(part => { + if (part.metrics && Array.isArray(part.metrics)) { + part.metrics.forEach((metric: any) => { + if (metric.type === 'moving_average') { + metric.model_type = metric.model; + metric.alpha = get(metric, 'settings.alpha', 0.3); + metric.beta = get(metric, 'settings.beta', 0.1); + metric.gamma = get(metric, 'settings.gamma', 0.3); + metric.period = get(metric, 'settings.period', 1); + metric.multiplicative = get(metric, 'settings.type') === 'mult'; + + delete metric.minimize; + delete metric.model; + delete metric.settings; + delete metric.predict; + } + }); + } + }); + + return { + ...doc, + attributes: { + ...doc.attributes, + visState: JSON.stringify(visState), + }, + }; + } + } catch (e) { + logger.log.warn(`Exception @ replaceMovAvgToMovFn! ${e}`); + logger.log.warn(`Exception @ replaceMovAvgToMovFn! Payload: ${visStateJSON}`); + } + } + + return doc; +}; + +const migrateFiltersAggQueryStringQueries: SavedObjectMigrationFn = (doc, logger) => { + const visStateJSON = get(doc, 'attributes.visState'); + + if (visStateJSON) { + try { + const visState = JSON.parse(visStateJSON); + + if (visState && visState.aggs) { + visState.aggs.forEach((agg: any) => { + if (agg.type !== 'filters') return doc; + + agg.params.filters.forEach((filter: any) => { + if (filter.input.query.query_string) { + filter.input.query = filter.input.query.query_string.query; + } + }); + }); + + return { + ...doc, + attributes: { + ...doc.attributes, + visState: JSON.stringify(visState), + }, + }; + } + } catch (e) { + // Let it go, the data is invalid and we'll leave it as is + } + } + return doc; +}; + +const addDocReferences: SavedObjectMigrationFn = doc => ({ + ...doc, + references: doc.references || [], +}); + +const migrateSavedSearch: SavedObjectMigrationFn = doc => { + const savedSearchId = get(doc, 'attributes.savedSearchId'); + + if (savedSearchId && doc.references) { + doc.references.push({ + type: 'search', + name: 'search_0', + id: savedSearchId, + }); + doc.attributes.savedSearchRefName = 'search_0'; + } + + delete doc.attributes.savedSearchId; + + return doc; +}; + +const migrateControls: SavedObjectMigrationFn = doc => { + const visStateJSON = get(doc, 'attributes.visState'); + + if (visStateJSON) { + let visState; + try { + visState = JSON.parse(visStateJSON); + } catch (e) { + // Let it go, the data is invalid and we'll leave it as is + } + if (visState) { + const controls: any[] = get(visState, 'params.controls') || []; + controls.forEach((control, i) => { + if (!control.indexPattern || !doc.references) { + return; + } + control.indexPatternRefName = `control_${i}_index_pattern`; + doc.references.push({ + name: control.indexPatternRefName, + type: 'index-pattern', + id: control.indexPattern, + }); + delete control.indexPattern; + }); + doc.attributes.visState = JSON.stringify(visState); + } + } + + return doc; +}; + +const migrateTableSplits: SavedObjectMigrationFn = doc => { + try { + const visState = JSON.parse(doc.attributes.visState); + if (get(visState, 'type') !== 'table') { + return doc; // do nothing; we only want to touch tables + } + + let splitCount = 0; + visState.aggs = visState.aggs.map((agg: any) => { + if (agg.schema !== 'split') { + return agg; + } + + splitCount++; + if (splitCount === 1) { + return agg; // leave the first split agg unchanged + } + agg.schema = 'bucket'; + // the `row` param is exclusively used by split aggs, so we remove it + agg.params = omit(agg.params, ['row']); + return agg; + }); + + if (splitCount <= 1) { + return doc; // do nothing; we only want to touch tables with multiple split aggs + } + + const newDoc = cloneDeep(doc); + newDoc.attributes.visState = JSON.stringify(visState); + + return newDoc; + } catch (e) { + throw new Error(`Failure attempting to migrate saved object '${doc.attributes.title}' - ${e}`); + } +}; + +export const visualizationSavedObjectTypeMigrations = { + /** + * We need to have this migration twice, once with a version prior to 7.0.0 once with a version + * after it. The reason for that is, that this migration has been introduced once 7.0.0 was already + * released. Thus a user who already had 7.0.0 installed already got the 7.0.0 migrations below running, + * so we need a version higher than that. But this fix was backported to the 6.7 release, meaning if we + * would only have the 7.0.1 migration in here a user on the 6.7 release will migrate their saved objects + * to the 7.0.1 state, and thus when updating their Kibana to 7.0, will never run the 7.0.0 migrations introduced + * in that version. So we apply this twice, once with 6.7.2 and once with 7.0.1 while the backport to 6.7 + * only contained the 6.7.2 migration and not the 7.0.1 migration. + */ + '6.7.2': flow(removeDateHistogramTimeZones), + '7.0.0': flow( + addDocReferences, + migrateIndexPattern, + migrateSavedSearch, + migrateControls, + migrateTableSplits + ), + '7.0.1': flow(removeDateHistogramTimeZones), + '7.2.0': flow( + migratePercentileRankAggregation, + migrateDateHistogramAggregation + ), + '7.3.0': flow( + migrateGaugeVerticalSplitToAlignment, + transformFilterStringToQueryObject, + migrateFiltersAggQuery, + replaceMovAvgToMovFn + ), + '7.3.1': flow(migrateFiltersAggQueryStringQueries), + '7.4.2': flow(transformSplitFiltersStringToQueryObject), +}; diff --git a/src/plugins/visualizations/server/types.ts b/src/plugins/visualizations/server/types.ts new file mode 100644 index 0000000000000..6924edb29627d --- /dev/null +++ b/src/plugins/visualizations/server/types.ts @@ -0,0 +1,23 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface VisualizationsPluginSetup {} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface VisualizationsPluginStart {} diff --git a/src/test_utils/kbn_server.ts b/src/test_utils/kbn_server.ts index f4c3ecd8243ce..12f7eb5a0a043 100644 --- a/src/test_utils/kbn_server.ts +++ b/src/test_utils/kbn_server.ts @@ -50,6 +50,7 @@ const DEFAULTS_SETTINGS = { logging: { silent: true }, plugins: {}, optimize: { enabled: false }, + migrations: { skip: true }, }; const DEFAULT_SETTINGS_WITH_CORE_PLUGINS = { diff --git a/test/api_integration/apis/index.js b/test/api_integration/apis/index.js index 8cdbbf8e74a3d..57e9120773f33 100644 --- a/test/api_integration/apis/index.js +++ b/test/api_integration/apis/index.js @@ -33,6 +33,5 @@ export default function({ loadTestFile }) { loadTestFile(require.resolve('./status')); loadTestFile(require.resolve('./stats')); loadTestFile(require.resolve('./ui_metric')); - loadTestFile(require.resolve('./core')); }); } diff --git a/test/functional/apps/visualize/index.ts b/test/functional/apps/visualize/index.ts index 68285971e5c4a..06d560530c28a 100644 --- a/test/functional/apps/visualize/index.ts +++ b/test/functional/apps/visualize/index.ts @@ -20,11 +20,13 @@ import { FtrProviderContext } from '../../ftr_provider_context.d'; // eslint-disable-next-line @typescript-eslint/no-namespace, import/no-default-export -export default function({ getService, loadTestFile }: FtrProviderContext) { +export default function({ getService, getPageObjects, loadTestFile }: FtrProviderContext) { const browser = getService('browser'); const log = getService('log'); const esArchiver = getService('esArchiver'); const kibanaServer = getService('kibanaServer'); + const PageObjects = getPageObjects(['common']); + let isOss = true; describe('visualize app', () => { before(async () => { @@ -37,6 +39,7 @@ export default function({ getService, loadTestFile }: FtrProviderContext) { defaultIndex: 'logstash-*', 'format:bytes:defaultPattern': '0,0.[000]b', }); + isOss = await PageObjects.common.isOss(); }); describe('', function() { @@ -67,20 +70,22 @@ export default function({ getService, loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./_line_chart')); loadTestFile(require.resolve('./_pie_chart')); - loadTestFile(require.resolve('./_region_map')); loadTestFile(require.resolve('./_point_series_options')); loadTestFile(require.resolve('./_markdown_vis')); loadTestFile(require.resolve('./_shared_item')); loadTestFile(require.resolve('./_lab_mode')); loadTestFile(require.resolve('./_linked_saved_searches')); loadTestFile(require.resolve('./_visualize_listing')); + if (isOss) { + loadTestFile(require.resolve('./_tile_map')); + loadTestFile(require.resolve('./_region_map')); + } }); describe('', function() { this.tags('ciGroup12'); loadTestFile(require.resolve('./_tag_cloud')); - loadTestFile(require.resolve('./_tile_map')); loadTestFile(require.resolve('./_vertical_bar_chart')); loadTestFile(require.resolve('./_vertical_bar_chart_nontimeindex')); loadTestFile(require.resolve('./_tsvb_chart')); diff --git a/test/plugin_functional/plugins/ui_settings_plugin/server/plugin.ts b/test/plugin_functional/plugins/ui_settings_plugin/server/plugin.ts index c32e8a75d95da..3801d3bbce055 100644 --- a/test/plugin_functional/plugins/ui_settings_plugin/server/plugin.ts +++ b/test/plugin_functional/plugins/ui_settings_plugin/server/plugin.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ - +import { schema } from '@kbn/config-schema'; import { Plugin, CoreSetup } from 'kibana/server'; export class UiSettingsPlugin implements Plugin { @@ -27,6 +27,7 @@ export class UiSettingsPlugin implements Plugin { description: 'just for testing', value: '2', category: ['any'], + schema: schema.string(), }, }); diff --git a/x-pack/legacy/plugins/apm/index.ts b/x-pack/legacy/plugins/apm/index.ts index 2efa13a0bbc8d..0107997f233fe 100644 --- a/x-pack/legacy/plugins/apm/index.ts +++ b/x-pack/legacy/plugins/apm/index.ts @@ -71,7 +71,7 @@ export const apm: LegacyPluginInitializer = kibana => { autocreateApmIndexPattern: Joi.boolean().default(true), // service map - serviceMapEnabled: Joi.boolean().default(false) + serviceMapEnabled: Joi.boolean().default(true) }).default(); }, diff --git a/x-pack/legacy/plugins/apm/public/components/app/Home/__snapshots__/Home.test.tsx.snap b/x-pack/legacy/plugins/apm/public/components/app/Home/__snapshots__/Home.test.tsx.snap index d5764001a7f18..88d9d7864576f 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Home/__snapshots__/Home.test.tsx.snap +++ b/x-pack/legacy/plugins/apm/public/components/app/Home/__snapshots__/Home.test.tsx.snap @@ -6,7 +6,7 @@ exports[`Home component should render services 1`] = ` Object { "config": Object { "indexPatternTitle": "apm-*", - "serviceMapEnabled": false, + "serviceMapEnabled": true, "ui": Object { "enabled": false, }, @@ -46,7 +46,7 @@ exports[`Home component should render traces 1`] = ` Object { "config": Object { "indexPatternTitle": "apm-*", - "serviceMapEnabled": false, + "serviceMapEnabled": true, "ui": Object { "enabled": false, }, diff --git a/x-pack/legacy/plugins/apm/public/components/app/Home/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/Home/index.tsx index 5f8fa8bf5dc07..07d7ce1e5b48c 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Home/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Home/index.tsx @@ -27,7 +27,7 @@ import { ServiceOverview } from '../ServiceOverview'; import { TraceOverview } from '../TraceOverview'; function getHomeTabs({ - serviceMapEnabled = false + serviceMapEnabled = true }: { serviceMapEnabled: boolean; }) { diff --git a/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx b/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx index 4ee45f7b3330b..6bcfbc4541b64 100644 --- a/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx +++ b/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx @@ -206,7 +206,7 @@ const mockCore = { const mockConfig: ConfigSchema = { indexPatternTitle: 'apm-*', - serviceMapEnabled: false, + serviceMapEnabled: true, ui: { enabled: false } diff --git a/x-pack/legacy/plugins/canvas/public/application.tsx b/x-pack/legacy/plugins/canvas/public/application.tsx index 15b5d2543fbc6..e26157aadebcb 100644 --- a/x-pack/legacy/plugins/canvas/public/application.tsx +++ b/x-pack/legacy/plugins/canvas/public/application.tsx @@ -8,14 +8,26 @@ import React from 'react'; import { Store } from 'redux'; import ReactDOM from 'react-dom'; import { I18nProvider } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; import { Provider } from 'react-redux'; -import { AppMountParameters, CoreStart } from 'kibana/public'; +import { AppMountParameters, CoreStart, CoreSetup } from 'kibana/public'; -import { CanvasStartDeps } from './plugin'; +import { CanvasStartDeps, CanvasSetupDeps } from './plugin'; // @ts-ignore Untyped local import { App } from './components/app'; import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public'; +import { initInterpreter, resetInterpreter } from './lib/run_interpreter'; +import { registerLanguage } from './lib/monaco_language_def'; +import { SetupRegistries } from './plugin_api'; +import { initRegistries, populateRegistries, destroyRegistries } from './registries'; +import { getDocumentationLinks } from './lib/documentation_links'; +// @ts-ignore untyped component +import { HelpMenu } from './components/help_menu/help_menu'; +import { createStore } from './store'; + +import { CapabilitiesStrings } from '../i18n'; +const { ReadOnlyBadge: strings } = CapabilitiesStrings; export const renderApp = ( coreStart: CoreStart, @@ -35,3 +47,60 @@ export const renderApp = ( ); return () => ReactDOM.unmountComponentAtNode(element); }; + +export const initializeCanvas = async ( + coreSetup: CoreSetup, + coreStart: CoreStart, + setupPlugins: CanvasSetupDeps, + startPlugins: CanvasStartDeps, + registries: SetupRegistries +) => { + // Create Store + const canvasStore = await createStore(coreSetup, setupPlugins); + + // Init Interpreter + initInterpreter(startPlugins.expressions, setupPlugins.expressions).then(() => { + registerLanguage(Object.values(startPlugins.expressions.getFunctions())); + }); + + // Init Registries + initRegistries(); + populateRegistries(registries); + + // Set Badge + coreStart.chrome.setBadge( + coreStart.application.capabilities.canvas && coreStart.application.capabilities.canvas.save + ? undefined + : { + text: strings.getText(), + tooltip: strings.getTooltip(), + iconType: 'glasses', + } + ); + + // Set help extensions + coreStart.chrome.setHelpExtension({ + appName: i18n.translate('xpack.canvas.helpMenu.appName', { + defaultMessage: 'Canvas', + }), + links: [ + { + linkType: 'documentation', + href: getDocumentationLinks().canvas, + }, + ], + content: domNode => () => { + ReactDOM.render(, domNode); + }, + }); + + return canvasStore; +}; + +export const teardownCanvas = (coreStart: CoreStart) => { + destroyRegistries(); + resetInterpreter(); + + coreStart.chrome.setBadge(undefined); + coreStart.chrome.setHelpExtension(undefined); +}; diff --git a/x-pack/legacy/plugins/canvas/public/lib/run_interpreter.ts b/x-pack/legacy/plugins/canvas/public/lib/run_interpreter.ts index d2f4cca8fe97d..fbbaf0ccf280e 100644 --- a/x-pack/legacy/plugins/canvas/public/lib/run_interpreter.ts +++ b/x-pack/legacy/plugins/canvas/public/lib/run_interpreter.ts @@ -11,7 +11,7 @@ import { notify } from './notify'; import { CanvasStartDeps, CanvasSetupDeps } from '../plugin'; -let expressionsStarting: Promise; +let expressionsStarting: Promise | undefined; export const initInterpreter = function( expressionsStart: CanvasStartDeps['expressions'], @@ -30,6 +30,10 @@ async function startExpressions( return expressionsStart; } +export const resetInterpreter = function() { + expressionsStarting = undefined; +}; + interface Options { castToRender?: boolean; } diff --git a/x-pack/legacy/plugins/canvas/public/plugin.tsx b/x-pack/legacy/plugins/canvas/public/plugin.tsx index 6644b927dd884..0a3faca1a2522 100644 --- a/x-pack/legacy/plugins/canvas/public/plugin.tsx +++ b/x-pack/legacy/plugins/canvas/public/plugin.tsx @@ -4,29 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; -import ReactDOM from 'react-dom'; import { Chrome } from 'ui/chrome'; -import { i18n } from '@kbn/i18n'; import { CoreSetup, CoreStart, Plugin } from '../../../../../src/core/public'; import { HomePublicPluginSetup } from '../../../../../src/plugins/home/public'; -// @ts-ignore: Untyped Local -import { CapabilitiesStrings } from '../i18n'; -const { ReadOnlyBadge: strings } = CapabilitiesStrings; - -import { createStore } from './store'; - -// @ts-ignore: untyped local component -import { HelpMenu } from './components/help_menu/help_menu'; -// @ts-ignore: untyped local -import { loadExpressionTypes } from './lib/load_expression_types'; -// @ts-ignore: untyped local -import { loadTransitions } from './lib/load_transitions'; import { initLoadingIndicator } from './lib/loading_indicator'; -import { getDocumentationLinks } from './lib/documentation_links'; - -// @ts-ignore: untyped local -import { initClipboard } from './lib/clipboard'; import { featureCatalogueEntry } from './feature_catalogue_entry'; import { ExpressionsSetup, ExpressionsStart } from '../../../../../src/plugins/expressions/public'; // @ts-ignore untyped local @@ -34,29 +15,12 @@ import { datasourceSpecs } from './expression_types/datasources'; // @ts-ignore untyped local import { argTypeSpecs } from './expression_types/arg_types'; import { transitions } from './transitions'; -import { registerLanguage } from './lib/monaco_language_def'; - -import { initInterpreter } from './lib/run_interpreter'; import { legacyRegistries } from './legacy_plugin_support'; -import { getPluginApi, CanvasApi, SetupRegistries } from './plugin_api'; -import { - initRegistries, - addElements, - addTransformUIs, - addDatasourceUIs, - addModelUIs, - addViewUIs, - addArgumentUIs, - addTagUIs, - addTemplates, - addTransitions, -} from './registries'; - +import { getPluginApi, CanvasApi } from './plugin_api'; import { initFunctions } from './functions'; - import { CanvasSrcPlugin } from '../canvas_plugin_src/plugin'; - export { CoreStart }; + /** * These are the private interfaces for the services your plugin depends on. * @internal @@ -89,36 +53,36 @@ export interface CanvasStart {} // eslint-disable-line @typescript-eslint/no-emp /** @internal */ export class CanvasPlugin implements Plugin { - private expressionSetup: CanvasSetupDeps['expressions'] | undefined; - private registries: SetupRegistries | undefined; - public setup(core: CoreSetup, plugins: CanvasSetupDeps) { const { api: canvasApi, registries } = getPluginApi(plugins.expressions); - this.registries = registries; core.application.register({ id: 'canvas', title: 'Canvas App', async mount(context, params) { // Load application bundle - const { renderApp } = await import('./application'); - - // Setup our store - const canvasStore = await createStore(core, plugins); + const { renderApp, initializeCanvas, teardownCanvas } = await import('./application'); // Get start services const [coreStart, depsStart] = await core.getStartServices(); - return renderApp(coreStart, depsStart, params, canvasStore); + const canvasStore = await initializeCanvas(core, coreStart, plugins, depsStart, registries); + + const unmount = renderApp(coreStart, depsStart, params, canvasStore); + + return () => { + unmount(); + teardownCanvas(coreStart); + }; }, }); plugins.home.featureCatalogue.register(featureCatalogueEntry); - this.expressionSetup = plugins.expressions; // Register Legacy plugin stuff canvasApi.addFunctions(legacyRegistries.browserFunctions.getOriginalFns()); canvasApi.addElements(legacyRegistries.elements.getOriginalFns()); + canvasApi.addTypes(legacyRegistries.types.getOriginalFns()); // TODO: Do we want to completely move canvas_plugin_src into it's own plugin? const srcPlugin = new CanvasSrcPlugin(); @@ -137,53 +101,6 @@ export class CanvasPlugin public start(core: CoreStart, plugins: CanvasStartDeps) { initLoadingIndicator(core.http.addLoadingCountSource); - initRegistries(); - - if (this.expressionSetup) { - const expressionSetup = this.expressionSetup; - initInterpreter(plugins.expressions, expressionSetup).then(() => { - registerLanguage(Object.values(plugins.expressions.getFunctions())); - }); - } - - if (this.registries) { - addElements(this.registries.elements); - addTransformUIs(this.registries.transformUIs); - addDatasourceUIs(this.registries.datasourceUIs); - addModelUIs(this.registries.modelUIs); - addViewUIs(this.registries.viewUIs); - addArgumentUIs(this.registries.argumentUIs); - addTemplates(this.registries.templates); - addTagUIs(this.registries.tagUIs); - addTransitions(this.registries.transitions); - } else { - throw new Error('Unable to initialize Canvas registries'); - } - - core.chrome.setBadge( - core.application.capabilities.canvas && core.application.capabilities.canvas.save - ? undefined - : { - text: strings.getText(), - tooltip: strings.getTooltip(), - iconType: 'glasses', - } - ); - - core.chrome.setHelpExtension({ - appName: i18n.translate('xpack.canvas.helpMenu.appName', { - defaultMessage: 'Canvas', - }), - links: [ - { - linkType: 'documentation', - href: getDocumentationLinks().canvas, - }, - ], - content: domNode => () => { - ReactDOM.render(, domNode); - }, - }); return {}; } diff --git a/x-pack/legacy/plugins/canvas/public/registries.ts b/x-pack/legacy/plugins/canvas/public/registries.ts index d175ab3934eed..99f309a917329 100644 --- a/x-pack/legacy/plugins/canvas/public/registries.ts +++ b/x-pack/legacy/plugins/canvas/public/registries.ts @@ -11,7 +11,6 @@ import { elementsRegistry } from './lib/elements_registry'; // @ts-ignore untyped local import { templatesRegistry } from './lib/templates_registry'; import { tagsRegistry } from './lib/tags_registry'; -import { ElementFactory } from '../types'; // @ts-ignore untyped local import { transitionsRegistry } from './lib/transitions_registry'; @@ -23,8 +22,9 @@ import { viewRegistry, // @ts-ignore untyped local } from './expression_types'; +import { SetupRegistries } from './plugin_api'; -export const registries = {}; +export let registries = {}; export function initRegistries() { addRegistries(registries, { @@ -40,42 +40,10 @@ export function initRegistries() { }); } -export function addElements(elements: ElementFactory[]) { - register(registries, { elements }); +export function populateRegistries(setupRegistries: SetupRegistries) { + register(registries, setupRegistries); } -export function addTransformUIs(transformUIs: any[]) { - register(registries, { transformUIs }); -} - -export function addDatasourceUIs(datasourceUIs: any[]) { - register(registries, { datasourceUIs }); -} - -export function addModelUIs(modelUIs: any[]) { - register(registries, { modelUIs }); -} - -export function addViewUIs(viewUIs: any[]) { - register(registries, { viewUIs }); -} - -export function addArgumentUIs(argumentUIs: any[]) { - register(registries, { argumentUIs }); -} - -export function addTemplates(templates: any[]) { - register(registries, { templates }); -} - -export function addTagUIs(tagUIs: any[]) { - register(registries, { tagUIs }); -} - -export function addTransitions(transitions: any[]) { - register(registries, { transitions }); -} - -export function addBrowserFunctions(browserFunctions: any[]) { - register(registries, { browserFunctions }); +export function destroyRegistries() { + registries = {}; } diff --git a/x-pack/legacy/plugins/graph/index.ts b/x-pack/legacy/plugins/graph/index.ts index b2d6fd3957d64..5122796335e45 100644 --- a/x-pack/legacy/plugins/graph/index.ts +++ b/x-pack/legacy/plugins/graph/index.ts @@ -4,31 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ -import { resolve } from 'path'; import { i18n } from '@kbn/i18n'; // @ts-ignore import migrations from './migrations'; import mappings from './mappings.json'; import { LegacyPluginInitializer } from '../../../../src/legacy/plugin_discovery/types'; -import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/utils'; export const graph: LegacyPluginInitializer = kibana => { return new kibana.Plugin({ id: 'graph', configPrefix: 'xpack.graph', - publicDir: resolve(__dirname, 'public'), require: ['kibana', 'elasticsearch', 'xpack_main'], uiExports: { - app: { - title: 'Graph', - order: 9000, - icon: 'plugins/graph/icon.png', - euiIconType: 'graphApp', - main: 'plugins/graph/index', - category: DEFAULT_APP_CATEGORIES.analyze, - }, - styleSheetPaths: resolve(__dirname, 'public/index.scss'), mappings, migrations, }, diff --git a/x-pack/legacy/plugins/graph/public/icon.png b/x-pack/legacy/plugins/graph/public/icon.png deleted file mode 100644 index f2a16437209c7..0000000000000 Binary files a/x-pack/legacy/plugins/graph/public/icon.png and /dev/null differ diff --git a/x-pack/legacy/plugins/graph/public/index.ts b/x-pack/legacy/plugins/graph/public/index.ts deleted file mode 100644 index fb60a66fb28cc..0000000000000 --- a/x-pack/legacy/plugins/graph/public/index.ts +++ /dev/null @@ -1,26 +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 { npSetup, npStart } from 'ui/new_platform'; -import { LicensingPluginSetup } from '../../../../plugins/licensing/public'; -import { GraphPlugin } from './plugin'; -import { GraphSetup } from '../../../../plugins/graph/public'; - -type XpackNpSetupDeps = typeof npSetup.plugins & { - licensing: LicensingPluginSetup; - graph: GraphSetup; -}; - -(async () => { - const instance = new GraphPlugin(); - instance.setup(npSetup.core, { - ...(npSetup.plugins as XpackNpSetupDeps), - }); - instance.start(npStart.core, { - npData: npStart.plugins.data, - navigation: npStart.plugins.navigation, - }); -})(); diff --git a/x-pack/legacy/plugins/graph/public/plugin.ts b/x-pack/legacy/plugins/graph/public/plugin.ts deleted file mode 100644 index 4ccaf6b5dfa27..0000000000000 --- a/x-pack/legacy/plugins/graph/public/plugin.ts +++ /dev/null @@ -1,75 +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. - */ - -// NP type imports -import { - AppMountParameters, - CoreSetup, - CoreStart, - Plugin, - SavedObjectsClientContract, -} from 'src/core/public'; -import { Plugin as DataPlugin } from 'src/plugins/data/public'; -import { Storage } from '../../../../../src/plugins/kibana_utils/public'; -import { LicensingPluginSetup } from '../../../../plugins/licensing/public'; -import { NavigationPublicPluginStart as NavigationStart } from '../../../../../src/plugins/navigation/public'; -import { initAngularBootstrap } from '../../../../../src/plugins/kibana_legacy/public'; -import { GraphSetup } from '../../../../plugins/graph/public'; - -export interface GraphPluginStartDependencies { - npData: ReturnType; - navigation: NavigationStart; -} - -export interface GraphPluginSetupDependencies { - licensing: LicensingPluginSetup; - graph: GraphSetup; -} - -export class GraphPlugin implements Plugin { - private navigationStart: NavigationStart | null = null; - private npDataStart: ReturnType | null = null; - private savedObjectsClient: SavedObjectsClientContract | null = null; - - setup(core: CoreSetup, { licensing, graph }: GraphPluginSetupDependencies) { - initAngularBootstrap(); - core.application.register({ - id: 'graph', - title: 'Graph', - mount: async (params: AppMountParameters) => { - const [coreStart] = await core.getStartServices(); - const { renderApp } = await import('./application'); - return renderApp({ - ...params, - licensing, - navigation: this.navigationStart!, - npData: this.npDataStart!, - savedObjectsClient: this.savedObjectsClient!, - addBasePath: core.http.basePath.prepend, - getBasePath: core.http.basePath.get, - canEditDrillDownUrls: graph.config.canEditDrillDownUrls, - graphSavePolicy: graph.config.savePolicy, - storage: new Storage(window.localStorage), - capabilities: coreStart.application.capabilities.graph, - coreStart, - chrome: coreStart.chrome, - config: coreStart.uiSettings, - toastNotifications: coreStart.notifications.toasts, - indexPatterns: this.npDataStart!.indexPatterns, - overlays: coreStart.overlays, - }); - }, - }); - } - - start(core: CoreStart, { npData, navigation }: GraphPluginStartDependencies) { - this.navigationStart = navigation; - this.npDataStart = npData; - this.savedObjectsClient = core.savedObjects.client; - } - - stop() {} -} 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 650e827cc1656..9af1a135794c0 100644 --- a/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable.js +++ b/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable.js @@ -10,10 +10,8 @@ import { Provider } from 'react-redux'; import { render, unmountComponentAtNode } from 'react-dom'; import 'mapbox-gl/dist/mapbox-gl.css'; -import { - Embeddable, - APPLY_FILTER_TRIGGER, -} from '../../../../../../src/legacy/core_plugins/embeddable_api/public/np_ready/public'; +import { Embeddable } from '../../../../../../src/legacy/core_plugins/embeddable_api/public/np_ready/public'; +import { APPLY_FILTER_TRIGGER } from '../../../../../../src/plugins/ui_actions/public'; import { esFilters } from '../../../../../../src/plugins/data/public'; import { I18nContext } from 'ui/i18n'; diff --git a/x-pack/legacy/plugins/siem/cypress/integration/detections.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/detections.spec.ts new file mode 100644 index 0000000000000..c048510c50c36 --- /dev/null +++ b/x-pack/legacy/plugins/siem/cypress/integration/detections.spec.ts @@ -0,0 +1,155 @@ +/* + * 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 { + NUMBER_OF_SIGNALS, + OPEN_CLOSE_SIGNALS_BTN, + SELECTED_SIGNALS, + SHOWING_SIGNALS, + SIGNALS, +} from '../screens/detections'; + +import { + closeFirstSignal, + closeSignals, + goToClosedSignals, + goToOpenedSignals, + openSignals, + selectNumberOfSignals, + waitForSignalsPanelToBeLoaded, + waitForSignals, + waitForSignalsToBeLoaded, +} from '../tasks/detections'; +import { esArchiverLoad } from '../tasks/es_archiver'; +import { loginAndWaitForPage } from '../tasks/login'; + +import { DETECTIONS } from '../urls/navigation'; + +describe('Detections', () => { + beforeEach(() => { + esArchiverLoad('signals'); + loginAndWaitForPage(DETECTIONS); + }); + + it('Closes and opens signals', () => { + waitForSignalsPanelToBeLoaded(); + waitForSignalsToBeLoaded(); + + cy.get(NUMBER_OF_SIGNALS) + .invoke('text') + .then(numberOfSignals => { + cy.get(SHOWING_SIGNALS) + .invoke('text') + .should('eql', `Showing ${numberOfSignals} signals`); + + const numberOfSignalsToBeClosed = 3; + selectNumberOfSignals(numberOfSignalsToBeClosed); + + cy.get(SELECTED_SIGNALS) + .invoke('text') + .should('eql', `Selected ${numberOfSignalsToBeClosed} signals`); + + closeSignals(); + waitForSignals(); + cy.reload(); + waitForSignals(); + + const expectedNumberOfSignalsAfterClosing = +numberOfSignals - numberOfSignalsToBeClosed; + cy.get(NUMBER_OF_SIGNALS) + .invoke('text') + .should('eq', expectedNumberOfSignalsAfterClosing.toString()); + cy.get(SHOWING_SIGNALS) + .invoke('text') + .should('eql', `Showing ${expectedNumberOfSignalsAfterClosing.toString()} signals`); + + goToClosedSignals(); + waitForSignals(); + + cy.get(NUMBER_OF_SIGNALS) + .invoke('text') + .should('eql', numberOfSignalsToBeClosed.toString()); + cy.get(SHOWING_SIGNALS) + .invoke('text') + .should('eql', `Showing ${numberOfSignalsToBeClosed.toString()} signals`); + cy.get(SIGNALS).should('have.length', numberOfSignalsToBeClosed); + + const numberOfSignalsToBeOpened = 1; + selectNumberOfSignals(numberOfSignalsToBeOpened); + + cy.get(SELECTED_SIGNALS) + .invoke('text') + .should('eql', `Selected ${numberOfSignalsToBeOpened} signal`); + + openSignals(); + waitForSignals(); + cy.reload(); + waitForSignalsToBeLoaded(); + waitForSignals(); + goToClosedSignals(); + waitForSignals(); + + const expectedNumberOfClosedSignalsAfterOpened = 2; + cy.get(NUMBER_OF_SIGNALS) + .invoke('text') + .should('eql', expectedNumberOfClosedSignalsAfterOpened.toString()); + cy.get(SHOWING_SIGNALS) + .invoke('text') + .should('eql', `Showing ${expectedNumberOfClosedSignalsAfterOpened.toString()} signals`); + cy.get(SIGNALS).should('have.length', expectedNumberOfClosedSignalsAfterOpened); + + goToOpenedSignals(); + waitForSignals(); + + const expectedNumberOfOpenedSignals = + +numberOfSignals - expectedNumberOfClosedSignalsAfterOpened; + cy.get(SHOWING_SIGNALS) + .invoke('text') + .should('eql', `Showing ${expectedNumberOfOpenedSignals.toString()} signals`); + + cy.get('[data-test-subj="server-side-event-count"]') + .invoke('text') + .should('eql', expectedNumberOfOpenedSignals.toString()); + }); + }); + + it('Closes one signal when more than one opened signals are selected', () => { + waitForSignalsToBeLoaded(); + + cy.get(NUMBER_OF_SIGNALS) + .invoke('text') + .then(numberOfSignals => { + const numberOfSignalsToBeClosed = 1; + const numberOfSignalsToBeSelected = 3; + + cy.get(OPEN_CLOSE_SIGNALS_BTN).should('have.attr', 'disabled'); + selectNumberOfSignals(numberOfSignalsToBeSelected); + cy.get(OPEN_CLOSE_SIGNALS_BTN).should('not.have.attr', 'disabled'); + + closeFirstSignal(); + cy.reload(); + waitForSignalsToBeLoaded(); + waitForSignals(); + + const expectedNumberOfSignals = +numberOfSignals - numberOfSignalsToBeClosed; + cy.get(NUMBER_OF_SIGNALS) + .invoke('text') + .should('eq', expectedNumberOfSignals.toString()); + cy.get(SHOWING_SIGNALS) + .invoke('text') + .should('eql', `Showing ${expectedNumberOfSignals.toString()} signals`); + + goToClosedSignals(); + waitForSignals(); + + cy.get(NUMBER_OF_SIGNALS) + .invoke('text') + .should('eql', numberOfSignalsToBeClosed.toString()); + cy.get(SHOWING_SIGNALS) + .invoke('text') + .should('eql', `Showing ${numberOfSignalsToBeClosed.toString()} signal`); + cy.get(SIGNALS).should('have.length', numberOfSignalsToBeClosed); + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/cypress/screens/detections.ts b/x-pack/legacy/plugins/siem/cypress/screens/detections.ts index 8089b028a10d4..f388ac1215d01 100644 --- a/x-pack/legacy/plugins/siem/cypress/screens/detections.ts +++ b/x-pack/legacy/plugins/siem/cypress/screens/detections.ts @@ -4,6 +4,24 @@ * you may not use this file except in compliance with the Elastic License. */ +export const CLOSED_SIGNALS_BTN = '[data-test-subj="closedSignals"]'; + export const LOADING_SIGNALS_PANEL = '[data-test-subj="loading-signals-panel"]'; export const MANAGE_SIGNAL_DETECTION_RULES_BTN = '[data-test-subj="manage-signal-detection-rules"]'; + +export const NUMBER_OF_SIGNALS = '[data-test-subj="server-side-event-count"]'; + +export const OPEN_CLOSE_SIGNAL_BTN = '[data-test-subj="update-signal-status-button"]'; + +export const OPEN_CLOSE_SIGNALS_BTN = '[data-test-subj="openCloseSignal"] button'; + +export const OPENED_SIGNALS_BTN = '[data-test-subj="openSignals"]'; + +export const SELECTED_SIGNALS = '[data-test-subj="selectedSignals"]'; + +export const SHOWING_SIGNALS = '[data-test-subj="showingSignals"]'; + +export const SIGNALS = '[data-test-subj="event"]'; + +export const SIGNAL_CHECKBOX = '[data-test-subj="select-event-container"] .euiCheckbox__input'; diff --git a/x-pack/legacy/plugins/siem/cypress/tasks/detections.ts b/x-pack/legacy/plugins/siem/cypress/tasks/detections.ts index 4a0a565a74e27..3416e3eb81de3 100644 --- a/x-pack/legacy/plugins/siem/cypress/tasks/detections.ts +++ b/x-pack/legacy/plugins/siem/cypress/tasks/detections.ts @@ -4,7 +4,31 @@ * you may not use this file except in compliance with the Elastic License. */ -import { LOADING_SIGNALS_PANEL, MANAGE_SIGNAL_DETECTION_RULES_BTN } from '../screens/detections'; +import { + CLOSED_SIGNALS_BTN, + LOADING_SIGNALS_PANEL, + MANAGE_SIGNAL_DETECTION_RULES_BTN, + OPEN_CLOSE_SIGNAL_BTN, + OPEN_CLOSE_SIGNALS_BTN, + OPENED_SIGNALS_BTN, + SIGNALS, + SIGNAL_CHECKBOX, +} from '../screens/detections'; +import { REFRESH_BUTTON } from '../screens/siem_header'; + +export const closeFirstSignal = () => { + cy.get(OPEN_CLOSE_SIGNAL_BTN) + .first() + .click({ force: true }); +}; + +export const closeSignals = () => { + cy.get(OPEN_CLOSE_SIGNALS_BTN).click({ force: true }); +}; + +export const goToClosedSignals = () => { + cy.get(CLOSED_SIGNALS_BTN).click({ force: true }); +}; export const goToManageSignalDetectionRules = () => { cy.get(MANAGE_SIGNAL_DETECTION_RULES_BTN) @@ -12,6 +36,28 @@ export const goToManageSignalDetectionRules = () => { .click({ force: true }); }; +export const goToOpenedSignals = () => { + cy.get(OPENED_SIGNALS_BTN).click({ force: true }); +}; + +export const openSignals = () => { + cy.get(OPEN_CLOSE_SIGNALS_BTN).click({ force: true }); +}; + +export const selectNumberOfSignals = (numberOfSignals: number) => { + for (let i = 0; i < numberOfSignals; i++) { + cy.get(SIGNAL_CHECKBOX) + .eq(i) + .click({ force: true }); + } +}; + +export const waitForSignals = () => { + cy.get(REFRESH_BUTTON) + .invoke('text') + .should('not.equal', 'Updating'); +}; + export const waitForSignalsIndexToBeCreated = () => { cy.request({ url: '/api/detection_engine/index', retryOnStatusCodeFailure: true }).then( response => { @@ -26,3 +72,8 @@ export const waitForSignalsPanelToBeLoaded = () => { cy.get(LOADING_SIGNALS_PANEL).should('exist'); cy.get(LOADING_SIGNALS_PANEL).should('not.exist'); }; + +export const waitForSignalsToBeLoaded = () => { + const expectedNumberOfDisplayedSignals = 25; + cy.get(SIGNALS).should('have.length', expectedNumberOfDisplayedSignals); +}; diff --git a/x-pack/legacy/plugins/siem/cypress/tasks/es_archiver.ts b/x-pack/legacy/plugins/siem/cypress/tasks/es_archiver.ts index 72c95cba2361b..1743fcb561064 100644 --- a/x-pack/legacy/plugins/siem/cypress/tasks/es_archiver.ts +++ b/x-pack/legacy/plugins/siem/cypress/tasks/es_archiver.ts @@ -12,6 +12,14 @@ export const esArchiverLoadEmptyKibana = () => { ); }; +export const esArchiverLoad = (folder: string) => { + cy.exec( + `node ../../../../scripts/es_archiver load ${folder} --dir ../../../test/siem_cypress/es_archives --config ../../../../test/functional/config.js --es-url ${Cypress.env( + 'ELASTICSEARCH_URL' + )} --kibana-url ${Cypress.config().baseUrl}` + ); +}; + export const esArchiverUnloadEmptyKibana = () => { cy.exec( `node ../../../../scripts/es_archiver empty_kibana unload empty--dir ../../../test/siem_cypress/es_archives --config ../../../../test/functional/config.js --es-url ${Cypress.env( diff --git a/x-pack/legacy/plugins/siem/index.ts b/x-pack/legacy/plugins/siem/index.ts index db398821aecfd..3773283555b32 100644 --- a/x-pack/legacy/plugins/siem/index.ts +++ b/x-pack/legacy/plugins/siem/index.ts @@ -40,7 +40,7 @@ export const siem = (kibana: any) => { id: APP_ID, configPrefix: 'xpack.siem', publicDir: resolve(__dirname, 'public'), - require: ['kibana', 'elasticsearch', 'alerting', 'actions'], + require: ['kibana', 'elasticsearch', 'alerting', 'actions', 'triggers_actions_ui'], uiExports: { app: { description: i18n.translate('xpack.siem.securityDescription', { diff --git a/x-pack/legacy/plugins/siem/public/components/utility_bar/utility_bar_action.tsx b/x-pack/legacy/plugins/siem/public/components/utility_bar/utility_bar_action.tsx index d3e2be0e8f816..19e884e326390 100644 --- a/x-pack/legacy/plugins/siem/public/components/utility_bar/utility_bar_action.tsx +++ b/x-pack/legacy/plugins/siem/public/components/utility_bar/utility_bar_action.tsx @@ -42,11 +42,23 @@ Popover.displayName = 'Popover'; export interface UtilityBarActionProps extends LinkIconProps { popoverContent?: (closePopover: () => void) => React.ReactNode; + dataTestSubj?: string; } export const UtilityBarAction = React.memo( - ({ children, color, disabled, href, iconSide, iconSize, iconType, onClick, popoverContent }) => ( - + ({ + children, + color, + dataTestSubj, + disabled, + href, + iconSide, + iconSize, + iconType, + onClick, + popoverContent, + }) => ( + {popoverContent ? ( (({ children }) => ( - {children} +export const UtilityBarText = React.memo(({ children, dataTestSubj }) => ( + {children} )); UtilityBarText.displayName = 'UtilityBarText'; diff --git a/x-pack/legacy/plugins/siem/public/containers/case/configure/api.ts b/x-pack/legacy/plugins/siem/public/containers/case/configure/api.ts new file mode 100644 index 0000000000000..a6db36d8f64e7 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/case/configure/api.ts @@ -0,0 +1,98 @@ +/* + * 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 { isEmpty } from 'lodash/fp'; +import { + CasesConnectorsFindResult, + CasesConfigurePatch, + CasesConfigureResponse, + CasesConfigureRequest, +} from '../../../../../../../plugins/case/common/api'; +import { KibanaServices } from '../../../lib/kibana'; + +import { CASES_CONFIGURE_URL } from '../constants'; +import { ApiProps } from '../types'; +import { convertToCamelCase, decodeCaseConfigureResponse } from '../utils'; +import { CaseConfigure, PatchConnectorProps } from './types'; + +export const fetchConnectors = async ({ signal }: ApiProps): Promise => { + const response = await KibanaServices.get().http.fetch( + `${CASES_CONFIGURE_URL}/connectors/_find`, + { + method: 'GET', + signal, + } + ); + + return response; +}; + +export const getCaseConfigure = async ({ signal }: ApiProps): Promise => { + const response = await KibanaServices.get().http.fetch( + CASES_CONFIGURE_URL, + { + method: 'GET', + signal, + } + ); + + return !isEmpty(response) + ? convertToCamelCase( + decodeCaseConfigureResponse(response) + ) + : null; +}; + +export const postCaseConfigure = async ( + caseConfiguration: CasesConfigureRequest, + signal: AbortSignal +): Promise => { + const response = await KibanaServices.get().http.fetch( + CASES_CONFIGURE_URL, + { + method: 'POST', + body: JSON.stringify(caseConfiguration), + signal, + } + ); + return convertToCamelCase( + decodeCaseConfigureResponse(response) + ); +}; + +export const patchCaseConfigure = async ( + caseConfiguration: CasesConfigurePatch, + signal: AbortSignal +): Promise => { + const response = await KibanaServices.get().http.fetch( + CASES_CONFIGURE_URL, + { + method: 'PATCH', + body: JSON.stringify(caseConfiguration), + signal, + } + ); + return convertToCamelCase( + decodeCaseConfigureResponse(response) + ); +}; + +export const patchConfigConnector = async ({ + connectorId, + config, + signal, +}: PatchConnectorProps): Promise => { + const response = await KibanaServices.get().http.fetch( + `${CASES_CONFIGURE_URL}/connectors/${connectorId}`, + { + method: 'PATCH', + body: JSON.stringify(config), + signal, + } + ); + + return response; +}; diff --git a/x-pack/legacy/plugins/siem/public/containers/case/configure/types.ts b/x-pack/legacy/plugins/siem/public/containers/case/configure/types.ts new file mode 100644 index 0000000000000..840828307163c --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/case/configure/types.ts @@ -0,0 +1,43 @@ +/* + * 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 { ElasticUser, ApiProps } from '../types'; +import { + ActionType, + CasesConnectorConfiguration, + CasesConfigurationMaps, + CaseField, + ClosureType, + Connector, + ThirdPartyField, +} from '../../../../../../../plugins/case/common/api'; + +export { ActionType, CasesConfigurationMaps, CaseField, ClosureType, Connector, ThirdPartyField }; + +export interface CasesConfigurationMapping { + source: CaseField; + target: ThirdPartyField; + actionType: ActionType; +} + +export interface CaseConfigure { + createdAt: string; + createdBy: ElasticUser; + connectorId: string; + closureType: ClosureType; + updatedAt: string; + updatedBy: ElasticUser; + version: string; +} + +export interface PatchConnectorProps extends ApiProps { + connectorId: string; + config: CasesConnectorConfiguration; +} + +export interface CCMapsCombinedActionAttributes extends CasesConfigurationMaps { + actionType?: ActionType; +} diff --git a/x-pack/legacy/plugins/siem/public/containers/case/configure/use_configure.tsx b/x-pack/legacy/plugins/siem/public/containers/case/configure/use_configure.tsx new file mode 100644 index 0000000000000..22ac54093d1dc --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/case/configure/use_configure.tsx @@ -0,0 +1,129 @@ +/* + * 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 { useState, useEffect, useCallback } from 'react'; +import { getCaseConfigure, patchCaseConfigure, postCaseConfigure } from './api'; + +import { useStateToaster, errorToToaster } from '../../../components/toasters'; +import * as i18n from '../translations'; +import { ClosureType } from './types'; + +interface PersistCaseConfigure { + connectorId: string; + closureType: ClosureType; +} + +export interface ReturnUseCaseConfigure { + loading: boolean; + refetchCaseConfigure: () => void; + persistCaseConfigure: ({ connectorId, closureType }: PersistCaseConfigure) => unknown; + persistLoading: boolean; +} + +interface UseCaseConfigure { + setConnectorId: (newConnectorId: string) => void; + setClosureType: (newClosureType: ClosureType) => void; +} + +export const useCaseConfigure = ({ + setConnectorId, + setClosureType, +}: UseCaseConfigure): ReturnUseCaseConfigure => { + const [, dispatchToaster] = useStateToaster(); + const [loading, setLoading] = useState(true); + const [persistLoading, setPersistLoading] = useState(false); + const [version, setVersion] = useState(''); + + const refetchCaseConfigure = useCallback(() => { + let didCancel = false; + const abortCtrl = new AbortController(); + + const fetchCaseConfiguration = async () => { + try { + setLoading(true); + const res = await getCaseConfigure({ signal: abortCtrl.signal }); + if (!didCancel) { + setLoading(false); + if (res != null) { + setConnectorId(res.connectorId); + setClosureType(res.closureType); + setVersion(res.version); + } + } + } catch (error) { + if (!didCancel) { + setLoading(false); + errorToToaster({ + title: i18n.ERROR_TITLE, + error: error.body && error.body.message ? new Error(error.body.message) : error, + dispatchToaster, + }); + } + } + }; + + fetchCaseConfiguration(); + + return () => { + didCancel = true; + abortCtrl.abort(); + }; + }, []); + + const persistCaseConfigure = useCallback( + async ({ connectorId, closureType }: PersistCaseConfigure) => { + let didCancel = false; + const abortCtrl = new AbortController(); + const saveCaseConfiguration = async () => { + try { + setPersistLoading(true); + const res = + version.length === 0 + ? await postCaseConfigure( + { connector_id: connectorId, closure_type: closureType }, + abortCtrl.signal + ) + : await patchCaseConfigure( + { connector_id: connectorId, closure_type: closureType, version }, + abortCtrl.signal + ); + if (!didCancel) { + setPersistLoading(false); + setConnectorId(res.connectorId); + setClosureType(res.closureType); + setVersion(res.version); + } + } catch (error) { + if (!didCancel) { + setPersistLoading(false); + errorToToaster({ + title: i18n.ERROR_TITLE, + error: error.body && error.body.message ? new Error(error.body.message) : error, + dispatchToaster, + }); + } + } + }; + saveCaseConfiguration(); + return () => { + didCancel = true; + abortCtrl.abort(); + }; + }, + [version] + ); + + useEffect(() => { + refetchCaseConfigure(); + }, []); + + return { + loading, + refetchCaseConfigure, + persistCaseConfigure, + persistLoading, + }; +}; diff --git a/x-pack/legacy/plugins/siem/public/containers/case/configure/use_connectors.tsx b/x-pack/legacy/plugins/siem/public/containers/case/configure/use_connectors.tsx new file mode 100644 index 0000000000000..f905ebe756d7d --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/case/configure/use_connectors.tsx @@ -0,0 +1,115 @@ +/* + * 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 { useState, useEffect, useCallback } from 'react'; + +import { useStateToaster, errorToToaster } from '../../../components/toasters'; +import * as i18n from '../translations'; +import { fetchConnectors, patchConfigConnector } from './api'; +import { CasesConfigurationMapping, Connector } from './types'; + +export interface ReturnConnectors { + loading: boolean; + connectors: Connector[]; + refetchConnectors: () => void; + updateConnector: (connectorId: string, mappings: CasesConfigurationMapping[]) => unknown; +} + +export const useConnectors = (): ReturnConnectors => { + const [, dispatchToaster] = useStateToaster(); + const [loading, setLoading] = useState(true); + const [connectors, setConnectors] = useState([]); + + const refetchConnectors = useCallback(() => { + let didCancel = false; + const abortCtrl = new AbortController(); + const getConnectors = async () => { + try { + setLoading(true); + const res = await fetchConnectors({ signal: abortCtrl.signal }); + if (!didCancel) { + setLoading(false); + setConnectors(res.data); + } + } catch (error) { + if (!didCancel) { + setLoading(false); + setConnectors([]); + errorToToaster({ + title: i18n.ERROR_TITLE, + error: error.body && error.body.message ? new Error(error.body.message) : error, + dispatchToaster, + }); + } + } + }; + getConnectors(); + return () => { + didCancel = true; + abortCtrl.abort(); + }; + }, []); + + const updateConnector = useCallback( + (connectorId: string, mappings: CasesConfigurationMapping[]) => { + if (connectorId === 'none') { + return; + } + + let didCancel = false; + const abortCtrl = new AbortController(); + const update = async () => { + try { + setLoading(true); + await patchConfigConnector({ + connectorId, + config: { + cases_configuration: { + mapping: mappings.map(m => ({ + source: m.source, + target: m.target, + action_type: m.actionType, + })), + }, + }, + signal: abortCtrl.signal, + }); + if (!didCancel) { + setLoading(false); + refetchConnectors(); + } + } catch (error) { + if (!didCancel) { + setLoading(false); + refetchConnectors(); + errorToToaster({ + title: i18n.ERROR_TITLE, + error: error.body && error.body.message ? new Error(error.body.message) : error, + dispatchToaster, + }); + } + } + }; + update(); + return () => { + didCancel = true; + abortCtrl.abort(); + }; + }, + [] + ); + + useEffect(() => { + refetchConnectors(); + }, []); + + return { + loading, + connectors, + refetchConnectors, + updateConnector, + }; +}; diff --git a/x-pack/legacy/plugins/siem/public/containers/case/constants.ts b/x-pack/legacy/plugins/siem/public/containers/case/constants.ts index a0e57faa7661f..ab8dc98db4f64 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/constants.ts +++ b/x-pack/legacy/plugins/siem/public/containers/case/constants.ts @@ -5,5 +5,6 @@ */ export const CASES_URL = `/api/cases`; +export const CASES_CONFIGURE_URL = `/api/cases/configure`; export const DEFAULT_TABLE_ACTIVE_PAGE = 1; export const DEFAULT_TABLE_LIMIT = 5; diff --git a/x-pack/legacy/plugins/siem/public/containers/case/types.ts b/x-pack/legacy/plugins/siem/public/containers/case/types.ts index 74e9515a154de..65d94865bf00c 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/types.ts +++ b/x-pack/legacy/plugins/siem/public/containers/case/types.ts @@ -11,7 +11,8 @@ export interface Comment { createdAt: string; createdBy: ElasticUser; comment: string; - updatedAt: string; + updatedAt: string | null; + updatedBy: ElasticUser | null; version: string; } @@ -25,7 +26,8 @@ export interface Case { status: string; tags: string[]; title: string; - updatedAt: string; + updatedAt: string | null; + updatedBy: ElasticUser | null; version: string; } @@ -69,3 +71,7 @@ export interface FetchCasesProps { queryParams?: QueryParams; filterOptions?: FilterOptions; } + +export interface ApiProps { + signal: AbortSignal; +} diff --git a/x-pack/legacy/plugins/siem/public/containers/case/use_get_case.tsx b/x-pack/legacy/plugins/siem/public/containers/case/use_get_case.tsx index 3436aa8908117..a179b6f546b9b 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/use_get_case.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/case/use_get_case.tsx @@ -59,7 +59,8 @@ const initialData: Case = { status: '', tags: [], title: '', - updatedAt: '', + updatedAt: null, + updatedBy: null, version: '', }; diff --git a/x-pack/legacy/plugins/siem/public/containers/case/utils.ts b/x-pack/legacy/plugins/siem/public/containers/case/utils.ts index ea297f6930fe3..8f24d5a435240 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/utils.ts +++ b/x-pack/legacy/plugins/siem/public/containers/case/utils.ts @@ -21,6 +21,8 @@ import { throwErrors, CommentResponse, CommentResponseRt, + CasesConfigureResponse, + CaseConfigureResponseRt, } from '../../../../../../plugins/case/common/api'; import { ToasterError } from '../../components/toasters'; import { AllCases, Case } from './types'; @@ -78,3 +80,9 @@ export const decodeCasesFindResponse = (respCases?: CasesFindResponse) => export const decodeCommentResponse = (respComment?: CommentResponse) => pipe(CommentResponseRt.decode(respComment), fold(throwErrors(createToasterPlainError), identity)); + +export const decodeCaseConfigureResponse = (respCase?: CasesConfigureResponse) => + pipe( + CaseConfigureResponseRt.decode(respCase), + fold(throwErrors(createToasterPlainError), identity) + ); diff --git a/x-pack/legacy/plugins/siem/public/legacy.ts b/x-pack/legacy/plugins/siem/public/legacy.ts index 49a03c93120d4..157ec54353a3e 100644 --- a/x-pack/legacy/plugins/siem/public/legacy.ts +++ b/x-pack/legacy/plugins/siem/public/legacy.ts @@ -5,11 +5,19 @@ */ import { npSetup, npStart } from 'ui/new_platform'; +import { PluginsSetup, PluginsStart } from 'ui/new_platform/new_platform'; import { PluginInitializerContext } from '../../../../../src/core/public'; import { plugin } from './'; +import { + TriggersAndActionsUIPublicPluginSetup, + TriggersAndActionsUIPublicPluginStart, +} from '../../../../plugins/triggers_actions_ui/public'; const pluginInstance = plugin({} as PluginInitializerContext); -pluginInstance.setup(npSetup.core, npSetup.plugins); -pluginInstance.start(npStart.core, npStart.plugins); +type myPluginsSetup = PluginsSetup & { triggers_actions_ui: TriggersAndActionsUIPublicPluginSetup }; +type myPluginsStart = PluginsStart & { triggers_actions_ui: TriggersAndActionsUIPublicPluginStart }; + +pluginInstance.setup(npSetup.core, npSetup.plugins as myPluginsSetup); +pluginInstance.start(npStart.core, npStart.plugins as myPluginsStart); diff --git a/x-pack/legacy/plugins/siem/public/lib/connectors/config.ts b/x-pack/legacy/plugins/siem/public/lib/connectors/config.ts new file mode 100644 index 0000000000000..baeb69b3f6943 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/lib/connectors/config.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CasesConfigurationMapping } from '../../containers/case/configure/types'; +import serviceNowLogo from './logos/servicenow.svg'; +import { Connector } from './types'; + +const connectors: Record = { + '.servicenow': { + actionTypeId: '.servicenow', + logo: serviceNowLogo, + }, +}; + +const defaultMapping: CasesConfigurationMapping[] = [ + { + source: 'title', + target: 'short_description', + actionType: 'overwrite', + }, + { + source: 'description', + target: 'description', + actionType: 'overwrite', + }, + { + source: 'comments', + target: 'comments', + actionType: 'append', + }, +]; + +export { connectors, defaultMapping }; diff --git a/x-pack/legacy/plugins/graph/public/legacy_imports.ts b/x-pack/legacy/plugins/siem/public/lib/connectors/index.ts similarity index 69% rename from x-pack/legacy/plugins/graph/public/legacy_imports.ts rename to x-pack/legacy/plugins/siem/public/lib/connectors/index.ts index 274cdc65232e2..fdf337b5ef120 100644 --- a/x-pack/legacy/plugins/graph/public/legacy_imports.ts +++ b/x-pack/legacy/plugins/siem/public/lib/connectors/index.ts @@ -4,6 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -import 'ace'; - -export { configureAppAngularModule } from '../../../../../src/plugins/kibana_legacy/public'; +export { getActionType as serviceNowActionType } from './servicenow'; diff --git a/x-pack/legacy/plugins/siem/public/lib/connectors/logos/servicenow.svg b/x-pack/legacy/plugins/siem/public/lib/connectors/logos/servicenow.svg new file mode 100755 index 0000000000000..dcd022a8dca18 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/lib/connectors/logos/servicenow.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/x-pack/legacy/plugins/siem/public/lib/connectors/servicenow.tsx b/x-pack/legacy/plugins/siem/public/lib/connectors/servicenow.tsx new file mode 100644 index 0000000000000..877757df30fb3 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/lib/connectors/servicenow.tsx @@ -0,0 +1,225 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React, { useCallback, ChangeEvent } from 'react'; +import { + EuiFieldText, + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiFieldPassword, + EuiSpacer, +} from '@elastic/eui'; + +import { isEmpty, get } from 'lodash/fp'; + +import { + ActionConnectorFieldsProps, + ActionTypeModel, + ValidationResult, + ActionParamsProps, + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from '../../../../../../plugins/triggers_actions_ui/public/types'; + +import { FieldMapping } from '../../pages/case/components/configure_cases/field_mapping'; + +import * as i18n from './translations'; + +import { ServiceNowActionConnector } from './types'; +import { isUrlInvalid } from './validators'; + +import { connectors, defaultMapping } from './config'; +import { CasesConfigurationMapping } from '../../containers/case/configure/types'; + +const serviceNowDefinition = connectors['.servicenow']; + +interface ServiceNowActionParams { + message: string; +} + +interface Errors { + apiUrl: string[]; + username: string[]; + password: string[]; +} + +export function getActionType(): ActionTypeModel { + return { + id: serviceNowDefinition.actionTypeId, + iconClass: serviceNowDefinition.logo, + selectMessage: i18n.SERVICENOW_DESC, + actionTypeTitle: i18n.SERVICENOW_TITLE, + validateConnector: (action: ServiceNowActionConnector): ValidationResult => { + const errors: Errors = { + apiUrl: [], + username: [], + password: [], + }; + + if (!action.config.apiUrl) { + errors.apiUrl = [...errors.apiUrl, i18n.SERVICENOW_API_URL_REQUIRED]; + } + + if (isUrlInvalid(action.config.apiUrl)) { + errors.apiUrl = [...errors.apiUrl, i18n.SERVICENOW_API_URL_INVALID]; + } + + if (!action.secrets.username) { + errors.username = [...errors.username, i18n.SERVICENOW_USERNAME_REQUIRED]; + } + + if (!action.secrets.password) { + errors.password = [...errors.password, i18n.SERVICENOW_PASSWORD_REQUIRED]; + } + + return { errors }; + }, + validateParams: (actionParams: ServiceNowActionParams): ValidationResult => { + return { errors: {} }; + }, + actionConnectorFields: ServiceNowConnectorFields, + actionParamsFields: ServiceNowParamsFields, + }; +} + +const ServiceNowConnectorFields: React.FunctionComponent> = ({ action, editActionConfig, editActionSecrets, errors }) => { + const { apiUrl, casesConfiguration: { mapping = [] } = {} } = action.config; + const { username, password } = action.secrets; + + const isApiUrlInvalid: boolean = errors.apiUrl.length > 0 && apiUrl != null; + const isUsernameInvalid: boolean = errors.username.length > 0 && username != null; + const isPasswordInvalid: boolean = errors.password.length > 0 && password != null; + + if (isEmpty(mapping)) { + editActionConfig('casesConfiguration', { + ...action.config.casesConfiguration, + mapping: defaultMapping, + }); + } + + const handleOnChangeActionConfig = useCallback( + (key: string, evt: ChangeEvent) => editActionConfig(key, evt.target.value), + [] + ); + + const handleOnBlurActionConfig = useCallback( + (key: string) => { + if (key === 'apiUrl' && action.config[key] == null) { + editActionConfig(key, ''); + } + }, + [action.config] + ); + + const handleOnChangeSecretConfig = useCallback( + (key: string, evt: ChangeEvent) => editActionSecrets(key, evt.target.value), + [] + ); + + const handleOnBlurSecretConfig = useCallback( + (key: string) => { + if (['username', 'password'].includes(key) && get(key, action.secrets) == null) { + editActionSecrets(key, ''); + } + }, + [action.secrets] + ); + + const handleOnChangeMappingConfig = useCallback( + (newMapping: CasesConfigurationMapping[]) => + editActionConfig('casesConfiguration', { + ...action.config.casesConfiguration, + mapping: newMapping, + }), + [action.config] + ); + + return ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +const ServiceNowParamsFields: React.FunctionComponent> = ({ actionParams, editAction, index, errors }) => { + return null; +}; diff --git a/x-pack/legacy/plugins/siem/public/lib/connectors/translations.ts b/x-pack/legacy/plugins/siem/public/lib/connectors/translations.ts new file mode 100644 index 0000000000000..ae2084120255c --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/lib/connectors/translations.ts @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const SERVICENOW_DESC = i18n.translate( + 'xpack.siem.case.connectors.servicenow.selectMessageText', + { + defaultMessage: 'Push or update SIEM case data to a new incident in ServiceNow', + } +); + +export const SERVICENOW_TITLE = i18n.translate( + 'xpack.siem.case.connectors.servicenow.actionTypeTitle', + { + defaultMessage: 'ServiceNow', + } +); + +export const SERVICENOW_API_URL_LABEL = i18n.translate( + 'xpack.siem.case.connectors.servicenow.apiUrlTextFieldLabel', + { + defaultMessage: 'URL', + } +); + +export const SERVICENOW_API_URL_REQUIRED = i18n.translate( + 'xpack.siem.case.connectors.servicenow.requiredApiUrlTextField', + { + defaultMessage: 'URL is required', + } +); + +export const SERVICENOW_API_URL_INVALID = i18n.translate( + 'xpack.siem.case.connectors.servicenow.invalidApiUrlTextField', + { + defaultMessage: 'URL is invalid', + } +); + +export const SERVICENOW_USERNAME_LABEL = i18n.translate( + 'xpack.siem.case.connectors.servicenow.usernameTextFieldLabel', + { + defaultMessage: 'Username', + } +); + +export const SERVICENOW_USERNAME_REQUIRED = i18n.translate( + 'xpack.siem.case.connectors.servicenow.requiredUsernameTextField', + { + defaultMessage: 'Username is required', + } +); + +export const SERVICENOW_PASSWORD_LABEL = i18n.translate( + 'xpack.siem.case.connectors.servicenow.passwordTextFieldLabel', + { + defaultMessage: 'Password', + } +); + +export const SERVICENOW_PASSWORD_REQUIRED = i18n.translate( + 'xpack.siem.case.connectors.servicenow.requiredPasswordTextField', + { + defaultMessage: 'Password is required', + } +); diff --git a/x-pack/legacy/plugins/siem/public/lib/connectors/types.ts b/x-pack/legacy/plugins/siem/public/lib/connectors/types.ts new file mode 100644 index 0000000000000..66326a6590deb --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/lib/connectors/types.ts @@ -0,0 +1,23 @@ +/* + * 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. + */ + +/* eslint-disable no-restricted-imports */ +/* eslint-disable @kbn/eslint/no-restricted-paths */ + +import { + ConfigType, + SecretsType, +} from '../../../../../../plugins/actions/server/builtin_action_types/servicenow/types'; + +export interface ServiceNowActionConnector { + config: ConfigType; + secrets: SecretsType; +} + +export interface Connector { + actionTypeId: string; + logo: string; +} diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/empty_state/index.ts b/x-pack/legacy/plugins/siem/public/lib/connectors/validators.ts similarity index 81% rename from x-pack/legacy/plugins/uptime/public/components/functional/empty_state/index.ts rename to x-pack/legacy/plugins/siem/public/lib/connectors/validators.ts index 8ee70bf51f006..2989cf4d98f85 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/empty_state/index.ts +++ b/x-pack/legacy/plugins/siem/public/lib/connectors/validators.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { EmptyState } from './empty_state'; +export { isUrlInvalid } from '../../utils/validators'; diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/__mock__/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/__mock__/index.tsx index 433e1cb17da02..0fe8daafcb30a 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/__mock__/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/__mock__/index.tsx @@ -22,7 +22,8 @@ export const useGetCasesMockState: UseGetCasesState = { status: 'open', tags: ['defacement'], title: 'Another horrible breach', - updatedAt: '2020-02-13T19:44:23.627Z', + updatedAt: null, + updatedBy: null, version: 'WzQ3LDFd', }, { @@ -35,7 +36,8 @@ export const useGetCasesMockState: UseGetCasesState = { status: 'open', tags: ['phishing'], title: 'Bad email', - updatedAt: '2020-02-13T19:44:13.328Z', + updatedAt: null, + updatedBy: null, version: 'WzQ3LDFd', }, { @@ -48,7 +50,8 @@ export const useGetCasesMockState: UseGetCasesState = { status: 'open', tags: ['phishing'], title: 'Bad email', - updatedAt: '2020-02-13T19:44:11.328Z', + updatedAt: null, + updatedBy: null, version: 'WzQ3LDFd', }, { @@ -61,7 +64,8 @@ export const useGetCasesMockState: UseGetCasesState = { status: 'closed', tags: ['phishing'], title: 'Uh oh', - updatedAt: '2020-02-18T21:32:24.056Z', + updatedAt: null, + updatedBy: null, version: 'WzQ3LDFd', }, { @@ -74,7 +78,8 @@ export const useGetCasesMockState: UseGetCasesState = { status: 'open', tags: ['phishing'], title: 'Uh oh', - updatedAt: '2020-02-13T19:44:01.901Z', + updatedAt: null, + updatedBy: null, version: 'WzQ3LDFd', }, ], diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.tsx index 1349246494ec8..7b655999ace09 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.tsx @@ -8,7 +8,6 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { EuiBasicTable, EuiButton, - EuiButtonIcon, EuiContextMenuPanel, EuiEmptyPrompt, EuiFlexGroup, @@ -45,6 +44,9 @@ import { OpenClosedStats } from '../open_closed_stats'; import { getActions } from './actions'; import { CasesTableFilters } from './table_filters'; +const CONFIGURE_CASES_URL = getConfigureCasesUrl(); +const CREATE_CASE_URL = getCreateCaseUrl(); + const Div = styled.div` margin-top: ${({ theme }) => theme.eui.paddingSizes.m}; `; @@ -259,16 +261,14 @@ export const AllCases = React.memo(() => { /> - - {i18n.CREATE_TITLE} + + {i18n.CONFIGURE_CASES_BUTTON} - + + {i18n.CREATE_TITLE} + @@ -325,7 +325,7 @@ export const AllCases = React.memo(() => { titleSize="xs" body={i18n.NO_CASES_BODY} actions={ - + {i18n.ADD_NEW_CASE} } diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/__mock__/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/__mock__/index.tsx index 6e5ef46af41c4..53cc1f80b5c10 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/__mock__/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/__mock__/index.tsx @@ -22,6 +22,9 @@ export const caseProps: CaseProps = { username: 'smilovic', }, updatedAt: '2020-02-20T23:06:33.798Z', + updatedBy: { + username: 'elastic', + }, version: 'WzQ3LDFd', }, ], @@ -32,6 +35,9 @@ export const caseProps: CaseProps = { tags: ['defacement'], title: 'Another horrible breach!!', updatedAt: '2020-02-19T15:02:57.995Z', + updatedBy: { + username: 'elastic', + }, version: 'WzQ3LDFd', }, }; @@ -49,6 +55,9 @@ export const data: Case = { username: 'smilovic', }, updatedAt: '2020-02-20T23:06:33.798Z', + updatedBy: { + username: 'elastic', + }, version: 'WzQ3LDFd', }, ], @@ -59,5 +68,8 @@ export const data: Case = { tags: ['defacement'], title: 'Another horrible breach!!', updatedAt: '2020-02-19T15:02:57.995Z', + updatedBy: { + username: 'elastic', + }, version: 'WzQ3LDFd', }; diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options.tsx index 3a2ef3bc21721..9879b9149059a 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options.tsx @@ -7,10 +7,21 @@ import React from 'react'; import { EuiDescribedFormGroup, EuiFormRow } from '@elastic/eui'; -import * as i18n from './translations'; +import { ClosureType } from '../../../../containers/case/configure/types'; import { ClosureOptionsRadio } from './closure_options_radio'; +import * as i18n from './translations'; + +interface ClosureOptionsProps { + closureTypeSelected: ClosureType; + disabled: boolean; + onChangeClosureType: (newClosureType: ClosureType) => void; +} -const ClosureOptionsComponent: React.FC = () => { +const ClosureOptionsComponent: React.FC = ({ + closureTypeSelected, + disabled, + onChangeClosureType, +}) => { return ( { description={i18n.CASE_CLOSURE_OPTIONS_DESC} > - + ); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options_radio.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options_radio.tsx index 5d1476acee5b1..f32f867b2471d 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options_radio.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options_radio.tsx @@ -4,37 +4,52 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useState } from 'react'; +import React, { ReactNode, useCallback } from 'react'; import { EuiRadioGroup } from '@elastic/eui'; +import { ClosureType } from '../../../../containers/case/configure/types'; import * as i18n from './translations'; -const ID_PREFIX = 'closure_options'; -const DEFAULT_RADIO = `${ID_PREFIX}_manual`; +interface ClosureRadios { + id: ClosureType; + label: ReactNode; +} -const radios = [ +const radios: ClosureRadios[] = [ { - id: DEFAULT_RADIO, + id: 'close-by-user', label: i18n.CASE_CLOSURE_OPTIONS_MANUAL, }, { - id: `${ID_PREFIX}_new_incident`, + id: 'close-by-pushing', label: i18n.CASE_CLOSURE_OPTIONS_NEW_INCIDENT, }, - { - id: `${ID_PREFIX}_closed_incident`, - label: i18n.CASE_CLOSURE_OPTIONS_CLOSED_INCIDENT, - }, ]; -const ClosureOptionsRadioComponent: React.FC = () => { - const [selectedClosure, setSelectedClosure] = useState(DEFAULT_RADIO); +interface ClosureOptionsRadioComponentProps { + closureTypeSelected: ClosureType; + disabled: boolean; + onChangeClosureType: (newClosureType: ClosureType) => void; +} + +const ClosureOptionsRadioComponent: React.FC = ({ + closureTypeSelected, + disabled, + onChangeClosureType, +}) => { + const onChangeLocal = useCallback( + (id: string) => { + onChangeClosureType(id as ClosureType); + }, + [onChangeClosureType] + ); return ( ); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors.tsx index 561464e44c703..55b256b66b72b 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { useState, useCallback } from 'react'; import { EuiDescribedFormGroup, EuiFormRow, @@ -18,6 +18,13 @@ import styled from 'styled-components'; import { ConnectorsDropdown } from './connectors_dropdown'; import * as i18n from './translations'; +import { + ActionsConnectorsContextProvider, + ConnectorAddFlyout, +} from '../../../../../../../../plugins/triggers_actions_ui/public'; +import { Connector } from '../../../../containers/case/configure/types'; +import { useKibana } from '../../../../lib/kibana'; + const EuiFormRowExtended = styled(EuiFormRow)` .euiFormRow__labelWrapper { .euiFormRow__label { @@ -26,26 +33,79 @@ const EuiFormRowExtended = styled(EuiFormRow)` } `; -const ConnectorsComponent: React.FC = () => { +interface Props { + connectors: Connector[]; + disabled: boolean; + isLoading: boolean; + onChangeConnector: (id: string) => void; + refetchConnectors: () => void; + selectedConnector: string; +} +const actionTypes = [ + { + id: '.servicenow', + name: 'ServiceNow', + enabled: true, + }, +]; + +const ConnectorsComponent: React.FC = ({ + connectors, + disabled, + isLoading, + onChangeConnector, + refetchConnectors, + selectedConnector, +}) => { + const { http, triggers_actions_ui, notifications, application } = useKibana().services; + const [addFlyoutVisible, setAddFlyoutVisibility] = useState(false); + + const handleShowFlyout = useCallback(() => setAddFlyoutVisibility(true), []); + const dropDownLabel = ( {i18n.INCIDENT_MANAGEMENT_SYSTEM_LABEL} - {i18n.ADD_NEW_CONNECTOR} + {i18n.ADD_NEW_CONNECTOR} ); + const reloadConnectors = useCallback(async () => refetchConnectors(), []); + return ( - {i18n.INCIDENT_MANAGEMENT_SYSTEM_TITLE}} - description={i18n.INCIDENT_MANAGEMENT_SYSTEM_DESC} - > - - - - + <> + {i18n.INCIDENT_MANAGEMENT_SYSTEM_TITLE}} + description={i18n.INCIDENT_MANAGEMENT_SYSTEM_DESC} + > + + + + + + + + ); }; diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors_dropdown.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors_dropdown.tsx index d43935deda395..a0a0ad6cd3e7f 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors_dropdown.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors_dropdown.tsx @@ -4,50 +4,78 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useState } from 'react'; -import { EuiSuperSelect, EuiIcon, EuiSuperSelectOption } from '@elastic/eui'; +import React, { useMemo } from 'react'; +import { EuiIcon, EuiSuperSelect } from '@elastic/eui'; import styled from 'styled-components'; +import { Connector } from '../../../../containers/case/configure/types'; +import { connectors as connectorsDefinition } from '../../../../lib/connectors/config'; import * as i18n from './translations'; +interface Props { + connectors: Connector[]; + disabled: boolean; + isLoading: boolean; + onChange: (id: string) => void; + selectedConnector: string; +} + const ICON_SIZE = 'm'; const EuiIconExtended = styled(EuiIcon)` margin-right: 13px; `; -const connectors: Array> = [ - { - value: 'no-connector', - inputDisplay: ( - <> - - {i18n.NO_CONNECTOR} - - ), - 'data-test-subj': 'no-connector', - }, - { - value: 'servicenow-connector', - inputDisplay: ( - <> - - {'My ServiceNow connector'} - - ), - 'data-test-subj': 'servicenow-connector', - }, -]; - -const ConnectorsDropdownComponent: React.FC = () => { - const [selectedConnector, setSelectedConnector] = useState(connectors[0].value); +const noConnectorOption = { + value: 'none', + inputDisplay: ( + <> + + {i18n.NO_CONNECTOR} + + ), + 'data-test-subj': 'no-connector', +}; + +const ConnectorsDropdownComponent: React.FC = ({ + connectors, + disabled, + isLoading, + onChange, + selectedConnector, +}) => { + const connectorsAsOptions = useMemo( + () => + connectors.reduce( + (acc, connector) => [ + ...acc, + { + value: connector.id, + inputDisplay: ( + <> + + {connector.name} + + ), + 'data-test-subj': connector.id, + }, + ], + [noConnectorOption] + ), + [connectors] + ); return ( ); }; diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping.tsx index 814f1bfd75ae4..0c0dc14f1c218 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping.tsx @@ -4,63 +4,118 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; -import { EuiDescribedFormGroup, EuiFormRow, EuiFlexItem, EuiFlexGroup } from '@elastic/eui'; +import React, { useCallback } from 'react'; +import { EuiFormRow, EuiFlexItem, EuiFlexGroup, EuiSuperSelectOption } from '@elastic/eui'; import styled from 'styled-components'; -import * as i18n from './translations'; +import { + CasesConfigurationMapping, + ThirdPartyField, + CaseField, + ActionType, +} from '../../../../containers/case/configure/types'; import { FieldMappingRow } from './field_mapping_row'; +import * as i18n from './translations'; + +import { defaultMapping } from '../../../../lib/connectors/config'; const FieldRowWrapper = styled.div` margin-top: 8px; font-size: 14px; `; -const supportedThirdPartyFields = [ +const supportedThirdPartyFields: Array> = [ { - value: 'short_description', - inputDisplay: {'Short Description'}, + value: 'not_mapped', + inputDisplay: {i18n.FIELD_MAPPING_FIELD_NOT_MAPPED}, }, { - value: 'comment', - inputDisplay: {'Comment'}, + value: 'short_description', + inputDisplay: {i18n.FIELD_MAPPING_FIELD_SHORT_DESC}, }, { - value: 'tags', - inputDisplay: {'Tags'}, + value: 'comments', + inputDisplay: {i18n.FIELD_MAPPING_FIELD_COMMENTS}, }, { value: 'description', - inputDisplay: {'Description'}, + inputDisplay: {i18n.FIELD_MAPPING_FIELD_DESC}, }, ]; -const FieldMappingComponent: React.FC = () => ( - {i18n.FIELD_MAPPING_TITLE}} - description={i18n.FIELD_MAPPING_DESC} - > - - - - {i18n.FIELD_MAPPING_FIRST_COL} - - - {i18n.FIELD_MAPPING_SECOND_COL} - - - {i18n.FIELD_MAPPING_THIRD_COL} - - - - - - - - - - -); +interface FieldMappingProps { + disabled: boolean; + mapping: CasesConfigurationMapping[] | null; + onChangeMapping: (newMapping: CasesConfigurationMapping[]) => void; +} + +const FieldMappingComponent: React.FC = ({ + disabled, + mapping, + onChangeMapping, +}) => { + const onChangeActionType = useCallback( + (caseField: CaseField, newActionType: ActionType) => { + const myMapping = mapping ?? defaultMapping; + const findItemIndex = myMapping.findIndex(item => item.source === caseField); + if (findItemIndex >= 0) { + onChangeMapping([ + ...myMapping.slice(0, findItemIndex), + { ...myMapping[findItemIndex], actionType: newActionType }, + ...myMapping.slice(findItemIndex + 1), + ]); + } + }, + [mapping] + ); + + const onChangeThirdParty = useCallback( + (caseField: CaseField, newThirdPartyField: ThirdPartyField) => { + const myMapping = mapping ?? defaultMapping; + onChangeMapping( + myMapping.map(item => { + if (item.source !== caseField && item.target === newThirdPartyField) { + return { ...item, target: 'not_mapped' }; + } else if (item.source === caseField) { + return { ...item, target: newThirdPartyField }; + } + return item; + }) + ); + }, + [mapping] + ); + return ( + <> + + + + {i18n.FIELD_MAPPING_FIRST_COL} + + + {i18n.FIELD_MAPPING_SECOND_COL} + + + {i18n.FIELD_MAPPING_THIRD_COL} + + + + + {(mapping ?? defaultMapping).map(item => ( + + ))} + + + ); +}; export const FieldMapping = React.memo(FieldMappingComponent); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping_row.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping_row.tsx index 0e446ad9bbe89..62e43c86af8d9 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping_row.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping_row.tsx @@ -4,48 +4,67 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useState } from 'react'; -import { EuiFlexItem, EuiFlexGroup, EuiSuperSelect, EuiIcon } from '@elastic/eui'; +import React, { useMemo } from 'react'; +import { + EuiFlexItem, + EuiFlexGroup, + EuiSuperSelect, + EuiIcon, + EuiSuperSelectOption, +} from '@elastic/eui'; +import { capitalize } from 'lodash/fp'; import * as i18n from './translations'; +import { + CaseField, + ActionType, + ThirdPartyField, +} from '../../../../containers/case/configure/types'; -interface ThirdPartyField { - value: string; - inputDisplay: JSX.Element; -} interface RowProps { - siemField: string; - thirdPartyOptions: ThirdPartyField[]; + disabled: boolean; + siemField: CaseField; + thirdPartyOptions: Array>; + onChangeActionType: (caseField: CaseField, newActionType: ActionType) => void; + onChangeThirdParty: (caseField: CaseField, newThirdPartyField: ThirdPartyField) => void; + selectedActionType: ActionType; + selectedThirdParty: ThirdPartyField; } -const editUpdateOptions = [ +const actionTypeOptions: Array> = [ { value: 'nothing', - inputDisplay: {i18n.FIELD_MAPPING_EDIT_NOTHING}, + inputDisplay: <>{i18n.FIELD_MAPPING_EDIT_NOTHING}, 'data-test-subj': 'edit-update-option-nothing', }, { value: 'overwrite', - inputDisplay: {i18n.FIELD_MAPPING_EDIT_OVERWRITE}, + inputDisplay: <>{i18n.FIELD_MAPPING_EDIT_OVERWRITE}, 'data-test-subj': 'edit-update-option-overwrite', }, { value: 'append', - inputDisplay: {i18n.FIELD_MAPPING_EDIT_APPEND}, + inputDisplay: <>{i18n.FIELD_MAPPING_EDIT_APPEND}, 'data-test-subj': 'edit-update-option-append', }, ]; -const FieldMappingRowComponent: React.FC = ({ siemField, thirdPartyOptions }) => { - const [selectedEditUpdate, setSelectedEditUpdate] = useState(editUpdateOptions[0].value); - const [selectedThirdParty, setSelectedThirdParty] = useState(thirdPartyOptions[0].value); - +const FieldMappingRowComponent: React.FC = ({ + disabled, + siemField, + thirdPartyOptions, + onChangeActionType, + onChangeThirdParty, + selectedActionType, + selectedThirdParty, +}) => { + const siemFieldCapitalized = useMemo(() => capitalize(siemField), [siemField]); return ( - {siemField} + {siemFieldCapitalized} @@ -54,16 +73,18 @@ const FieldMappingRowComponent: React.FC = ({ siemField, thirdPartyOpt diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/index.tsx new file mode 100644 index 0000000000000..da715fb66953f --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/index.tsx @@ -0,0 +1,201 @@ +/* + * 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, { useReducer, useCallback, useEffect, useState } from 'react'; +import styled, { css } from 'styled-components'; + +import { EuiFlexGroup, EuiFlexItem, EuiButton, EuiSpacer, EuiCallOut } from '@elastic/eui'; +import { noop, isEmpty } from 'lodash/fp'; +import { useConnectors } from '../../../../containers/case/configure/use_connectors'; +import { useCaseConfigure } from '../../../../containers/case/configure/use_configure'; +import { + ClosureType, + CasesConfigurationMapping, + CCMapsCombinedActionAttributes, +} from '../../../../containers/case/configure/types'; +import { Connectors } from '../configure_cases/connectors'; +import { ClosureOptions } from '../configure_cases/closure_options'; +import { Mapping } from '../configure_cases/mapping'; +import { SectionWrapper } from '../wrappers'; +import { configureCasesReducer, State } from './reducer'; +import * as i18n from './translations'; + +const FormWrapper = styled.div` + ${({ theme }) => css` + & > * { + margin-top 40px; + } + + padding-top: ${theme.eui.paddingSizes.l}; + padding-bottom: ${theme.eui.paddingSizes.l}; + `} +`; + +const initialState: State = { + connectorId: 'none', + closureType: 'close-by-user', + mapping: null, +}; + +const ConfigureCasesComponent: React.FC = () => { + const [connectorIsValid, setConnectorIsValid] = useState(true); + + const [{ connectorId, closureType, mapping }, dispatch] = useReducer( + configureCasesReducer(), + initialState + ); + + const setConnectorId = useCallback((newConnectorId: string) => { + dispatch({ + type: 'setConnectorId', + connectorId: newConnectorId, + }); + }, []); + + const setClosureType = useCallback((newClosureType: ClosureType) => { + dispatch({ + type: 'setClosureType', + closureType: newClosureType, + }); + }, []); + + const setMapping = useCallback((newMapping: CasesConfigurationMapping[]) => { + dispatch({ + type: 'setMapping', + mapping: newMapping, + }); + }, []); + + const { loading: loadingCaseConfigure, persistLoading, persistCaseConfigure } = useCaseConfigure({ + setConnectorId, + setClosureType, + }); + const { + loading: isLoadingConnectors, + connectors, + refetchConnectors, + updateConnector, + } = useConnectors(); + + const isLoadingAny = isLoadingConnectors || persistLoading || loadingCaseConfigure; + + const handleSubmit = useCallback( + // TO DO give a warning/error to user when field are not mapped so they have chance to do it + () => { + persistCaseConfigure({ connectorId, closureType }); + updateConnector(connectorId, mapping ?? []); + }, + [connectorId, closureType, mapping] + ); + + useEffect(() => { + if ( + !isEmpty(connectors) && + connectorId !== 'none' && + connectors.some(c => c.id === connectorId) + ) { + const myConnector = connectors.find(c => c.id === connectorId); + const myMapping = myConnector?.config?.casesConfiguration?.mapping ?? []; + setMapping( + myMapping.map((m: CCMapsCombinedActionAttributes) => ({ + source: m.source, + target: m.target, + actionType: m.action_type ?? m.actionType, + })) + ); + } + }, [connectors, connectorId]); + + useEffect(() => { + if ( + !isLoadingConnectors && + connectorId !== 'none' && + !connectors.some(c => c.id === connectorId) + ) { + setConnectorIsValid(false); + } else if ( + !isLoadingConnectors && + (connectorId === 'none' || connectors.some(c => c.id === connectorId)) + ) { + setConnectorIsValid(true); + } + }, [connectors, connectorId]); + + return ( + + {!connectorIsValid && ( + + + {i18n.WARNING_NO_CONNECTOR_MESSAGE} + + + )} + + + + + + + + + + + + + + + {i18n.CANCEL} + + + + + {i18n.SAVE_CHANGES} + + + + + + ); +}; + +export const ConfigureCases = React.memo(ConfigureCasesComponent); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/mapping.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/mapping.tsx new file mode 100644 index 0000000000000..10c8f6b938023 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/mapping.tsx @@ -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 React from 'react'; +import { EuiDescribedFormGroup } from '@elastic/eui'; + +import * as i18n from './translations'; + +import { FieldMapping } from './field_mapping'; +import { CasesConfigurationMapping } from '../../../../containers/case/configure/types'; + +interface MappingProps { + disabled: boolean; + mapping: CasesConfigurationMapping[] | null; + onChangeMapping: (newMapping: CasesConfigurationMapping[]) => void; +} + +const MappingComponent: React.FC = ({ disabled, mapping, onChangeMapping }) => ( + {i18n.FIELD_MAPPING_TITLE}} + description={i18n.FIELD_MAPPING_DESC} + > + + +); + +export const Mapping = React.memo(MappingComponent); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/reducer.ts b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/reducer.ts new file mode 100644 index 0000000000000..f9e4a73b3c396 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/reducer.ts @@ -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 { + ClosureType, + CasesConfigurationMapping, +} from '../../../../containers/case/configure/types'; + +export interface State { + mapping: CasesConfigurationMapping[] | null; + connectorId: string; + closureType: ClosureType; +} + +export type Action = + | { + type: 'setConnectorId'; + connectorId: string; + } + | { + type: 'setClosureType'; + closureType: ClosureType; + } + | { + type: 'setMapping'; + mapping: CasesConfigurationMapping[]; + }; + +export const configureCasesReducer = () => (state: State, action: Action) => { + switch (action.type) { + case 'setConnectorId': { + return { + ...state, + connectorId: action.connectorId, + }; + } + case 'setClosureType': { + return { + ...state, + closureType: action.closureType, + }; + } + case 'setMapping': { + return { + ...state, + mapping: action.mapping, + }; + } + default: + return state; + } +}; diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/translations.ts b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/translations.ts index ca2d878c58ee3..d24921a636082 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/translations.ts @@ -135,3 +135,54 @@ export const FIELD_MAPPING_EDIT_APPEND = i18n.translate( defaultMessage: 'Append', } ); + +export const CANCEL = i18n.translate('xpack.siem.case.configureCases.cancelButton', { + defaultMessage: 'Cancel', +}); + +export const SAVE_CHANGES = i18n.translate('xpack.siem.case.configureCases.saveChangesButton', { + defaultMessage: 'Save Changes', +}); + +export const WARNING_NO_CONNECTOR_TITLE = i18n.translate( + 'xpack.siem.case.configureCases.warningTitle', + { + defaultMessage: 'Warning', + } +); + +export const WARNING_NO_CONNECTOR_MESSAGE = i18n.translate( + 'xpack.siem.case.configureCases.warningMessage', + { + defaultMessage: + 'Configuration seems to be invalid. The selected connector is missing. Did you delete the connector?', + } +); + +export const FIELD_MAPPING_FIELD_NOT_MAPPED = i18n.translate( + 'xpack.siem.case.configureCases.fieldMappingFieldNotMapped', + { + defaultMessage: 'Not mapped', + } +); + +export const FIELD_MAPPING_FIELD_SHORT_DESC = i18n.translate( + 'xpack.siem.case.configureCases.fieldMappingFieldShortDescription', + { + defaultMessage: 'Short Description', + } +); + +export const FIELD_MAPPING_FIELD_DESC = i18n.translate( + 'xpack.siem.case.configureCases.fieldMappingFieldDescription', + { + defaultMessage: 'Description', + } +); + +export const FIELD_MAPPING_FIELD_COMMENTS = i18n.translate( + 'xpack.siem.case.configureCases.fieldMappingFieldComments', + { + defaultMessage: 'Comments', + } +); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/configure_cases.tsx b/x-pack/legacy/plugins/siem/public/pages/case/configure_cases.tsx index 556d7779c664f..b546a88744439 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/configure_cases.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/configure_cases.tsx @@ -5,17 +5,14 @@ */ import React from 'react'; -import styled, { css } from 'styled-components'; import { WrapperPage } from '../../components/wrapper_page'; import { CaseHeaderPage } from './components/case_header_page'; import { SpyRoute } from '../../utils/route/spy_routes'; import { getCaseUrl } from '../../components/link_to'; import { WhitePageWrapper, SectionWrapper } from './components/wrappers'; -import { Connectors } from './components/configure_cases/connectors'; import * as i18n from './translations'; -import { ClosureOptions } from './components/configure_cases/closure_options'; -import { FieldMapping } from './components/configure_cases/field_mapping'; +import { ConfigureCases } from './components/configure_cases'; const backOptions = { href: getCaseUrl(), @@ -28,17 +25,6 @@ const wrapperPageStyle: Record = { paddingBottom: '0', }; -const FormWrapper = styled.div` - ${({ theme }) => css` - & > * { - margin-top 40px; - } - - padding-top: ${theme.eui.paddingSizes.l}; - padding-bottom: ${theme.eui.paddingSizes.l}; - `} -`; - const ConfigureCasesPageComponent: React.FC = () => ( <> @@ -46,17 +32,7 @@ const ConfigureCasesPageComponent: React.FC = () => ( - - - - - - - - - - - + diff --git a/x-pack/legacy/plugins/siem/public/pages/case/translations.ts b/x-pack/legacy/plugins/siem/public/pages/case/translations.ts index 9c0287a56ccbc..6ef412d408ae5 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/case/translations.ts @@ -120,7 +120,7 @@ export const CONFIGURE_CASES_PAGE_TITLE = i18n.translate( ); export const CONFIGURE_CASES_BUTTON = i18n.translate('xpack.siem.case.configureCasesButton', { - defaultMessage: 'Configure cases', + defaultMessage: 'Edit third-party connection', }); export const ADD_COMMENT = i18n.translate('xpack.siem.case.caseView.comment.addComment', { diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/signals_filter_group/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/signals_filter_group/index.tsx index 3c1317d463f8e..a8dd22863e3c9 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/signals_filter_group/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/signals_filter_group/index.tsx @@ -32,6 +32,7 @@ const SignalsTableFilterGroupComponent: React.FC = ({ onFilterGroupChange return ( = ({ onFilterGroupChange diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/signals_utility_bar/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/signals_utility_bar/index.tsx index 25c0424cadf11..2000a699ab18d 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/signals_utility_bar/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/signals_utility_bar/index.tsx @@ -65,13 +65,15 @@ const SignalsUtilityBarComponent: React.FC = ({ - {i18n.SHOWING_SIGNALS(formattedTotalCount, totalCount)} + + {i18n.SHOWING_SIGNALS(formattedTotalCount, totalCount)} + {canUserCRUD && hasIndexWrite && ( <> - + {i18n.SELECTED_SIGNALS( showClearSelection ? formattedTotalCount : formattedSelectedEventsCount, showClearSelection ? totalCount : Object.keys(selectedEventIds).length @@ -79,6 +81,7 @@ const SignalsUtilityBarComponent: React.FC = ({ { const [coreStart, startPlugins] = await core.getStartServices(); const { renderApp } = await import('./app'); + plugins.triggers_actions_ui.actionTypeRegistry.register(serviceNowActionType()); + return renderApp(coreStart, startPlugins as StartPlugins, params); }, }); diff --git a/x-pack/legacy/plugins/siem/public/utils/validators/index.ts b/x-pack/legacy/plugins/siem/public/utils/validators/index.ts new file mode 100644 index 0000000000000..99b01c8b22974 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/utils/validators/index.ts @@ -0,0 +1,16 @@ +/* + * 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 { isEmpty } from 'lodash/fp'; + +const urlExpression = /(https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,})/gi; + +export const isUrlInvalid = (url: string | null | undefined) => { + if (!isEmpty(url) && url != null && url.match(urlExpression) == null) { + return true; + } + return false; +}; diff --git a/x-pack/legacy/plugins/siem/reporter_config.json b/x-pack/legacy/plugins/siem/reporter_config.json index ff19c242ad8dc..dda68d501f975 100644 --- a/x-pack/legacy/plugins/siem/reporter_config.json +++ b/x-pack/legacy/plugins/siem/reporter_config.json @@ -3,7 +3,7 @@ "reporterOptions": { "html": false, "json": true, - "mochaFile": "../../../../target/kibana-siem/cypress/results/results-[hash].xml", + "mochaFile": "../../../../target/kibana-siem/cypress/results/TEST-siem-cypress-[hash].xml", "overwrite": false, "reportDir": "../../../../target/kibana-siem/cypress/results" } diff --git a/x-pack/legacy/plugins/spaces/server/routes/views/enter_space.ts b/x-pack/legacy/plugins/spaces/server/routes/views/enter_space.ts index 337faa2a18fb6..60ae3a1fa77bb 100644 --- a/x-pack/legacy/plugins/spaces/server/routes/views/enter_space.ts +++ b/x-pack/legacy/plugins/spaces/server/routes/views/enter_space.ts @@ -14,7 +14,13 @@ export function initEnterSpaceView(server: Legacy.Server) { path: ENTER_SPACE_PATH, async handler(request, h) { try { - return h.redirect(await request.getDefaultRoute()); + const uiSettings = request.getUiSettingsService(); + const defaultRoute = await uiSettings.get('defaultRoute'); + + const basePath = server.newPlatform.setup.core.http.basePath.get(request); + const url = `${basePath}${defaultRoute}`; + + return h.redirect(url); } catch (e) { server.log(['spaces', 'error'], `Error navigating to space: ${e}`); return wrapError(e); diff --git a/x-pack/legacy/plugins/uptime/common/constants/index.ts b/x-pack/legacy/plugins/uptime/common/constants/index.ts index 9d5ad4607491c..0425fc19a7b45 100644 --- a/x-pack/legacy/plugins/uptime/common/constants/index.ts +++ b/x-pack/legacy/plugins/uptime/common/constants/index.ts @@ -12,3 +12,4 @@ export * from './capabilities'; export { PLUGIN } from './plugin'; export { QUERY, STATES } from './query'; export * from './ui'; +export * from './rest_api'; diff --git a/x-pack/legacy/plugins/uptime/common/constants/rest_api.ts b/x-pack/legacy/plugins/uptime/common/constants/rest_api.ts new file mode 100644 index 0000000000000..f09c795977831 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/common/constants/rest_api.ts @@ -0,0 +1,9 @@ +/* + * 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 enum REST_API_URLS { + INDEX_STATUS = '/api/uptime/index_status', +} diff --git a/x-pack/legacy/plugins/uptime/common/graphql/types.ts b/x-pack/legacy/plugins/uptime/common/graphql/types.ts index a33a69c229873..1a37ce0b18c73 100644 --- a/x-pack/legacy/plugins/uptime/common/graphql/types.ts +++ b/x-pack/legacy/plugins/uptime/common/graphql/types.ts @@ -21,8 +21,6 @@ export interface Query { /** Fetches the current state of Uptime monitors for the given parameters. */ getMonitorStates?: MonitorSummaryResult | null; - /** Fetches details about the uptime index. */ - getStatesIndexStatus: StatesIndexStatus; } export interface PingResults { @@ -392,7 +390,7 @@ export interface MonitorSummaryResult { /** The objects representing the state of a series of heartbeat monitors. */ summaries?: MonitorSummary[] | null; /** The number of summaries. */ - totalSummaryCount: DocCount; + totalSummaryCount: number; } /** Represents the current state and associated data for an Uptime monitor. */ export interface MonitorSummary { @@ -525,13 +523,7 @@ export interface SummaryHistogramPoint { /** The number of _down_ documents. */ down: number; } -/** Represents the current status of the uptime index. */ -export interface StatesIndexStatus { - /** Flag denoting whether the index exists. */ - indexExists: boolean; - /** The number of documents in the index. */ - docCount?: DocCount | null; -} + export interface AllPingsQueryArgs { /** Optional: the direction to sort by. Accepts 'asc' and 'desc'. Defaults to 'desc'. */ diff --git a/x-pack/legacy/plugins/uptime/common/runtime_types/common.ts b/x-pack/legacy/plugins/uptime/common/runtime_types/common.ts index 84e3ae33294f0..37101b5b46fd2 100644 --- a/x-pack/legacy/plugins/uptime/common/runtime_types/common.ts +++ b/x-pack/legacy/plugins/uptime/common/runtime_types/common.ts @@ -22,6 +22,12 @@ export const SummaryType = t.partial({ geo: CheckGeoType, }); +export const StatesIndexStatusType = t.type({ + indexExists: t.boolean, + docCount: t.number, +}); + export type Summary = t.TypeOf; export type CheckGeo = t.TypeOf; export type Location = t.TypeOf; +export type StatesIndexStatus = t.TypeOf; diff --git a/x-pack/legacy/plugins/uptime/public/components/connected/empty_state/empty_state.tsx b/x-pack/legacy/plugins/uptime/public/components/connected/empty_state/empty_state.tsx new file mode 100644 index 0000000000000..cac7042ca5b5c --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/components/connected/empty_state/empty_state.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, { useEffect } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { indexStatusAction } from '../../../state/actions'; +import { indexStatusSelector } from '../../../state/selectors'; +import { EmptyStateComponent } from '../../functional/empty_state/empty_state'; + +export const EmptyState: React.FC = ({ children }) => { + const { data, loading, errors } = useSelector(indexStatusSelector); + + const dispatch = useDispatch(); + + useEffect(() => { + dispatch(indexStatusAction.get()); + }, [dispatch]); + + return ( + + ); +}; diff --git a/x-pack/legacy/plugins/uptime/public/components/connected/index.ts b/x-pack/legacy/plugins/uptime/public/components/connected/index.ts index 2e30e5c3cb24f..baa961ddc87d2 100644 --- a/x-pack/legacy/plugins/uptime/public/components/connected/index.ts +++ b/x-pack/legacy/plugins/uptime/public/components/connected/index.ts @@ -13,3 +13,4 @@ export { MonitorStatusBar } from './monitor/status_bar_container'; export { MonitorListDrawer } from './monitor/list_drawer_container'; export { MonitorListActionsPopover } from './monitor/drawer_popover_container'; export { DurationChart } from './charts/monitor_duration'; +export { EmptyState } from './empty_state/empty_state'; diff --git a/x-pack/legacy/plugins/uptime/public/components/connected/kuerybar/kuery_bar_container.tsx b/x-pack/legacy/plugins/uptime/public/components/connected/kuerybar/kuery_bar_container.tsx index d0f160b2c5540..a42f96962b95e 100644 --- a/x-pack/legacy/plugins/uptime/public/components/connected/kuerybar/kuery_bar_container.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/connected/kuerybar/kuery_bar_container.tsx @@ -10,7 +10,7 @@ import { selectIndexPattern } from '../../../state/selectors'; import { getIndexPattern } from '../../../state/actions'; import { KueryBarComponent } from '../../functional'; -const mapStateToProps = (state: AppState) => ({ indexPattern: selectIndexPattern(state) }); +const mapStateToProps = (state: AppState) => ({ ...selectIndexPattern(state) }); const mapDispatchToProps = (dispatch: any) => ({ loadIndexPattern: () => { diff --git a/x-pack/legacy/plugins/uptime/public/components/connected/pages/overview_container.tsx b/x-pack/legacy/plugins/uptime/public/components/connected/pages/overview_container.tsx index cbd1fae77c518..79aaa071507e1 100644 --- a/x-pack/legacy/plugins/uptime/public/components/connected/pages/overview_container.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/connected/pages/overview_container.tsx @@ -18,6 +18,6 @@ const mapDispatchToProps = (dispatch: any): DispatchProps => ({ setEsKueryFilters: (esFilters: string) => dispatch(setEsKueryString(esFilters)), }); -const mapStateToProps = (state: AppState) => ({ indexPattern: selectIndexPattern(state) }); +const mapStateToProps = (state: AppState) => ({ ...selectIndexPattern(state) }); export const OverviewPage = connect(mapStateToProps, mapDispatchToProps)(OverviewPageComponent); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/empty_state/__tests__/__snapshots__/empty_state.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/functional/empty_state/__tests__/__snapshots__/empty_state.test.tsx.snap index 472d9c2be59e4..a885cfe22ccd2 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/empty_state/__tests__/__snapshots__/empty_state.test.tsx.snap +++ b/x-pack/legacy/plugins/uptime/public/components/functional/empty_state/__tests__/__snapshots__/empty_state.test.tsx.snap @@ -2,16 +2,6 @@ exports[`EmptyState component does not render empty state with appropriate base path and no docs 1`] = ` `; -exports[`EmptyState component doesn't render child components when count is falsey 1`] = ` +exports[`EmptyState component doesn't render child components when count is falsy 1`] = ` { let statesIndexStatus: StatesIndexStatus; @@ -16,15 +16,13 @@ describe('EmptyState component', () => { beforeEach(() => { statesIndexStatus = { indexExists: true, - docCount: { - count: 1, - }, + docCount: 1, }; }); it('renders child components when count is truthy', () => { const component = shallowWithIntl( - +
Foo
Bar
Baz
@@ -33,9 +31,9 @@ describe('EmptyState component', () => { expect(component).toMatchSnapshot(); }); - it(`doesn't render child components when count is falsey`, () => { + it(`doesn't render child components when count is falsy`, () => { const component = mountWithIntl( - +
Shouldn't be rendered
); @@ -57,7 +55,7 @@ describe('EmptyState component', () => { }, ]; const component = mountWithIntl( - +
Shouldn't appear...
); @@ -66,7 +64,7 @@ describe('EmptyState component', () => { it('renders loading state if no errors or doc count', () => { const component = mountWithIntl( - +
Should appear even while loading...
); @@ -75,13 +73,11 @@ describe('EmptyState component', () => { it('does not render empty state with appropriate base path and no docs', () => { statesIndexStatus = { - docCount: { - count: 0, - }, + docCount: 0, indexExists: true, }; const component = mountWithIntl( - +
If this is in the snapshot the test should fail
); @@ -91,7 +87,7 @@ describe('EmptyState component', () => { it('notifies when index does not exist', () => { statesIndexStatus.indexExists = false; const component = mountWithIntl( - +
This text should not render
); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/empty_state/empty_state.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/empty_state/empty_state.tsx index d2d46dff3b9f5..80afc2894ea44 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/empty_state/empty_state.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/empty_state/empty_state.tsx @@ -6,29 +6,29 @@ import React, { Fragment } from 'react'; import { i18n } from '@kbn/i18n'; -import { UptimeGraphQLQueryProps, withUptimeGraphQL } from '../../higher_order'; -import { docCountQuery } from '../../../queries'; import { EmptyStateError } from './empty_state_error'; import { EmptyStateLoading } from './empty_state_loading'; -import { StatesIndexStatus } from '../../../../common/graphql/types'; import { DataMissing } from './data_missing'; - -interface EmptyStateQueryResult { - statesIndexStatus?: StatesIndexStatus; -} +import { StatesIndexStatus } from '../../../../common/runtime_types'; interface EmptyStateProps { children: JSX.Element[] | JSX.Element; + statesIndexStatus: StatesIndexStatus | null; + loading: boolean; + errors?: Error[]; } -type Props = UptimeGraphQLQueryProps & EmptyStateProps; - -export const EmptyStateComponent = ({ children, data, errors }: Props) => { - if (errors) { +export const EmptyStateComponent = ({ + children, + statesIndexStatus, + loading, + errors, +}: EmptyStateProps) => { + if (errors?.length) { return ; } - if (data && data.statesIndexStatus) { - const { indexExists, docCount } = data.statesIndexStatus; + if (!loading && statesIndexStatus) { + const { indexExists, docCount } = statesIndexStatus; if (!indexExists) { return ( { })} /> ); - } else if (indexExists && docCount && docCount.count === 0) { + } else if (indexExists && docCount === 0) { return ( { } return ; }; - -export const EmptyState = withUptimeGraphQL( - EmptyStateComponent, - docCountQuery -); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/empty_state/empty_state_error.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/empty_state/empty_state_error.tsx index 745b185b57fac..c8e2bece1cb7f 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/empty_state/empty_state_error.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/empty_state/empty_state_error.tsx @@ -7,15 +7,14 @@ import { EuiEmptyPrompt, EuiPanel, EuiTitle, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { Fragment } from 'react'; -import { GraphQLError } from 'graphql'; interface EmptyStateErrorProps { - errors: GraphQLError[]; + errors: Error[]; } export const EmptyStateError = ({ errors }: EmptyStateErrorProps) => { const unauthorized = errors.find( - (error: GraphQLError) => error.message && error.message.includes('unauthorized') + (error: Error) => error.message && error.message.includes('unauthorized') ); return ( @@ -46,7 +45,7 @@ export const EmptyStateError = ({ errors }: EmptyStateErrorProps) => { body={ {!unauthorized && - errors.map((error: GraphQLError) =>

{error.message}

)} + errors.map((error: Error) =>

{error.message}

)}
} /> diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/index.ts b/x-pack/legacy/plugins/uptime/public/components/functional/index.ts index e86ba548fb5d9..daba13d8df641 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/index.ts +++ b/x-pack/legacy/plugins/uptime/public/components/functional/index.ts @@ -5,7 +5,6 @@ */ export { DonutChart } from './charts/donut_chart'; -export { EmptyState } from './empty_state'; export { KueryBarComponent } from './kuery_bar/kuery_bar'; export { MonitorCharts } from './monitor_charts'; export { MonitorList } from './monitor_list'; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/kuery_bar/kuery_bar.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/kuery_bar/kuery_bar.tsx index 496e8d898df3c..2f5ccc2adf313 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/kuery_bar/kuery_bar.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/kuery_bar/kuery_bar.tsx @@ -34,14 +34,16 @@ function convertKueryToEsQuery(kuery: string, indexPattern: IIndexPattern) { interface Props { autocomplete: DataPublicPluginSetup['autocomplete']; - loadIndexPattern: any; - indexPattern: any; + loadIndexPattern: () => void; + indexPattern: IIndexPattern | null; + loading: boolean; } export function KueryBarComponent({ autocomplete: autocompleteService, loadIndexPattern, indexPattern, + loading, }: Props) { useEffect(() => { if (!indexPattern) { @@ -53,19 +55,13 @@ export function KueryBarComponent({ suggestions: [], isLoadingIndexPattern: true, }); - const [isLoadingIndexPattern, setIsLoadingIndexPattern] = useState(true); const [isLoadingSuggestions, setIsLoadingSuggestions] = useState(false); let currentRequestCheck: string; - useEffect(() => { - if (indexPattern !== undefined) { - setIsLoadingIndexPattern(false); - } - }, [indexPattern]); const [getUrlParams, updateUrlParams] = useUrlParams(); const { search: kuery } = getUrlParams(); - const indexPatternMissing = !isLoadingIndexPattern && !indexPattern; + const indexPatternMissing = loading && !indexPattern; async function onChange(inputValue: string, selectionStart: number) { if (!indexPattern) { @@ -124,7 +120,7 @@ export function KueryBarComponent({ { }, }, ], - totalSummaryCount: { - count: 2, - }, + totalSummaryCount: 2, }; }); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/__tests__/monitor_list_pagination.test.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/__tests__/monitor_list_pagination.test.tsx index ff54e61006156..1aef9281a3066 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/__tests__/monitor_list_pagination.test.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/__tests__/monitor_list_pagination.test.tsx @@ -89,9 +89,7 @@ describe('MonitorListPagination component', () => { }, }, ], - totalSummaryCount: { - count: 2, - }, + totalSummaryCount: 2, }; }); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/monitor_list_drawer/__tests__/data.json b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/monitor_list_drawer/__tests__/data.json index a45e974685b9c..e8142f0480c4a 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/monitor_list_drawer/__tests__/data.json +++ b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/monitor_list_drawer/__tests__/data.json @@ -3,7 +3,7 @@ "monitorStates": { "prevPagePagination": null, "nextPagePagination": null, - "totalSummaryCount": { "count": 147428, "__typename": "DocCount" }, + "totalSummaryCount": 147428, "summaries": [ { "monitor_id": "andrewvc-com", diff --git a/x-pack/legacy/plugins/uptime/public/hooks/update_kuery_string.ts b/x-pack/legacy/plugins/uptime/public/hooks/update_kuery_string.ts index 5fcacf8424660..ab4d6f75849e8 100644 --- a/x-pack/legacy/plugins/uptime/public/hooks/update_kuery_string.ts +++ b/x-pack/legacy/plugins/uptime/public/hooks/update_kuery_string.ts @@ -24,7 +24,7 @@ const getKueryString = (urlFilters: string): string => { }; export const useUpdateKueryString = ( - indexPattern: IIndexPattern, + indexPattern: IIndexPattern | null, filterQueryString = '', urlFilters: string ): [string?, Error?] => { diff --git a/x-pack/legacy/plugins/uptime/public/pages/overview.tsx b/x-pack/legacy/plugins/uptime/public/pages/overview.tsx index 15e31d5e44629..af9b8bf046416 100644 --- a/x-pack/legacy/plugins/uptime/public/pages/overview.tsx +++ b/x-pack/legacy/plugins/uptime/public/pages/overview.tsx @@ -9,7 +9,6 @@ import React, { useContext, useEffect } from 'react'; import styled from 'styled-components'; import { i18n } from '@kbn/i18n'; import { - EmptyState, MonitorList, OverviewPageParsingErrorCallout, StatusPanel, @@ -19,13 +18,13 @@ import { stringifyUrlParams } from '../lib/helper/stringify_url_params'; import { useTrackPageview } from '../../../../../plugins/observability/public'; import { DataPublicPluginSetup, IIndexPattern } from '../../../../../../src/plugins/data/public'; import { UptimeThemeContext } from '../contexts'; -import { FilterGroup, KueryBar } from '../components/connected'; +import { EmptyState, FilterGroup, KueryBar } from '../components/connected'; import { useUpdateKueryString } from '../hooks'; import { PageHeader } from './page_header'; interface OverviewPageProps { autocomplete: DataPublicPluginSetup['autocomplete']; - indexPattern: IIndexPattern; + indexPattern: IIndexPattern | null; setEsKueryFilters: (esFilters: string) => void; } @@ -81,7 +80,7 @@ export const OverviewPageComponent = ({ autocomplete, indexPattern, setEsKueryFi return ( <> - + diff --git a/x-pack/legacy/plugins/uptime/public/queries/doc_count_query.ts b/x-pack/legacy/plugins/uptime/public/queries/doc_count_query.ts deleted file mode 100644 index 3067a9d16f050..0000000000000 --- a/x-pack/legacy/plugins/uptime/public/queries/doc_count_query.ts +++ /dev/null @@ -1,22 +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 gql from 'graphql-tag'; - -export const docCountQueryString = ` -query GetStateIndexStatus { - statesIndexStatus: getStatesIndexStatus { - docCount { - count - } - indexExists - } -} -`; - -export const docCountQuery = gql` - ${docCountQueryString} -`; diff --git a/x-pack/legacy/plugins/uptime/public/queries/index.ts b/x-pack/legacy/plugins/uptime/public/queries/index.ts index f2fff9bc506d0..283382ec1b7ba 100644 --- a/x-pack/legacy/plugins/uptime/public/queries/index.ts +++ b/x-pack/legacy/plugins/uptime/public/queries/index.ts @@ -4,5 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { docCountQuery, docCountQueryString } from './doc_count_query'; export { pingsQuery, pingsQueryString } from './pings_query'; diff --git a/x-pack/legacy/plugins/uptime/public/queries/monitor_states_query.ts b/x-pack/legacy/plugins/uptime/public/queries/monitor_states_query.ts index 76f62ad453bd9..9e609786094d5 100644 --- a/x-pack/legacy/plugins/uptime/public/queries/monitor_states_query.ts +++ b/x-pack/legacy/plugins/uptime/public/queries/monitor_states_query.ts @@ -17,9 +17,7 @@ query MonitorStates($dateRangeStart: String!, $dateRangeEnd: String!, $paginatio ) { prevPagePagination nextPagePagination - totalSummaryCount { - count - } + totalSummaryCount summaries { monitor_id histogram { diff --git a/x-pack/legacy/plugins/uptime/public/state/actions/index.ts b/x-pack/legacy/plugins/uptime/public/state/actions/index.ts index dfcea64bf9c08..b2ab73879a4a7 100644 --- a/x-pack/legacy/plugins/uptime/public/state/actions/index.ts +++ b/x-pack/legacy/plugins/uptime/public/state/actions/index.ts @@ -11,3 +11,4 @@ export * from './monitor_status'; export * from './index_patternts'; export * from './ping'; export * from './monitor_duration'; +export * from './index_status'; diff --git a/x-pack/legacy/plugins/uptime/public/state/actions/index_status.ts b/x-pack/legacy/plugins/uptime/public/state/actions/index_status.ts new file mode 100644 index 0000000000000..336758a71ce60 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/state/actions/index_status.ts @@ -0,0 +1,10 @@ +/* + * 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 { createAsyncAction } from './utils'; +import { StatesIndexStatus } from '../../../common/runtime_types'; + +export const indexStatusAction = createAsyncAction('GET INDEX STATUS'); diff --git a/x-pack/legacy/plugins/uptime/public/state/actions/types.ts b/x-pack/legacy/plugins/uptime/public/state/actions/types.ts index dba70ed839ac5..e9bf11256b0b8 100644 --- a/x-pack/legacy/plugins/uptime/public/state/actions/types.ts +++ b/x-pack/legacy/plugins/uptime/public/state/actions/types.ts @@ -4,6 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ +import { Action } from 'redux-actions'; + +export interface AsyncAction { + get: (payload?: any) => Action; + success: (payload?: any) => Action; + fail: (payload?: any) => Action; +} + export interface QueryParams { monitorId: string; dateStart: string; diff --git a/x-pack/legacy/plugins/uptime/public/state/actions/utils.ts b/x-pack/legacy/plugins/uptime/public/state/actions/utils.ts new file mode 100644 index 0000000000000..337c4bfb2fa47 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/state/actions/utils.ts @@ -0,0 +1,16 @@ +/* + * 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 { createAction } from 'redux-actions'; +import { AsyncAction } from './types'; + +export function createAsyncAction(actionStr: string): AsyncAction { + return { + get: createAction(actionStr), + success: createAction(`${actionStr}_SUCCESS`), + fail: createAction(`${actionStr}_FAIL`), + }; +} diff --git a/x-pack/legacy/plugins/uptime/public/state/api/index.ts b/x-pack/legacy/plugins/uptime/public/state/api/index.ts index 7d42c6ee46bdc..518091cb36dde 100644 --- a/x-pack/legacy/plugins/uptime/public/state/api/index.ts +++ b/x-pack/legacy/plugins/uptime/public/state/api/index.ts @@ -9,5 +9,6 @@ export * from './overview_filters'; export * from './snapshot'; export * from './monitor_status'; export * from './index_pattern'; +export * from './index_status'; export * from './ping'; export * from './monitor_duration'; diff --git a/x-pack/legacy/plugins/uptime/public/state/api/index_status.ts b/x-pack/legacy/plugins/uptime/public/state/api/index_status.ts new file mode 100644 index 0000000000000..9c531b3406a7c --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/state/api/index_status.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 { PathReporter } from 'io-ts/lib/PathReporter'; +import { isRight } from 'fp-ts/lib/Either'; +import { getApiPath } from '../../lib/helper'; +import { REST_API_URLS } from '../../../common/constants/rest_api'; +import { StatesIndexStatus, StatesIndexStatusType } from '../../../common/runtime_types'; + +interface ApiRequest { + basePath: string; +} + +export const fetchIndexStatus = async ({ basePath }: ApiRequest): Promise => { + const url = getApiPath(REST_API_URLS.INDEX_STATUS, basePath); + + const response = await fetch(url); + if (!response.ok) { + throw new Error(response.statusText); + } + const responseData = await response.json(); + const decoded = StatesIndexStatusType.decode(responseData); + PathReporter.report(decoded); + if (isRight(decoded)) { + return decoded.right; + } + throw PathReporter.report(decoded); +}; diff --git a/x-pack/legacy/plugins/uptime/public/state/effects/fetch_effect.ts b/x-pack/legacy/plugins/uptime/public/state/effects/fetch_effect.ts index d293cdbe451b5..ea389ff0a6745 100644 --- a/x-pack/legacy/plugins/uptime/public/state/effects/fetch_effect.ts +++ b/x-pack/legacy/plugins/uptime/public/state/effects/fetch_effect.ts @@ -26,10 +26,6 @@ export function fetchEffectFactory( ) { return function*(action: Action) { try { - if (!action.payload) { - yield put(fail(new Error('Cannot fetch snapshot for undefined parameters.'))); - return; - } const { payload: { ...params }, } = action; diff --git a/x-pack/legacy/plugins/uptime/public/state/effects/index.ts b/x-pack/legacy/plugins/uptime/public/state/effects/index.ts index 43af88f4cc291..7c45aa142ecfd 100644 --- a/x-pack/legacy/plugins/uptime/public/state/effects/index.ts +++ b/x-pack/legacy/plugins/uptime/public/state/effects/index.ts @@ -12,6 +12,7 @@ import { fetchMonitorStatusEffect } from './monitor_status'; import { fetchIndexPatternEffect } from './index_pattern'; import { fetchPingHistogramEffect } from './ping'; import { fetchMonitorDurationEffect } from './monitor_duration'; +import { fetchIndexStatusEffect } from './index_status'; export function* rootEffect() { yield fork(fetchMonitorDetailsEffect); @@ -21,4 +22,5 @@ export function* rootEffect() { yield fork(fetchIndexPatternEffect); yield fork(fetchPingHistogramEffect); yield fork(fetchMonitorDurationEffect); + yield fork(fetchIndexStatusEffect); } diff --git a/x-pack/legacy/plugins/uptime/public/state/effects/index_status.ts b/x-pack/legacy/plugins/uptime/public/state/effects/index_status.ts new file mode 100644 index 0000000000000..793a671f5fed8 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/state/effects/index_status.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { takeLatest } from 'redux-saga/effects'; +import { indexStatusAction } from '../actions'; +import { fetchIndexStatus } from '../api'; +import { fetchEffectFactory } from './fetch_effect'; + +export function* fetchIndexStatusEffect() { + yield takeLatest( + indexStatusAction.get, + fetchEffectFactory(fetchIndexStatus, indexStatusAction.success, indexStatusAction.fail) + ); +} diff --git a/x-pack/legacy/plugins/uptime/public/state/reducers/index.ts b/x-pack/legacy/plugins/uptime/public/state/reducers/index.ts index 32362afae42bc..4a83b54504ca8 100644 --- a/x-pack/legacy/plugins/uptime/public/state/reducers/index.ts +++ b/x-pack/legacy/plugins/uptime/public/state/reducers/index.ts @@ -13,6 +13,7 @@ import { monitorStatusReducer } from './monitor_status'; import { indexPatternReducer } from './index_pattern'; import { pingReducer } from './ping'; import { monitorDurationReducer } from './monitor_duration'; +import { indexStatusReducer } from './index_status'; export const rootReducer = combineReducers({ monitor: monitorReducer, @@ -23,4 +24,5 @@ export const rootReducer = combineReducers({ indexPattern: indexPatternReducer, ping: pingReducer, monitorDuration: monitorDurationReducer, + indexStatus: indexStatusReducer, }); diff --git a/x-pack/legacy/plugins/uptime/public/state/reducers/index_pattern.ts b/x-pack/legacy/plugins/uptime/public/state/reducers/index_pattern.ts index dff043f81b95c..bc482e2f35c45 100644 --- a/x-pack/legacy/plugins/uptime/public/state/reducers/index_pattern.ts +++ b/x-pack/legacy/plugins/uptime/public/state/reducers/index_pattern.ts @@ -5,9 +5,10 @@ */ import { handleActions, Action } from 'redux-actions'; import { getIndexPattern, getIndexPatternSuccess, getIndexPatternFail } from '../actions'; +import { IIndexPattern } from '../../../../../../../src/plugins/data/common/index_patterns'; export interface IndexPatternState { - index_pattern: any; + index_pattern: IIndexPattern | null; errors: any[]; loading: boolean; } diff --git a/x-pack/legacy/plugins/uptime/public/state/reducers/index_status.ts b/x-pack/legacy/plugins/uptime/public/state/reducers/index_status.ts new file mode 100644 index 0000000000000..50a02210fb5d3 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/state/reducers/index_status.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { handleActions } from 'redux-actions'; +import { indexStatusAction } from '../actions'; +import { handleAsyncAction } from './utils'; +import { IReducerState } from './types'; +import { StatesIndexStatus } from '../../../common/runtime_types'; + +export interface IndexStatusState extends IReducerState { + data: StatesIndexStatus | null; +} + +const initialState: IndexStatusState = { + data: null, + loading: false, + errors: [], +}; + +type PayLoad = StatesIndexStatus & Error; + +export const indexStatusReducer = handleActions( + { + ...handleAsyncAction('data', indexStatusAction), + }, + initialState +); diff --git a/x-pack/legacy/plugins/uptime/public/state/reducers/types.ts b/x-pack/legacy/plugins/uptime/public/state/reducers/types.ts new file mode 100644 index 0000000000000..40fe4bddbf172 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/state/reducers/types.ts @@ -0,0 +1,10 @@ +/* + * 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 interface IReducerState { + errors: Error[]; + loading: boolean; +} diff --git a/x-pack/legacy/plugins/uptime/public/state/reducers/utils.ts b/x-pack/legacy/plugins/uptime/public/state/reducers/utils.ts new file mode 100644 index 0000000000000..773ec10686943 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/state/reducers/utils.ts @@ -0,0 +1,33 @@ +/* + * 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 { Action } from 'redux-actions'; +import { AsyncAction } from '../actions/types'; +import { IReducerState } from './types'; + +export function handleAsyncAction( + storeKey: string, + asyncAction: AsyncAction +) { + return { + [String(asyncAction.get)]: (state: ReducerState) => ({ + ...state, + loading: true, + }), + + [String(asyncAction.success)]: (state: ReducerState, action: Action) => ({ + ...state, + loading: false, + [storeKey]: action.payload === null ? action.payload : { ...action.payload }, + }), + + [String(asyncAction.fail)]: (state: ReducerState, action: Action) => ({ + ...state, + errors: [...state.errors, action.payload], + loading: false, + }), + }; +} diff --git a/x-pack/legacy/plugins/uptime/public/state/selectors/__tests__/index.test.ts b/x-pack/legacy/plugins/uptime/public/state/selectors/__tests__/index.test.ts index 24d34b4d067cc..de446418632b8 100644 --- a/x-pack/legacy/plugins/uptime/public/state/selectors/__tests__/index.test.ts +++ b/x-pack/legacy/plugins/uptime/public/state/selectors/__tests__/index.test.ts @@ -60,6 +60,11 @@ describe('state selectors', () => { loading: false, errors: [], }, + indexStatus: { + loading: false, + data: null, + errors: [], + }, }; it('selects base path from state', () => { diff --git a/x-pack/legacy/plugins/uptime/public/state/selectors/index.ts b/x-pack/legacy/plugins/uptime/public/state/selectors/index.ts index 0a914a14c372b..adba288b8b145 100644 --- a/x-pack/legacy/plugins/uptime/public/state/selectors/index.ts +++ b/x-pack/legacy/plugins/uptime/public/state/selectors/index.ts @@ -30,7 +30,7 @@ export const selectMonitorStatus = (state: AppState) => { }; export const selectIndexPattern = ({ indexPattern }: AppState) => { - return indexPattern.index_pattern; + return { indexPattern: indexPattern.index_pattern, loading: indexPattern.loading }; }; export const selectPingHistogram = ({ ping, ui }: AppState) => { @@ -45,3 +45,7 @@ export const selectPingHistogram = ({ ping, ui }: AppState) => { export const selectDurationLines = ({ monitorDuration }: AppState) => { return monitorDuration; }; + +export const indexStatusSelector = ({ indexStatus }: AppState) => { + return indexStatus; +}; diff --git a/x-pack/plugins/apm/server/index.ts b/x-pack/plugins/apm/server/index.ts index ebd954015c910..94cc5cecb54b1 100644 --- a/x-pack/plugins/apm/server/index.ts +++ b/x-pack/plugins/apm/server/index.ts @@ -16,7 +16,7 @@ export const config = { }, schema: schema.object({ enabled: schema.boolean({ defaultValue: true }), - serviceMapEnabled: schema.boolean({ defaultValue: false }), + serviceMapEnabled: schema.boolean({ defaultValue: true }), autocreateApmIndexPattern: schema.boolean({ defaultValue: true }), ui: schema.object({ enabled: schema.boolean({ defaultValue: true }), diff --git a/x-pack/plugins/case/common/api/cases/configure.ts b/x-pack/plugins/case/common/api/cases/configure.ts new file mode 100644 index 0000000000000..e0489ed7270fa --- /dev/null +++ b/x-pack/plugins/case/common/api/cases/configure.ts @@ -0,0 +1,107 @@ +/* + * 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 * as rt from 'io-ts'; + +import { ActionResult } from '../../../../actions/common'; +import { UserRT } from '../user'; + +/* + * This types below are related to the service now configuration + * mapping between our case and service-now + * + */ + +const ActionTypeRT = rt.union([ + rt.literal('append'), + rt.literal('nothing'), + rt.literal('overwrite'), +]); + +const CaseFieldRT = rt.union([ + rt.literal('title'), + rt.literal('description'), + rt.literal('comments'), +]); + +const ThirdPartyFieldRT = rt.union([ + rt.literal('comments'), + rt.literal('description'), + rt.literal('not_mapped'), + rt.literal('short_description'), +]); + +export const CasesConfigurationMapsRT = rt.type({ + source: CaseFieldRT, + target: ThirdPartyFieldRT, + action_type: ActionTypeRT, +}); + +export const CasesConfigurationRT = rt.type({ + mapping: rt.array(CasesConfigurationMapsRT), +}); + +export const CasesConnectorConfigurationRT = rt.type({ + cases_configuration: CasesConfigurationRT, + // version: rt.string, +}); + +export type ActionType = rt.TypeOf; +export type CaseField = rt.TypeOf; +export type ThirdPartyField = rt.TypeOf; + +export type CasesConfigurationMaps = rt.TypeOf; +export type CasesConfiguration = rt.TypeOf; +export type CasesConnectorConfiguration = rt.TypeOf; + +/** ********************************************************************** */ + +export type Connector = ActionResult; + +export interface CasesConnectorsFindResult { + page: number; + perPage: number; + total: number; + data: Connector[]; +} + +// TO DO we will need to add this type rt.literal('close-by-thrid-party') +const ClosureTypeRT = rt.union([rt.literal('close-by-user'), rt.literal('close-by-pushing')]); + +const CasesConfigureBasicRt = rt.type({ + connector_id: rt.string, + closure_type: ClosureTypeRT, +}); + +export const CasesConfigureRequestRt = CasesConfigureBasicRt; +export const CasesConfigurePatchRt = rt.intersection([ + rt.partial(CasesConfigureBasicRt.props), + rt.type({ version: rt.string }), +]); + +export const CaseConfigureAttributesRt = rt.intersection([ + CasesConfigureBasicRt, + rt.type({ + created_at: rt.string, + created_by: UserRT, + updated_at: rt.union([rt.string, rt.null]), + updated_by: rt.union([UserRT, rt.null]), + }), +]); + +export const CaseConfigureResponseRt = rt.intersection([ + CaseConfigureAttributesRt, + rt.type({ + version: rt.string, + }), +]); + +export type ClosureType = rt.TypeOf; + +export type CasesConfigureRequest = rt.TypeOf; +export type CasesConfigurePatch = rt.TypeOf; +export type CasesConfigureAttributes = rt.TypeOf; +export type CasesConfigureResponse = rt.TypeOf; diff --git a/x-pack/plugins/case/common/api/cases/index.ts b/x-pack/plugins/case/common/api/cases/index.ts index 5a355c631f396..5fbee98bc57ad 100644 --- a/x-pack/plugins/case/common/api/cases/index.ts +++ b/x-pack/plugins/case/common/api/cases/index.ts @@ -5,5 +5,6 @@ */ export * from './case'; +export * from './configure'; export * from './comment'; export * from './status'; diff --git a/x-pack/plugins/case/kibana.json b/x-pack/plugins/case/kibana.json index 4a0151546c8fb..f565dc1b6924e 100644 --- a/x-pack/plugins/case/kibana.json +++ b/x-pack/plugins/case/kibana.json @@ -2,7 +2,7 @@ "configPath": ["xpack", "case"], "id": "case", "kibanaVersion": "kibana", - "requiredPlugins": ["security"], + "requiredPlugins": ["security", "actions"], "optionalPlugins": [ "spaces", "security" diff --git a/x-pack/plugins/case/server/plugin.ts b/x-pack/plugins/case/server/plugin.ts index 7ce3a61f03779..1d6495c2d81f3 100644 --- a/x-pack/plugins/case/server/plugin.ts +++ b/x-pack/plugins/case/server/plugin.ts @@ -12,8 +12,12 @@ import { SecurityPluginSetup } from '../../security/server'; import { ConfigType } from './config'; import { initCaseApi } from './routes/api'; -import { caseSavedObjectType, caseCommentSavedObjectType } from './saved_object_types'; -import { CaseService } from './services'; +import { + caseSavedObjectType, + caseConfigureSavedObjectType, + caseCommentSavedObjectType, +} from './saved_object_types'; +import { CaseConfigureService, CaseService } from './services'; function createConfig$(context: PluginInitializerContext) { return context.config.create().pipe(map(config => config)); @@ -41,8 +45,10 @@ export class CasePlugin { core.savedObjects.registerType(caseSavedObjectType); core.savedObjects.registerType(caseCommentSavedObjectType); + core.savedObjects.registerType(caseConfigureSavedObjectType); - const service = new CaseService(this.log); + const caseServicePlugin = new CaseService(this.log); + const caseConfigureServicePlugin = new CaseConfigureService(this.log); this.log.debug( `Setting up Case Workflow with core contract [${Object.keys( @@ -50,12 +56,14 @@ export class CasePlugin { )}] and plugins [${Object.keys(plugins)}]` ); - const caseService = await service.setup({ + const caseService = await caseServicePlugin.setup({ authentication: plugins.security.authc, }); + const caseConfigureService = await caseConfigureServicePlugin.setup(); const router = core.http.createRouter(); initCaseApi({ + caseConfigureService, caseService, router, }); diff --git a/x-pack/plugins/case/server/routes/api/__fixtures__/mock_router.ts b/x-pack/plugins/case/server/routes/api/__fixtures__/mock_router.ts index 32348fecba1be..bc41ddbeff1f9 100644 --- a/x-pack/plugins/case/server/routes/api/__fixtures__/mock_router.ts +++ b/x-pack/plugins/case/server/routes/api/__fixtures__/mock_router.ts @@ -6,7 +6,7 @@ import { IRouter } from 'kibana/server'; import { loggingServiceMock, httpServiceMock } from '../../../../../../../src/core/server/mocks'; -import { CaseService } from '../../../services'; +import { CaseService, CaseConfigureService } from '../../../services'; import { authenticationMock } from '../__fixtures__'; import { RouteDeps } from '../types'; @@ -20,14 +20,18 @@ export const createRoute = async ( const log = loggingServiceMock.create().get('case'); - const service = new CaseService(log); - const caseService = await service.setup({ + const caseServicePlugin = new CaseService(log); + const caseConfigureServicePlugin = new CaseConfigureService(log); + + const caseService = await caseServicePlugin.setup({ authentication: badAuth ? authenticationMock.createInvalid() : authenticationMock.create(), }); + const caseConfigureService = await caseConfigureServicePlugin.setup(); api({ - router, + caseConfigureService, caseService, + router, }); return router[method].mock.calls[0][1]; diff --git a/x-pack/plugins/case/server/routes/api/cases/configure/get_configure.ts b/x-pack/plugins/case/server/routes/api/cases/configure/get_configure.ts new file mode 100644 index 0000000000000..2832edaa892d5 --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/cases/configure/get_configure.ts @@ -0,0 +1,37 @@ +/* + * 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 { CaseConfigureResponseRt } from '../../../../../common/api'; +import { RouteDeps } from '../../types'; +import { wrapError } from '../../utils'; + +export function initGetCaseConfigure({ caseConfigureService, caseService, router }: RouteDeps) { + router.get( + { + path: '/api/cases/configure', + validate: false, + }, + async (context, request, response) => { + try { + const client = context.core.savedObjects.client; + + const myCaseConfigure = await caseConfigureService.find({ client }); + + return response.ok({ + body: + myCaseConfigure.saved_objects.length > 0 + ? CaseConfigureResponseRt.encode({ + ...myCaseConfigure.saved_objects[0].attributes, + version: myCaseConfigure.saved_objects[0].version ?? '', + }) + : {}, + }); + } catch (error) { + return response.customError(wrapError(error)); + } + } + ); +} diff --git a/x-pack/plugins/case/server/routes/api/cases/configure/get_connectors.ts b/x-pack/plugins/case/server/routes/api/cases/configure/get_connectors.ts new file mode 100644 index 0000000000000..b7d4977d16b17 --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/cases/configure/get_connectors.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Boom from 'boom'; +import { RouteDeps } from '../../types'; +import { wrapError } from '../../utils'; + +/* + * Be aware that this api will only return 20 connectors + */ + +const CASE_SERVICE_NOW_ACTION = '.servicenow'; + +export function initCaseConfigureGetActionConnector({ caseService, router }: RouteDeps) { + router.get( + { + path: '/api/cases/configure/connectors/_find', + validate: false, + }, + async (context, request, response) => { + try { + const actionsClient = await context.actions?.getActionsClient(); + + if (actionsClient == null) { + throw Boom.notFound('Action client have not been found'); + } + + const results = await actionsClient.find({ + options: { + filter: `action.attributes.actionTypeId: ${CASE_SERVICE_NOW_ACTION}`, + }, + }); + return response.ok({ body: { ...results } }); + } catch (error) { + return response.customError(wrapError(error)); + } + } + ); +} diff --git a/x-pack/plugins/case/server/routes/api/cases/configure/patch_configure.ts b/x-pack/plugins/case/server/routes/api/cases/configure/patch_configure.ts new file mode 100644 index 0000000000000..1da1161ab01d1 --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/cases/configure/patch_configure.ts @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Boom from 'boom'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { fold } from 'fp-ts/lib/Either'; +import { identity } from 'fp-ts/lib/function'; + +import { + CasesConfigurePatchRt, + CaseConfigureResponseRt, + throwErrors, +} from '../../../../../common/api'; +import { RouteDeps } from '../../types'; +import { wrapError, escapeHatch } from '../../utils'; + +export function initPatchCaseConfigure({ caseConfigureService, caseService, router }: RouteDeps) { + router.patch( + { + path: '/api/cases/configure', + validate: { + body: escapeHatch, + }, + }, + async (context, request, response) => { + try { + const client = context.core.savedObjects.client; + const query = pipe( + CasesConfigurePatchRt.decode(request.body), + fold(throwErrors(Boom.badRequest), identity) + ); + + const myCaseConfigure = await caseConfigureService.find({ client }); + const { version, ...queryWithoutVersion } = query; + + if (myCaseConfigure.saved_objects.length === 0) { + throw Boom.conflict( + 'You can not patch this configuration since you did not created first with a post' + ); + } + + if (version !== myCaseConfigure.saved_objects[0].version) { + throw Boom.conflict( + 'This configuration has been updated. Please refresh before saving additional updates.' + ); + } + + const updatedBy = await caseService.getUser({ request, response }); + const { full_name, username } = updatedBy; + + const updateDate = new Date().toISOString(); + const patch = await caseConfigureService.patch({ + client, + caseConfigureId: myCaseConfigure.saved_objects[0].id, + updatedAttributes: { + ...queryWithoutVersion, + updated_at: updateDate, + updated_by: { full_name, username }, + }, + }); + + return response.ok({ + body: CaseConfigureResponseRt.encode({ + ...myCaseConfigure.saved_objects[0].attributes, + ...patch.attributes, + version: patch.version ?? '', + }), + }); + } catch (error) { + return response.customError(wrapError(error)); + } + } + ); +} diff --git a/x-pack/plugins/case/server/routes/api/cases/configure/patch_connector.ts b/x-pack/plugins/case/server/routes/api/cases/configure/patch_connector.ts new file mode 100644 index 0000000000000..a9fbe0ef4f721 --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/cases/configure/patch_connector.ts @@ -0,0 +1,68 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema } from '@kbn/config-schema'; +import Boom from 'boom'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { fold } from 'fp-ts/lib/Either'; +import { identity } from 'fp-ts/lib/function'; + +import { ActionResult } from '../../../../../../actions/common'; +import { CasesConnectorConfigurationRT, throwErrors } from '../../../../../common/api'; +import { RouteDeps } from '../../types'; +import { wrapError, escapeHatch } from '../../utils'; + +export function initCaseConfigurePatchActionConnector({ caseService, router }: RouteDeps) { + router.patch( + { + path: '/api/cases/configure/connectors/{connector_id}', + validate: { + params: schema.object({ + connector_id: schema.string(), + }), + body: escapeHatch, + }, + }, + async (context, request, response) => { + try { + const query = pipe( + CasesConnectorConfigurationRT.decode(request.body), + fold(throwErrors(Boom.badRequest), identity) + ); + + const client = context.core.savedObjects.client; + const { connector_id: connectorId } = request.params; + const { cases_configuration: casesConfiguration } = query; + + const normalizedMapping = casesConfiguration.mapping.map(m => ({ + source: m.source, + target: m.target, + actionType: m.action_type, + })); + + const action = await client.get('action', connectorId); + + const { config } = action.attributes; + const res = await client.update('action', connectorId, { + config: { + ...config, + casesConfiguration: { ...casesConfiguration, mapping: normalizedMapping }, + }, + }); + + return response.ok({ + body: CasesConnectorConfigurationRT.encode({ + cases_configuration: + res.attributes.config?.casesConfiguration ?? + action.attributes.config.casesConfiguration, + }), + }); + } catch (error) { + return response.customError(wrapError(error)); + } + } + ); +} diff --git a/x-pack/plugins/case/server/routes/api/cases/configure/post_configure.ts b/x-pack/plugins/case/server/routes/api/cases/configure/post_configure.ts new file mode 100644 index 0000000000000..a22dd8437e508 --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/cases/configure/post_configure.ts @@ -0,0 +1,68 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Boom from 'boom'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { fold } from 'fp-ts/lib/Either'; +import { identity } from 'fp-ts/lib/function'; + +import { + CasesConfigureRequestRt, + CaseConfigureResponseRt, + throwErrors, +} from '../../../../../common/api'; +import { RouteDeps } from '../../types'; +import { wrapError, escapeHatch } from '../../utils'; + +export function initPostCaseConfigure({ caseConfigureService, caseService, router }: RouteDeps) { + router.post( + { + path: '/api/cases/configure', + validate: { + body: escapeHatch, + }, + }, + async (context, request, response) => { + try { + const client = context.core.savedObjects.client; + const query = pipe( + CasesConfigureRequestRt.decode(request.body), + fold(throwErrors(Boom.badRequest), identity) + ); + + const myCaseConfigure = await caseConfigureService.find({ client }); + + if (myCaseConfigure.saved_objects.length > 0) { + await Promise.all( + myCaseConfigure.saved_objects.map(cc => + caseConfigureService.delete({ client, caseConfigureId: cc.id }) + ) + ); + } + const updatedBy = await caseService.getUser({ request, response }); + const { full_name, username } = updatedBy; + + const creationDate = new Date().toISOString(); + const post = await caseConfigureService.post({ + client, + attributes: { + ...query, + created_at: creationDate, + created_by: { full_name, username }, + updated_at: null, + updated_by: null, + }, + }); + + return response.ok({ + body: CaseConfigureResponseRt.encode({ ...post.attributes, version: post.version ?? '' }), + }); + } catch (error) { + return response.customError(wrapError(error)); + } + } + ); +} diff --git a/x-pack/plugins/case/server/routes/api/index.ts b/x-pack/plugins/case/server/routes/api/index.ts index cfaef1251bf8c..956f410c9c10a 100644 --- a/x-pack/plugins/case/server/routes/api/index.ts +++ b/x-pack/plugins/case/server/routes/api/index.ts @@ -25,6 +25,11 @@ import { initGetCasesStatusApi } from './cases/status/get_status'; import { initGetTagsApi } from './cases/tags/get_tags'; import { RouteDeps } from './types'; +import { initCaseConfigureGetActionConnector } from './cases/configure/get_connectors'; +import { initCaseConfigurePatchActionConnector } from './cases/configure/patch_connector'; +import { initGetCaseConfigure } from './cases/configure/get_configure'; +import { initPatchCaseConfigure } from './cases/configure/patch_configure'; +import { initPostCaseConfigure } from './cases/configure/post_configure'; export function initCaseApi(deps: RouteDeps) { // Cases @@ -41,6 +46,12 @@ export function initCaseApi(deps: RouteDeps) { initGetAllCommentsApi(deps); initPatchCommentApi(deps); initPostCommentApi(deps); + // Cases Configure + initCaseConfigureGetActionConnector(deps); + initCaseConfigurePatchActionConnector(deps); + initGetCaseConfigure(deps); + initPatchCaseConfigure(deps); + initPostCaseConfigure(deps); // Reporters initGetReportersApi(deps); // Status diff --git a/x-pack/plugins/case/server/routes/api/types.ts b/x-pack/plugins/case/server/routes/api/types.ts index e8668db5d232f..eac259cc69c5a 100644 --- a/x-pack/plugins/case/server/routes/api/types.ts +++ b/x-pack/plugins/case/server/routes/api/types.ts @@ -3,10 +3,12 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ + import { IRouter } from 'src/core/server'; -import { CaseServiceSetup } from '../../services'; +import { CaseConfigureServiceSetup, CaseServiceSetup } from '../../services'; export interface RouteDeps { + caseConfigureService: CaseConfigureServiceSetup; caseService: CaseServiceSetup; router: IRouter; } diff --git a/x-pack/plugins/case/server/routes/api/utils.ts b/x-pack/plugins/case/server/routes/api/utils.ts index 2d73c3aa7976d..04fe426bb2ecc 100644 --- a/x-pack/plugins/case/server/routes/api/utils.ts +++ b/x-pack/plugins/case/server/routes/api/utils.ts @@ -12,6 +12,7 @@ import { SavedObject, SavedObjectsFindResponse, } from 'kibana/server'; + import { CaseRequest, CaseResponse, @@ -21,7 +22,6 @@ import { CommentsResponse, CommentAttributes, } from '../../../common/api'; - import { SortFieldCase } from './types'; export const transformNewCase = ({ @@ -63,7 +63,8 @@ export const transformNewComment = ({ }); export function wrapError(error: any): CustomHttpResponseOptions { - const boom = isBoom(error) ? error : boomify(error); + const options = { statusCode: error.statusCode ?? 500 }; + const boom = isBoom(error) ? error : boomify(error, options); return { body: boom, headers: boom.output.headers, diff --git a/x-pack/plugins/case/server/saved_object_types/configure.ts b/x-pack/plugins/case/server/saved_object_types/configure.ts new file mode 100644 index 0000000000000..8ea6f6bba7d4f --- /dev/null +++ b/x-pack/plugins/case/server/saved_object_types/configure.ts @@ -0,0 +1,51 @@ +/* + * 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 { SavedObjectsType } from 'src/core/server'; + +export const CASE_CONFIGURE_SAVED_OBJECT = 'cases-configure'; + +export const caseConfigureSavedObjectType: SavedObjectsType = { + name: CASE_CONFIGURE_SAVED_OBJECT, + hidden: false, + namespaceAgnostic: false, + mappings: { + properties: { + created_at: { + type: 'date', + }, + created_by: { + properties: { + username: { + type: 'keyword', + }, + full_name: { + type: 'keyword', + }, + }, + }, + connector_id: { + type: 'keyword', + }, + closure_type: { + type: 'keyword', + }, + updated_at: { + type: 'date', + }, + updated_by: { + properties: { + username: { + type: 'keyword', + }, + full_name: { + type: 'keyword', + }, + }, + }, + }, + }, +}; diff --git a/x-pack/plugins/case/server/saved_object_types/index.ts b/x-pack/plugins/case/server/saved_object_types/index.ts index 1e29b9dd98ead..978b3d35ee5c6 100644 --- a/x-pack/plugins/case/server/saved_object_types/index.ts +++ b/x-pack/plugins/case/server/saved_object_types/index.ts @@ -5,4 +5,5 @@ */ export { caseSavedObjectType, CASE_SAVED_OBJECT } from './cases'; +export { caseConfigureSavedObjectType, CASE_CONFIGURE_SAVED_OBJECT } from './configure'; export { caseCommentSavedObjectType, CASE_COMMENT_SAVED_OBJECT } from './comments'; diff --git a/x-pack/plugins/case/server/services/configure/index.ts b/x-pack/plugins/case/server/services/configure/index.ts new file mode 100644 index 0000000000000..42c0dc293a648 --- /dev/null +++ b/x-pack/plugins/case/server/services/configure/index.ts @@ -0,0 +1,99 @@ +/* + * 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 { + Logger, + SavedObject, + SavedObjectsClientContract, + SavedObjectsFindResponse, + SavedObjectsUpdateResponse, +} from 'kibana/server'; + +import { CasesConfigureAttributes, SavedObjectFindOptions } from '../../../common/api'; +import { CASE_CONFIGURE_SAVED_OBJECT } from '../../saved_object_types'; + +interface ClientArgs { + client: SavedObjectsClientContract; +} + +interface GetCaseConfigureArgs extends ClientArgs { + caseConfigureId: string; +} +interface FindCaseConfigureArgs extends ClientArgs { + options?: SavedObjectFindOptions; +} + +interface PostCaseConfigureArgs extends ClientArgs { + attributes: CasesConfigureAttributes; +} + +interface PatchCaseConfigureArgs extends ClientArgs { + caseConfigureId: string; + updatedAttributes: Partial; +} + +export interface CaseConfigureServiceSetup { + delete(args: GetCaseConfigureArgs): Promise<{}>; + get(args: GetCaseConfigureArgs): Promise>; + find(args: FindCaseConfigureArgs): Promise>; + patch( + args: PatchCaseConfigureArgs + ): Promise>; + post(args: PostCaseConfigureArgs): Promise>; +} + +export class CaseConfigureService { + constructor(private readonly log: Logger) {} + public setup = async (): Promise => ({ + delete: async ({ client, caseConfigureId }: GetCaseConfigureArgs) => { + try { + this.log.debug(`Attempting to DELETE case configure ${caseConfigureId}`); + return await client.delete(CASE_CONFIGURE_SAVED_OBJECT, caseConfigureId); + } catch (error) { + this.log.debug(`Error on DELETE case configure ${caseConfigureId}: ${error}`); + throw error; + } + }, + get: async ({ client, caseConfigureId }: GetCaseConfigureArgs) => { + try { + this.log.debug(`Attempting to GET case configuration ${caseConfigureId}`); + return await client.get(CASE_CONFIGURE_SAVED_OBJECT, caseConfigureId); + } catch (error) { + this.log.debug(`Error on GET case configuration ${caseConfigureId}: ${error}`); + throw error; + } + }, + find: async ({ client, options }: FindCaseConfigureArgs) => { + try { + this.log.debug(`Attempting to find all case configuration`); + return await client.find({ ...options, type: CASE_CONFIGURE_SAVED_OBJECT }); + } catch (error) { + this.log.debug(`Attempting to find all case configuration`); + throw error; + } + }, + post: async ({ client, attributes }: PostCaseConfigureArgs) => { + try { + this.log.debug(`Attempting to POST a new case configuration`); + return await client.create(CASE_CONFIGURE_SAVED_OBJECT, { ...attributes }); + } catch (error) { + this.log.debug(`Error on POST a new case configuration: ${error}`); + throw error; + } + }, + patch: async ({ client, caseConfigureId, updatedAttributes }: PatchCaseConfigureArgs) => { + try { + this.log.debug(`Attempting to UPDATE case configuration ${caseConfigureId}`); + return await client.update(CASE_CONFIGURE_SAVED_OBJECT, caseConfigureId, { + ...updatedAttributes, + }); + } catch (error) { + this.log.debug(`Error on UPDATE case configuration ${caseConfigureId}: ${error}`); + throw error; + } + }, + }); +} diff --git a/x-pack/plugins/case/server/services/index.ts b/x-pack/plugins/case/server/services/index.ts index ccb07280028b5..4bbffddf63251 100644 --- a/x-pack/plugins/case/server/services/index.ts +++ b/x-pack/plugins/case/server/services/index.ts @@ -23,6 +23,8 @@ import { CASE_SAVED_OBJECT, CASE_COMMENT_SAVED_OBJECT } from '../saved_object_ty import { readReporters } from './reporters/read_reporters'; import { readTags } from './tags/read_tags'; +export { CaseConfigureService, CaseConfigureServiceSetup } from './configure'; + interface ClientArgs { client: SavedObjectsClientContract; } diff --git a/x-pack/legacy/plugins/graph/README.md b/x-pack/plugins/graph/README.md similarity index 93% rename from x-pack/legacy/plugins/graph/README.md rename to x-pack/plugins/graph/README.md index f402b35bba49f..9cc2617abe94c 100644 --- a/x-pack/legacy/plugins/graph/README.md +++ b/x-pack/plugins/graph/README.md @@ -8,7 +8,7 @@ Graph shows only up in the side bar if your server is running on a platinum or t * Run tests `node x-pack/scripts/jest.js --watch plugins/graph` * Run type check `node scripts/type_check.js --project=x-pack/tsconfig.json` -* Run linter `node scripts/eslint.js x-pack/legacy/plugins/graph` +* Run linter `node scripts/eslint.js x-pack/plugins/graph` * Run functional tests (make sure to stop dev server) * Server `cd x-pack && node ./scripts/functional_tests_server.js` * Tests `cd x-pack && node ../scripts/functional_test_runner.js --config ./test/functional/config.js --grep=graph` @@ -21,7 +21,6 @@ Currently most of the state handling is done by a central angular controller. Th * `angular/` contains all code using javascript and angular. Rewriting this code in typescript and react is currently ongoing. When the migration is finished, this folder will go away * `components/` contains react components for various parts of the interface. Components can hold local UI state (e.g. current form data), everything else should be passed in from the caller. Styles should reside in a component-specific stylesheet -* `hacks/` contains files that need to run before the actual app is started. When moving to the new platform, this folder will go away. * `services/` contains functions that encapsule other parts of Kibana. Stateful dependencies are passed in from the outside. Components should not rely on services directly but have callbacks passed in. Once the migration to redux/saga is complete, only sagas will use services. * `helpers/` contains side effect free helper functions that can be imported and used from components and services * `state_management/` contains reducers, action creators, selectors and sagas. It also exports the central store creator diff --git a/x-pack/plugins/graph/kibana.json b/x-pack/plugins/graph/kibana.json index 0c77b446fa28d..cf3a5fab92f56 100644 --- a/x-pack/plugins/graph/kibana.json +++ b/x-pack/plugins/graph/kibana.json @@ -4,7 +4,7 @@ "kibanaVersion": "kibana", "server": true, "ui": true, - "requiredPlugins": ["licensing"], + "requiredPlugins": ["licensing", "data", "navigation"], "optionalPlugins": ["home"], "configPath": ["xpack", "graph"] } diff --git a/x-pack/legacy/plugins/graph/public/_main.scss b/x-pack/plugins/graph/public/_main.scss similarity index 100% rename from x-pack/legacy/plugins/graph/public/_main.scss rename to x-pack/plugins/graph/public/_main.scss diff --git a/x-pack/legacy/plugins/graph/public/_mixins.scss b/x-pack/plugins/graph/public/_mixins.scss similarity index 100% rename from x-pack/legacy/plugins/graph/public/_mixins.scss rename to x-pack/plugins/graph/public/_mixins.scss diff --git a/x-pack/legacy/plugins/graph/public/angular/graph_client_workspace.d.ts b/x-pack/plugins/graph/public/angular/graph_client_workspace.d.ts similarity index 100% rename from x-pack/legacy/plugins/graph/public/angular/graph_client_workspace.d.ts rename to x-pack/plugins/graph/public/angular/graph_client_workspace.d.ts diff --git a/x-pack/legacy/plugins/graph/public/angular/graph_client_workspace.js b/x-pack/plugins/graph/public/angular/graph_client_workspace.js similarity index 85% rename from x-pack/legacy/plugins/graph/public/angular/graph_client_workspace.js rename to x-pack/plugins/graph/public/angular/graph_client_workspace.js index 14d4248dc664d..a7d98a42404ec 100644 --- a/x-pack/legacy/plugins/graph/public/angular/graph_client_workspace.js +++ b/x-pack/plugins/graph/public/angular/graph_client_workspace.js @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +import $ from 'jquery'; + // Kibana wrapper const d3 = require('d3'); @@ -79,7 +81,7 @@ module.exports = (function() { self.redo = reverseOperation.undo; } - function GroupOperation(receiver, orphan, vm) { + function GroupOperation(receiver, orphan) { const self = this; self.receiver = receiver; self.orphan = orphan; @@ -91,7 +93,7 @@ module.exports = (function() { }; } - function UnGroupOperation(parent, child, vm) { + function UnGroupOperation(parent, child) { const self = this; self.parent = parent; self.child = child; @@ -152,9 +154,7 @@ module.exports = (function() { if (lastOps) { this.stopLayout(); this.redoLog.push(lastOps); - for (const i in lastOps) { - lastOps[i].undo(); - } + lastOps.forEach(ops => ops.undo()); this.runLayout(); } }; @@ -163,29 +163,22 @@ module.exports = (function() { if (lastOps) { this.stopLayout(); this.undoLog.push(lastOps); - for (const i in lastOps) { - lastOps[i].redo(); - } + lastOps.forEach(ops => ops.redo()); this.runLayout(); } }; //Determines if 2 nodes are connected via an edge this.areLinked = function(a, b) { - if (a == b) return true; - const allEdges = this.edges; - for (const e in allEdges) { - if (e.source == a) { - if (e.target == b) { - return true; - } + if (a === b) return true; + this.edges.forEach(e => { + if (e.source === a && e.target === b) { + return true; } - if (e.source == b) { - if (e.target == a) { - return true; - } + if (e.source === b && e.target === a) { + return true; } - } + }); return false; }; @@ -193,47 +186,43 @@ module.exports = (function() { this.selectAll = function() { self.selectedNodes = []; - for (const n in self.nodes) { - const node = self.nodes[n]; - if (node.parent == undefined) { + self.nodes.forEach(node => { + if (node.parent === undefined) { node.isSelected = true; self.selectedNodes.push(node); } else { node.isSelected = false; } - } + }); }; this.selectNone = function() { self.selectedNodes = []; - for (const n in self.nodes) { - const node = self.nodes[n]; + self.nodes.forEach(node => { node.isSelected = false; - } + }); }; this.selectInvert = function() { self.selectedNodes = []; - for (const n in self.nodes) { - const node = self.nodes[n]; - if (node.parent != undefined) { - continue; + self.nodes.forEach(node => { + if (node.parent !== undefined) { + return; } node.isSelected = !node.isSelected; if (node.isSelected) { self.selectedNodes.push(node); } - } + }); }; this.selectNodes = function(nodes) { - for (const n in nodes) { - const node = nodes[n]; + nodes.forEach(node => { node.isSelected = true; if (self.selectedNodes.indexOf(node) < 0) { self.selectedNodes.push(node); } - } + }); }; this.selectNode = function(node) { @@ -247,29 +236,27 @@ module.exports = (function() { let allAndGrouped = self.returnUnpackedGroupeds(self.selectedNodes); // Nothing selected so process all nodes - if (allAndGrouped.length == 0) { + if (allAndGrouped.length === 0) { allAndGrouped = self.nodes.slice(0); } const undoOperations = []; - for (const i in allAndGrouped) { - const node = allAndGrouped[i]; + allAndGrouped.forEach(node => { //We set selected to false because despite being deleted, node objects sit in an undo log node.isSelected = false; delete self.nodesMap[node.id]; undoOperations.push(new ReverseOperation(new AddNodeOperation(node, self))); - } + }); self.arrRemoveAll(self.nodes, allAndGrouped); self.arrRemoveAll(self.selectedNodes, allAndGrouped); const danglingEdges = self.edges.filter(function(edge) { return self.nodes.indexOf(edge.source) < 0 || self.nodes.indexOf(edge.target) < 0; }); - for (const i in danglingEdges) { - const edge = danglingEdges[i]; + danglingEdges.forEach(edge => { delete self.edgesMap[edge.id]; undoOperations.push(new ReverseOperation(new AddEdgeOperation(edge, self))); - } + }); self.addUndoLogEntry(undoOperations); self.arrRemoveAll(self.edges, danglingEdges); self.runLayout(); @@ -277,8 +264,7 @@ module.exports = (function() { this.selectNeighbours = function() { const newSelections = []; - for (const n in self.edges) { - const edge = self.edges[n]; + self.edges.forEach(edge => { if (!edge.topSrc.isSelected) { if (self.selectedNodes.indexOf(edge.topTarget) >= 0) { if (newSelections.indexOf(edge.topSrc) < 0) { @@ -293,18 +279,17 @@ module.exports = (function() { } } } - } - for (const i in newSelections) { - const newlySelectedNode = newSelections[i]; + }); + newSelections.forEach(newlySelectedNode => { self.selectedNodes.push(newlySelectedNode); newlySelectedNode.isSelected = true; - } + }); }; this.selectNone = function() { - for (const n in self.selectedNodes) { - self.selectedNodes[n].isSelected = false; - } + self.selectedNodes.forEach(node => { + node.isSelected = false; + }); self.selectedNodes = []; }; @@ -318,30 +303,25 @@ module.exports = (function() { }; this.colorSelected = function(colorNum) { - const selections = self.getAllSelectedNodes(); - for (const i in selections) { - selections[i].color = colorNum; - } + self.getAllSelectedNodes().forEach(node => { + node.color = colorNum; + }); }; this.getSelectionsThatAreGrouped = function() { const result = []; - const selections = self.selectedNodes; - for (const i in selections) { - const node = selections[i]; + self.selectedNodes.forEach(node => { if (node.numChildren > 0) { result.push(node); } - } + }); return result; }; this.ungroupSelection = function() { - const selections = self.getSelectionsThatAreGrouped(); - for (const i in selections) { - const node = selections[i]; + self.getSelectionsThatAreGrouped().forEach(node => { self.ungroup(node); - } + }); }; this.toggleNodeSelection = function(node) { @@ -371,7 +351,7 @@ module.exports = (function() { if (result.indexOf(topLevelTarget) >= 0) { //visible top-level node is selected - add all nesteds starting from bottom up let target = edge.target; - while (target.parent != undefined) { + while (target.parent !== undefined) { if (result.indexOf(target) < 0) { result.push(target); } @@ -382,7 +362,7 @@ module.exports = (function() { if (result.indexOf(topLevelSource) >= 0) { //visible top-level node is selected - add all nesteds starting from bottom up let source = edge.source; - while (source.parent != undefined) { + while (source.parent !== undefined) { if (result.indexOf(source) < 0) { result.push(source); } @@ -425,22 +405,21 @@ module.exports = (function() { this.getNeighbours = function(node) { const neighbourNodes = []; - for (const e in self.edges) { - const edge = self.edges[e]; - if (edge.topSrc == edge.topTarget) { - continue; + self.edges.forEach(edge => { + if (edge.topSrc === edge.topTarget) { + return; } - if (edge.topSrc == node) { + if (edge.topSrc === node) { if (neighbourNodes.indexOf(edge.topTarget) < 0) { neighbourNodes.push(edge.topTarget); } } - if (edge.topTarget == node) { + if (edge.topTarget === node) { if (neighbourNodes.indexOf(edge.topSrc) < 0) { neighbourNodes.push(edge.topSrc); } } - } + }); return neighbourNodes; }; @@ -448,7 +427,7 @@ module.exports = (function() { this.buildNodeQuery = function(topLevelNode) { let containedNodes = [topLevelNode]; containedNodes = self.returnUnpackedGroupeds(containedNodes); - if (containedNodes.length == 1) { + if (containedNodes.length === 1) { //Simple case - return a single-term query const tq = {}; tq[topLevelNode.data.field] = topLevelNode.data.term; @@ -457,17 +436,16 @@ module.exports = (function() { }; } const termsByField = {}; - for (const i in containedNodes) { - const node = containedNodes[i]; + containedNodes.forEach(node => { let termsList = termsByField[node.data.field]; if (!termsList) { termsList = []; termsByField[node.data.field] = termsList; } termsList.push(node.data.term); - } + }); //Single field case - if (Object.keys(termsByField).length == 1) { + if (Object.keys(termsByField).length === 1) { return { terms: termsByField, }; @@ -479,11 +457,13 @@ module.exports = (function() { }, }; for (const field in termsByField) { - const tq = {}; - tq[field] = termsByField[field]; - q.bool.should.push({ - terms: tq, - }); + if (termsByField.hasOwnProperty(field)) { + const tq = {}; + tq[field] = termsByField[field]; + q.bool.should.push({ + terms: tq, + }); + } } return q; }; @@ -503,39 +483,40 @@ module.exports = (function() { // is potentially a reduced set of nodes if the client has used any // grouping of nodes into parent nodes. const effectiveEdges = []; - const edges = self.edges; - for (const e in edges) { - const edge = edges[e]; + self.edges.forEach(edge => { let topSrc = edge.source; let topTarget = edge.target; - while (topSrc.parent != undefined) { + while (topSrc.parent !== undefined) { topSrc = topSrc.parent; } - while (topTarget.parent != undefined) { + while (topTarget.parent !== undefined) { topTarget = topTarget.parent; } edge.topSrc = topSrc; edge.topTarget = topTarget; - if (topSrc != topTarget) { + if (topSrc !== topTarget) { effectiveEdges.push({ source: topSrc, target: topTarget, }); } - } + }); const visibleNodes = self.nodes.filter(function(n) { - return n.parent == undefined; + return n.parent === undefined; }); //reset then roll-up all the counts const allNodes = self.nodes; - for (const n in allNodes) { - const node = allNodes[n]; + allNodes.forEach(node => { node.numChildren = 0; - } + }); + for (const n in allNodes) { + if (!allNodes.hasOwnProperty(n)) { + continue; + } let node = allNodes[n]; - while (node.parent != undefined) { + while (node.parent !== undefined) { node = node.parent; node.numChildren = node.numChildren + 1; } @@ -551,36 +532,34 @@ module.exports = (function() { .theta(0.99) .alpha(0.5) .size([800, 600]) - .on('tick', function(e) { + .on('tick', function() { const nodeArray = self.nodes; let hasRollups = false; //Update the position of all "top level nodes" - for (const i in nodeArray) { - const n = nodeArray[i]; + nodeArray.forEach(n => { //Code to support roll-ups - if (n.parent == undefined) { + if (n.parent === undefined) { n.kx = n.x; n.ky = n.y; } else { hasRollups = true; } - } + }); if (hasRollups) { - for (const i in nodeArray) { - const n = nodeArray[i]; + nodeArray.forEach(n => { //Code to support roll-ups - if (n.parent != undefined) { + if (n.parent !== undefined) { // Is a grouped node - inherit parent's position so edges point into parent // d3 thinks it has moved it to x and y but we have final say using kx and ky. let topLevelNode = n.parent; - while (topLevelNode.parent != undefined) { + while (topLevelNode.parent !== undefined) { topLevelNode = topLevelNode.parent; } n.kx = topLevelNode.x; n.ky = topLevelNode.y; } - } + }); } if (self.changeHandler) { // Hook to allow any client to respond to position changes @@ -597,11 +576,11 @@ module.exports = (function() { this.groupSelections = function(node) { const ops = []; self.nodes.forEach(function(otherNode) { - if (otherNode != node && otherNode.isSelected && otherNode.parent == undefined) { + if (otherNode !== node && otherNode.isSelected && otherNode.parent === undefined) { otherNode.parent = node; otherNode.isSelected = false; self.arrRemove(self.selectedNodes, otherNode); - ops.push(new GroupOperation(node, otherNode, self)); + ops.push(new GroupOperation(node, otherNode)); } }); self.selectNone(); @@ -614,11 +593,11 @@ module.exports = (function() { const neighbours = self.getNeighbours(node); const ops = []; neighbours.forEach(function(otherNode) { - if (otherNode != node && otherNode.parent == undefined) { + if (otherNode !== node && otherNode.parent === undefined) { otherNode.parent = node; otherNode.isSelected = false; self.arrRemove(self.selectedNodes, otherNode); - ops.push(new GroupOperation(node, otherNode, self)); + ops.push(new GroupOperation(node, otherNode)); } }); self.addUndoLogEntry(ops); @@ -633,11 +612,11 @@ module.exports = (function() { const selClone = self.selectedNodes.slice(); const ops = []; selClone.forEach(function(otherNode) { - if (otherNode != targetNode && otherNode.parent == undefined) { + if (otherNode !== targetNode && otherNode.parent === undefined) { otherNode.parent = targetNode; otherNode.isSelected = false; self.arrRemove(self.selectedNodes, otherNode); - ops.push(new GroupOperation(targetNode, otherNode, self)); + ops.push(new GroupOperation(targetNode, otherNode)); } }); self.addUndoLogEntry(ops); @@ -647,9 +626,9 @@ module.exports = (function() { this.ungroup = function(node) { const ops = []; self.nodes.forEach(function(other) { - if (other.parent == node) { + if (other.parent === node) { other.parent = undefined; - ops.push(new UnGroupOperation(node, other, self)); + ops.push(new UnGroupOperation(node, other)); } }); self.addUndoLogEntry(ops); @@ -669,12 +648,11 @@ module.exports = (function() { danglingEdges.push(edge); } }); - for (const n in selection) { - const node = selection[n]; + selection.forEach(node => { delete self.nodesMap[node.id]; self.blacklistedNodes.push(node); node.isSelected = false; - } + }); self.arrRemoveAll(self.nodes, selection); self.arrRemoveAll(self.edges, danglingEdges); self.selectedNodes = []; @@ -722,9 +700,7 @@ module.exports = (function() { for (let hopNum = 0; hopNum < numHops; hopNum++) { const arr = []; - for (const f in fieldsChoice) { - const field = fieldsChoice[f].name; - const hopSize = fieldsChoice[f].hopSize; + fieldsChoice.forEach(({ name: field, hopSize }) => { const excludes = excludeNodesByField[field]; const stepField = { field: field, @@ -735,7 +711,7 @@ module.exports = (function() { stepField.exclude = excludes; } arr.push(stepField); - } + }); step.vertices = arr; if (hopNum < numHops - 1) { // if (s < (stepSizes.length - 1)) { @@ -814,8 +790,7 @@ module.exports = (function() { //Remove nodes we already have const dedupedNodes = []; - for (const o in newData.nodes) { - const node = newData.nodes[o]; + newData.nodes.forEach(node => { //Assign an ID node.id = self.makeNodeId(node.field, node.term); if (!this.nodesMap[node.id]) { @@ -825,14 +800,13 @@ module.exports = (function() { } dedupedNodes.push(node); } - } + }); if (dedupedNodes.length > 0 && this.options.nodeLabeller) { // A hook for client code to attach labels etc to newly introduced nodes. this.options.nodeLabeller(dedupedNodes); } - for (const o in dedupedNodes) { - const dedupedNode = dedupedNodes[o]; + dedupedNodes.forEach(dedupedNode => { let label = dedupedNode.term; if (dedupedNode.label) { label = dedupedNode.label; @@ -856,10 +830,9 @@ module.exports = (function() { this.nodes.push(node); lastOps.push(new AddNodeOperation(node, self)); this.nodesMap[node.id] = node; - } + }); - for (const o in newData.edges) { - const edge = newData.edges[o]; + newData.edges.forEach(edge => { const src = newData.nodes[edge.source]; const target = newData.nodes[edge.target]; edge.id = this.makeEdgeId(src.id, target.id); @@ -873,7 +846,7 @@ module.exports = (function() { existingEdge.weight = Math.max(existingEdge.weight, edge.weight); //TODO update width too? existingEdge.doc_count = Math.max(existingEdge.doc_count, edge.doc_count); - continue; + return; } const newEdge = { source: srcWrapperObj, @@ -890,7 +863,7 @@ module.exports = (function() { this.edgesMap[newEdge.id] = newEdge; this.edges.push(newEdge); lastOps.push(new AddEdgeOperation(newEdge, self)); - } + }); if (lastOps.length > 0) { self.addUndoLogEntry(lastOps); @@ -907,7 +880,7 @@ module.exports = (function() { self.arrRemove(self.selectedNodes, child); } child.parent = parent; - self.addUndoLogEntry([new GroupOperation(parent, child, self)]); + self.addUndoLogEntry([new GroupOperation(parent, child)]); self.runLayout(); }; @@ -922,7 +895,7 @@ module.exports = (function() { this.expandSelecteds = function(targetOptions = {}) { let startNodes = self.getAllSelectedNodes(); - if (startNodes.length == 0) { + if (startNodes.length === 0) { startNodes = self.nodes; } const clone = startNodes.slice(); @@ -1000,11 +973,13 @@ module.exports = (function() { const primaryVertices = []; const secondaryVertices = []; for (const fieldName in nodesByField) { - primaryVertices.push({ - field: fieldName, - include: nodesByField[fieldName], - min_doc_count: parseInt(self.options.exploreControls.minDocCount), - }); + if (nodesByField.hasOwnProperty(fieldName)) { + primaryVertices.push({ + field: fieldName, + include: nodesByField[fieldName], + min_doc_count: parseInt(self.options.exploreControls.minDocCount), + }); + } } let targetFields = this.options.vertex_fields; @@ -1013,11 +988,11 @@ module.exports = (function() { } //Identify target fields - for (const f in targetFields) { - const fieldName = targetFields[f].name; + targetFields.forEach(targetField => { + const fieldName = targetField.name; // Sometimes the target field is disabled from loading new hops so we need to use the last valid figure const hopSize = - targetFields[f].hopSize > 0 ? targetFields[f].hopSize : targetFields[f].lastValidHopSize; + targetField.hopSize > 0 ? targetField.hopSize : targetField.lastValidHopSize; const fieldHop = { field: fieldName, @@ -1026,7 +1001,7 @@ module.exports = (function() { }; fieldHop.exclude = excludeNodesByField[fieldName]; secondaryVertices.push(fieldHop); - } + }); const request = { controls: self.buildControls(), @@ -1038,33 +1013,27 @@ module.exports = (function() { self.lastRequest = JSON.stringify(request, null, '\t'); graphExplorer(self.options.indexName, request, function(data) { self.lastResponse = JSON.stringify(data, null, '\t'); - const nodes = []; const edges = []; //Label fields with a field number for CSS styling - for (const n in data.vertices) { - const node = data.vertices[n]; - for (const f in targetFields) { - const fieldDef = targetFields[f]; - if (node.field == fieldDef.name) { + data.vertices.forEach(node => { + targetFields.some(fieldDef => { + if (node.field === fieldDef.name) { node.color = fieldDef.color; node.icon = fieldDef.icon; node.fieldDef = fieldDef; - break; + return true; } - } - } + return false; + }); + }); // Size the edges based on the maximum weight const minLineSize = 2; const maxLineSize = 10; let maxEdgeWeight = 0.00000001; - for (const e in data.connections) { - const edge = data.connections[e]; + data.connections.forEach(edge => { maxEdgeWeight = Math.max(maxEdgeWeight, edge.weight); - } - for (const e in data.connections) { - const edge = data.connections[e]; edges.push({ source: edge.source, target: edge.target, @@ -1072,7 +1041,7 @@ module.exports = (function() { weight: edge.weight, width: Math.max(minLineSize, (edge.weight / maxEdgeWeight) * maxLineSize), }); - } + }); // Add the new nodes and edges into the existing workspace's graph self.mergeGraph({ @@ -1087,8 +1056,7 @@ module.exports = (function() { let trimmedEdges = []; const maxNumEdgesToReturn = 5; //Trim here to just the new edges that are most interesting. - for (const o in newEdges) { - const edge = newEdges[o]; + newEdges.forEach(edge => { const src = newNodes[edge.source]; const target = newNodes[edge.target]; const srcId = src.field + '..' + src.term; @@ -1097,25 +1065,25 @@ module.exports = (function() { const existingSrcNode = self.nodesMap[srcId]; const existingTargetNode = self.nodesMap[targetId]; if (existingSrcNode != null && existingTargetNode != null) { - if (existingSrcNode.parent != undefined && existingTargetNode.parent != undefined) { + if (existingSrcNode.parent !== undefined && existingTargetNode.parent !== undefined) { // both nodes are rolled-up and grouped so this edge would not be a visible // change to the graph - lose it in favour of any other visible ones. - continue; + return; } } else { console.log('Error? Missing nodes ' + srcId + ' or ' + targetId, self.nodesMap); - continue; + return; } const existingEdge = self.edgesMap[id]; if (existingEdge) { existingEdge.weight = Math.max(existingEdge.weight, edge.weight); existingEdge.doc_count = Math.max(existingEdge.doc_count, edge.doc_count); - continue; + return; } else { trimmedEdges.push(edge); } - } + }); if (trimmedEdges.length > maxNumEdgesToReturn) { //trim to only the most interesting ones trimmedEdges.sort(function(a, b) { @@ -1132,12 +1100,11 @@ module.exports = (function() { if (!startNodes) { nodes = self.nodes; } - for (const bs in nodes) { - const node = nodes[bs]; - if (node.parent == undefined) { + nodes.forEach(node => { + if (node.parent === undefined) { shoulds.push(self.buildNodeQuery(node)); } - } + }); return { bool: { should: shoulds, @@ -1256,7 +1223,7 @@ module.exports = (function() { const t2 = keyedBuckets[ids[1]].doc_count; const t1AndT2 = bucket.doc_count; // Calc the significant_terms score to prioritize selection of interesting links - bucket.weight = self.JLHScore( + bucket.weight = self.jLHScore( t1AndT2, Math.max(t1, t2), Math.min(t1, t2), @@ -1276,7 +1243,7 @@ module.exports = (function() { return; } const ids = bucket.key.split('|'); - if (ids.length == 2) { + if (ids.length === 2) { // Bucket represents an edge const srcNode = nodesForLinking[ids[0]]; const targetNode = nodesForLinking[ids[1]]; @@ -1340,16 +1307,18 @@ module.exports = (function() { txtsByFieldType[node.data.field] = txt; }); for (const field in txtsByFieldType) { - likeQueries.push({ - more_like_this: { - like: txtsByFieldType[field], - min_term_freq: 1, - minimum_should_match: '20%', - min_doc_freq: 1, - boost_terms: 2, - max_query_terms: 25, - }, - }); + if (txtsByFieldType.hasOwnProperty(field)) { + likeQueries.push({ + more_like_this: { + like: txtsByFieldType[field], + min_term_freq: 1, + minimum_should_match: '20%', + min_doc_freq: 1, + boost_terms: 2, + max_query_terms: 25, + }, + }); + } } const excludeNodesByField = {}; @@ -1397,10 +1366,10 @@ module.exports = (function() { }; this.getSelectedIntersections = function(callback) { - if (self.selectedNodes.length == 0) { + if (self.selectedNodes.length === 0) { return self.getAllIntersections(callback, self.nodes); } - if (self.selectedNodes.length == 1) { + if (self.selectedNodes.length === 1) { const selectedNode = self.selectedNodes[0]; const neighbourNodes = self.getNeighbours(selectedNode); neighbourNodes.push(selectedNode); @@ -1409,7 +1378,7 @@ module.exports = (function() { return self.getAllIntersections(callback, self.getAllSelectedNodes()); }; - this.JLHScore = function(subsetFreq, subsetSize, supersetFreq, supersetSize) { + this.jLHScore = function(subsetFreq, subsetSize, supersetFreq, supersetSize) { const subsetProbability = subsetFreq / subsetSize; const supersetProbability = supersetFreq / supersetSize; @@ -1432,7 +1401,7 @@ module.exports = (function() { this.getAllIntersections = function(callback, nodes) { //Ensure these are all top-level nodes only nodes = nodes.filter(function(n) { - return n.parent == undefined; + return n.parent === undefined; }); const allQueries = nodes.map(function(node) { @@ -1468,44 +1437,42 @@ module.exports = (function() { }, }, }; - for (const n in allQueries) { + allQueries.forEach((query, n) => { // Add aggs to get intersection stats with root node. - request.aggs.sources.filters.filters['bg' + n] = allQueries[n]; - request.aggs.sources.aggs.targets.filters.filters['fg' + n] = allQueries[n]; - } - const dataForServer = JSON.stringify(request); + request.aggs.sources.filters.filters['bg' + n] = query; + request.aggs.sources.aggs.targets.filters.filters['fg' + n] = query; + }); searcher(self.options.indexName, request, function(data) { const termIntersects = []; const fullDocCounts = []; const allDocCount = data.aggregations.all.doc_count; // Gather the background stats for all nodes. - for (const n in nodes) { + nodes.forEach((rootNode, n) => { fullDocCounts.push(data.aggregations.sources.buckets['bg' + n].doc_count); - } - for (const n in nodes) { - const rootNode = nodes[n]; + }); + + nodes.forEach((rootNode, n) => { const t1 = fullDocCounts[n]; const baseAgg = data.aggregations.sources.buckets['bg' + n].targets.buckets; - for (const l in nodes) { + nodes.forEach((leafNode, l) => { const t2 = fullDocCounts[l]; - const leafNode = nodes[l]; - if (l == n) { - continue; + if (l === n) { + return; } if (t1 > t2) { // We should get the same stats for t2->t1 from the t1->t2 bucket path - continue; + return; } - if (t1 == t2) { + if (t1 === t2) { if (rootNode.id > leafNode.id) { // We should get the same stats for t2->t1 from the t1->t2 bucket path - continue; + return; } } const t1AndT2 = baseAgg['fg' + l].doc_count; - if (t1AndT2 == 0) { - continue; + if (t1AndT2 === 0) { + return; } const neighbourNode = nodes[l]; let t1Label = rootNode.data.label; @@ -1521,7 +1488,7 @@ module.exports = (function() { // var mergeConfidence=t1AndT2/t1; // So using Significance heuristic instead - const mergeConfidence = self.JLHScore(t1AndT2, t2, t1, allDocCount); + const mergeConfidence = self.jLHScore(t1AndT2, t2, t1, allDocCount); const termIntersect = { id1: rootNode.id, @@ -1536,16 +1503,16 @@ module.exports = (function() { overlap: t1AndT2, }; termIntersects.push(termIntersect); - } - } + }); + }); termIntersects.sort(function(a, b) { - if (b.mergeConfidence != a.mergeConfidence) { + if (b.mergeConfidence !== a.mergeConfidence) { return b.mergeConfidence - a.mergeConfidence; } // If of equal similarity use the size of the overlap as // a measure of magnitude/significance for tie-breaker. - if (b.overlap != a.overlap) { + if (b.overlap !== a.overlap) { return b.overlap - a.overlap; } //All other things being equal we now favour where t2 NOT t1 is small. @@ -1563,32 +1530,28 @@ module.exports = (function() { self.lastRequest = JSON.stringify(request, null, '\t'); graphExplorer(self.options.indexName, request, function(data) { self.lastResponse = JSON.stringify(data, null, '\t'); - const nodes = []; const edges = []; //Label the nodes with field number for CSS styling - for (const n in data.vertices) { - const node = data.vertices[n]; - for (const f in self.options.vertex_fields) { - const fieldDef = self.options.vertex_fields[f]; - if (node.field == fieldDef.name) { + data.vertices.forEach(node => { + self.options.vertex_fields.some(fieldDef => { + if (node.field === fieldDef.name) { node.color = fieldDef.color; node.icon = fieldDef.icon; node.fieldDef = fieldDef; - break; + return true; } - } - } + return false; + }); + }); //Size the edges depending on weight const minLineSize = 2; const maxLineSize = 10; let maxEdgeWeight = 0.00000001; - for (const e in data.connections) { - const edge = data.connections[e]; + data.connections.forEach(edge => { maxEdgeWeight = Math.max(maxEdgeWeight, edge.weight); - } - for (const e in data.connections) { - const edge = data.connections[e]; + }); + data.connections.forEach(edge => { edges.push({ source: edge.source, target: edge.target, @@ -1596,7 +1559,7 @@ module.exports = (function() { weight: edge.weight, width: Math.max(minLineSize, (edge.weight / maxEdgeWeight) * maxLineSize), }); - } + }); self.mergeGraph( { diff --git a/x-pack/legacy/plugins/graph/public/angular/graph_client_workspace.test.js b/x-pack/plugins/graph/public/angular/graph_client_workspace.test.js similarity index 97% rename from x-pack/legacy/plugins/graph/public/angular/graph_client_workspace.test.js rename to x-pack/plugins/graph/public/angular/graph_client_workspace.test.js index 6179467966764..6f81a443086c0 100644 --- a/x-pack/legacy/plugins/graph/public/angular/graph_client_workspace.test.js +++ b/x-pack/plugins/graph/public/angular/graph_client_workspace.test.js @@ -77,7 +77,7 @@ describe('graphui-workspace', function() { }, ], }; - workspace.simpleSearch('myquery', {}, 2); + workspace.simpleSearch('myquery', undefined, 2); expect(workspace.nodes.length).toEqual(2); expect(workspace.edges.length).toEqual(1); @@ -119,7 +119,7 @@ describe('graphui-workspace', function() { }, ], }; - workspace.simpleSearch('myquery', {}, 2); + workspace.simpleSearch('myquery', undefined, 2); expect(workspace.nodes.length).toEqual(2); expect(workspace.edges.length).toEqual(1); @@ -201,7 +201,7 @@ describe('graphui-workspace', function() { }, ], }; - workspace.simpleSearch('myquery', {}, 2); + workspace.simpleSearch('myquery', undefined, 2); expect(workspace.selectedNodes.length).toEqual(0); @@ -264,7 +264,7 @@ describe('graphui-workspace', function() { }, ], }; - workspace.simpleSearch('myquery', {}, 2); + workspace.simpleSearch('myquery', undefined, 2); expect(workspace.nodes.length).toEqual(2); @@ -320,7 +320,7 @@ describe('graphui-workspace', function() { }, ], }; - workspace.simpleSearch('myquery', {}, 2); + workspace.simpleSearch('myquery', undefined, 2); expect(workspace.nodes.length).toEqual(2); diff --git a/x-pack/legacy/plugins/graph/public/angular/templates/_graph.scss b/x-pack/plugins/graph/public/angular/templates/_graph.scss similarity index 100% rename from x-pack/legacy/plugins/graph/public/angular/templates/_graph.scss rename to x-pack/plugins/graph/public/angular/templates/_graph.scss diff --git a/x-pack/legacy/plugins/graph/public/angular/templates/_index.scss b/x-pack/plugins/graph/public/angular/templates/_index.scss similarity index 100% rename from x-pack/legacy/plugins/graph/public/angular/templates/_index.scss rename to x-pack/plugins/graph/public/angular/templates/_index.scss diff --git a/x-pack/legacy/plugins/graph/public/angular/templates/_inspect.scss b/x-pack/plugins/graph/public/angular/templates/_inspect.scss similarity index 100% rename from x-pack/legacy/plugins/graph/public/angular/templates/_inspect.scss rename to x-pack/plugins/graph/public/angular/templates/_inspect.scss diff --git a/x-pack/legacy/plugins/graph/public/angular/templates/_sidebar.scss b/x-pack/plugins/graph/public/angular/templates/_sidebar.scss similarity index 100% rename from x-pack/legacy/plugins/graph/public/angular/templates/_sidebar.scss rename to x-pack/plugins/graph/public/angular/templates/_sidebar.scss diff --git a/x-pack/legacy/plugins/graph/public/angular/templates/index.html b/x-pack/plugins/graph/public/angular/templates/index.html similarity index 100% rename from x-pack/legacy/plugins/graph/public/angular/templates/index.html rename to x-pack/plugins/graph/public/angular/templates/index.html diff --git a/x-pack/legacy/plugins/graph/public/angular/templates/listing_ng_wrapper.html b/x-pack/plugins/graph/public/angular/templates/listing_ng_wrapper.html similarity index 100% rename from x-pack/legacy/plugins/graph/public/angular/templates/listing_ng_wrapper.html rename to x-pack/plugins/graph/public/angular/templates/listing_ng_wrapper.html diff --git a/x-pack/legacy/plugins/graph/public/app.js b/x-pack/plugins/graph/public/app.js similarity index 96% rename from x-pack/legacy/plugins/graph/public/app.js rename to x-pack/plugins/graph/public/app.js index df968681a38e2..72dddc2b9f813 100644 --- a/x-pack/legacy/plugins/graph/public/app.js +++ b/x-pack/plugins/graph/public/app.js @@ -6,13 +6,12 @@ import _ from 'lodash'; import { i18n } from '@kbn/i18n'; -import 'ace'; import React from 'react'; import { Provider } from 'react-redux'; import { isColorDark, hexToRgb } from '@elastic/eui'; -import { toMountPoint } from '../../../../../src/plugins/kibana_react/public'; -import { showSaveModal } from '../../../../../src/plugins/saved_objects/public'; +import { toMountPoint } from '../../../../src/plugins/kibana_react/public'; +import { showSaveModal } from '../../../../src/plugins/saved_objects/public'; import appTemplate from './angular/templates/index.html'; import listingTemplate from './angular/templates/listing_ng_wrapper.html'; @@ -41,7 +40,7 @@ export function initGraphApp(angularModule, deps) { indexPatterns, addBasePath, getBasePath, - npData, + data, config, savedWorkspaceLoader, capabilities, @@ -107,7 +106,7 @@ export function initGraphApp(angularModule, deps) { .when('/home', { template: listingTemplate, badge: getReadonlyBadge, - controller($location, $scope) { + controller: function($location, $scope) { $scope.listingLimit = config.get('savedObjects:listingLimit'); $scope.create = () => { $location.url(getNewPath()); @@ -249,6 +248,7 @@ export function initGraphApp(angularModule, deps) { const store = createGraphStore({ basePath: getBasePath(), + addBasePath, indexPatternProvider: $scope.indexPatternProvider, indexPatterns: $route.current.locals.indexPatterns, createWorkspace: (indexPattern, exploreControls) => { @@ -301,7 +301,7 @@ export function initGraphApp(angularModule, deps) { }); // register things on scope passed down to react components - $scope.pluginDataStart = npData; + $scope.pluginDataStart = data; $scope.storage = storage; $scope.coreStart = coreStart; $scope.loading = false; @@ -420,11 +420,13 @@ export function initGraphApp(angularModule, deps) { while (found) { found = false; for (const i in $scope.detail.mergeCandidates) { - const mc = $scope.detail.mergeCandidates[i]; - if (mc.id1 === childId || mc.id2 === childId) { - $scope.detail.mergeCandidates.splice(i, 1); - found = true; - break; + if ($scope.detail.mergeCandidates.hasOwnProperty(i)) { + const mc = $scope.detail.mergeCandidates[i]; + if (mc.id1 === childId || mc.id2 === childId) { + $scope.detail.mergeCandidates.splice(i, 1); + found = true; + break; + } } } } @@ -434,8 +436,7 @@ export function initGraphApp(angularModule, deps) { $scope.handleMergeCandidatesCallback = function(termIntersects) { const mergeCandidates = []; - for (const i in termIntersects) { - const ti = termIntersects[i]; + termIntersects.forEach(ti => { mergeCandidates.push({ id1: ti.id1, id2: ti.id2, @@ -445,7 +446,7 @@ export function initGraphApp(angularModule, deps) { v2: ti.v2, overlap: ti.overlap, }); - } + }); $scope.detail = { mergeCandidates }; }; diff --git a/x-pack/legacy/plugins/graph/public/application.ts b/x-pack/plugins/graph/public/application.ts similarity index 81% rename from x-pack/legacy/plugins/graph/public/application.ts rename to x-pack/plugins/graph/public/application.ts index 536382e62d473..4f7bdd69db356 100644 --- a/x-pack/legacy/plugins/graph/public/application.ts +++ b/x-pack/plugins/graph/public/application.ts @@ -9,34 +9,36 @@ // They can stay even after NP cutover import angular from 'angular'; import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/angular'; - +import '../../../../webpackShims/ace'; +// required for i18nIdDirective +import 'angular-sanitize'; // type imports import { AppMountContext, ChromeStart, - LegacyCoreStart, + CoreStart, + PluginInitializerContext, SavedObjectsClientContract, ToastsStart, IUiSettingsClient, OverlayStart, } from 'kibana/public'; -import { configureAppAngularModule } from './legacy_imports'; // @ts-ignore import { initGraphApp } from './app'; -import { - Plugin as DataPlugin, - IndexPatternsContract, -} from '../../../../../src/plugins/data/public'; -import { LicensingPluginSetup } from '../../../../plugins/licensing/public'; -import { checkLicense } from '../../../../plugins/graph/common/check_license'; -import { NavigationPublicPluginStart as NavigationStart } from '../../../../../src/plugins/navigation/public'; +import { Plugin as DataPlugin, IndexPatternsContract } from '../../../../src/plugins/data/public'; +import { LicensingPluginSetup } from '../../licensing/public'; +import { checkLicense } from '../common/check_license'; +import { NavigationPublicPluginStart as NavigationStart } from '../../../../src/plugins/navigation/public'; import { createSavedWorkspacesLoader } from './services/persistence/saved_workspace_loader'; -import { Storage } from '../../../../../src/plugins/kibana_utils/public'; +import { Storage } from '../../../../src/plugins/kibana_utils/public'; import { + addAppRedirectMessageToUrl, + configureAppAngularModule, createTopNavDirective, createTopNavHelper, -} from '../../../../../src/plugins/kibana_legacy/public'; -import { addAppRedirectMessageToUrl } from '../../../../../src/plugins/kibana_legacy/public'; +} from '../../../../src/plugins/kibana_legacy/public'; + +import './index.scss'; /** * These are dependencies of the Graph app besides the base dependencies @@ -45,6 +47,8 @@ import { addAppRedirectMessageToUrl } from '../../../../../src/plugins/kibana_le * itself changes */ export interface GraphDependencies { + pluginInitializerContext: PluginInitializerContext; + core: CoreStart; element: HTMLElement; appBasePath: string; capabilities: Record>; @@ -55,7 +59,7 @@ export interface GraphDependencies { config: IUiSettingsClient; toastNotifications: ToastsStart; indexPatterns: IndexPatternsContract; - npData: ReturnType; + data: ReturnType; savedObjectsClient: SavedObjectsClientContract; addBasePath: (url: string) => string; getBasePath: () => string; @@ -67,7 +71,11 @@ export interface GraphDependencies { export const renderApp = ({ appBasePath, element, ...deps }: GraphDependencies) => { const graphAngularModule = createLocalAngularModule(deps.navigation); - configureAppAngularModule(graphAngularModule, deps.coreStart as LegacyCoreStart, true); + configureAppAngularModule( + graphAngularModule, + { core: deps.core, env: deps.pluginInitializerContext.env }, + true + ); const licenseSubscription = deps.licensing.license$.subscribe(license => { const info = checkLicense(license); @@ -81,7 +89,7 @@ export const renderApp = ({ appBasePath, element, ...deps }: GraphDependencies) const savedWorkspaceLoader = createSavedWorkspacesLoader({ chrome: deps.coreStart.chrome, - indexPatterns: deps.npData.indexPatterns, + indexPatterns: deps.data.indexPatterns, overlays: deps.coreStart.overlays, savedObjectsClient: deps.coreStart.savedObjects.client, basePath: deps.coreStart.http.basePath, @@ -113,6 +121,7 @@ function mountGraphApp(appBasePath: string, element: HTMLElement) { // make angular-within-angular possible const $injector = angular.bootstrap(mountpoint, [moduleName]); element.appendChild(mountpoint); + element.setAttribute('class', 'kbnLocalApplicationWrapper'); return $injector; } diff --git a/x-pack/legacy/plugins/graph/public/badge.js b/x-pack/plugins/graph/public/badge.js similarity index 100% rename from x-pack/legacy/plugins/graph/public/badge.js rename to x-pack/plugins/graph/public/badge.js diff --git a/x-pack/legacy/plugins/graph/public/components/_app.scss b/x-pack/plugins/graph/public/components/_app.scss similarity index 100% rename from x-pack/legacy/plugins/graph/public/components/_app.scss rename to x-pack/plugins/graph/public/components/_app.scss diff --git a/x-pack/legacy/plugins/graph/public/components/_index.scss b/x-pack/plugins/graph/public/components/_index.scss similarity index 100% rename from x-pack/legacy/plugins/graph/public/components/_index.scss rename to x-pack/plugins/graph/public/components/_index.scss diff --git a/x-pack/legacy/plugins/graph/public/components/_search_bar.scss b/x-pack/plugins/graph/public/components/_search_bar.scss similarity index 100% rename from x-pack/legacy/plugins/graph/public/components/_search_bar.scss rename to x-pack/plugins/graph/public/components/_search_bar.scss diff --git a/x-pack/legacy/plugins/graph/public/components/_source_modal.scss b/x-pack/plugins/graph/public/components/_source_modal.scss similarity index 100% rename from x-pack/legacy/plugins/graph/public/components/_source_modal.scss rename to x-pack/plugins/graph/public/components/_source_modal.scss diff --git a/x-pack/legacy/plugins/graph/public/components/app.tsx b/x-pack/plugins/graph/public/components/app.tsx similarity index 96% rename from x-pack/legacy/plugins/graph/public/components/app.tsx rename to x-pack/plugins/graph/public/components/app.tsx index 957a8f66907a1..a57842eaf23f5 100644 --- a/x-pack/legacy/plugins/graph/public/components/app.tsx +++ b/x-pack/plugins/graph/public/components/app.tsx @@ -18,7 +18,7 @@ import { GraphStore } from '../state_management'; import { GuidancePanel } from './guidance_panel'; import { GraphTitle } from './graph_title'; -import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; +import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public'; export interface GraphAppProps extends SearchBarProps { coreStart: CoreStart; diff --git a/x-pack/legacy/plugins/graph/public/components/field_manager/_field_editor.scss b/x-pack/plugins/graph/public/components/field_manager/_field_editor.scss similarity index 100% rename from x-pack/legacy/plugins/graph/public/components/field_manager/_field_editor.scss rename to x-pack/plugins/graph/public/components/field_manager/_field_editor.scss diff --git a/x-pack/legacy/plugins/graph/public/components/field_manager/_field_picker.scss b/x-pack/plugins/graph/public/components/field_manager/_field_picker.scss similarity index 100% rename from x-pack/legacy/plugins/graph/public/components/field_manager/_field_picker.scss rename to x-pack/plugins/graph/public/components/field_manager/_field_picker.scss diff --git a/x-pack/legacy/plugins/graph/public/components/field_manager/_index.scss b/x-pack/plugins/graph/public/components/field_manager/_index.scss similarity index 100% rename from x-pack/legacy/plugins/graph/public/components/field_manager/_index.scss rename to x-pack/plugins/graph/public/components/field_manager/_index.scss diff --git a/x-pack/legacy/plugins/graph/public/components/field_manager/field_editor.tsx b/x-pack/plugins/graph/public/components/field_manager/field_editor.tsx similarity index 99% rename from x-pack/legacy/plugins/graph/public/components/field_manager/field_editor.tsx rename to x-pack/plugins/graph/public/components/field_manager/field_editor.tsx index 9c7cffa775781..78e4180aa2b2a 100644 --- a/x-pack/legacy/plugins/graph/public/components/field_manager/field_editor.tsx +++ b/x-pack/plugins/graph/public/components/field_manager/field_editor.tsx @@ -29,7 +29,7 @@ import classNames from 'classnames'; import { WorkspaceField } from '../../types'; import { iconChoices } from '../../helpers/style_choices'; import { LegacyIcon } from '../legacy_icon'; -import { FieldIcon } from '../../../../../../../src/plugins/kibana_react/public'; +import { FieldIcon } from '../../../../../../src/plugins/kibana_react/public'; import { UpdateableFieldProperties } from './field_manager'; import { isEqual } from '../helpers'; diff --git a/x-pack/legacy/plugins/graph/public/components/field_manager/field_manager.test.tsx b/x-pack/plugins/graph/public/components/field_manager/field_manager.test.tsx similarity index 100% rename from x-pack/legacy/plugins/graph/public/components/field_manager/field_manager.test.tsx rename to x-pack/plugins/graph/public/components/field_manager/field_manager.test.tsx diff --git a/x-pack/legacy/plugins/graph/public/components/field_manager/field_manager.tsx b/x-pack/plugins/graph/public/components/field_manager/field_manager.tsx similarity index 100% rename from x-pack/legacy/plugins/graph/public/components/field_manager/field_manager.tsx rename to x-pack/plugins/graph/public/components/field_manager/field_manager.tsx diff --git a/x-pack/legacy/plugins/graph/public/components/field_manager/field_picker.tsx b/x-pack/plugins/graph/public/components/field_manager/field_picker.tsx similarity index 98% rename from x-pack/legacy/plugins/graph/public/components/field_manager/field_picker.tsx rename to x-pack/plugins/graph/public/components/field_manager/field_picker.tsx index 30f1fcffd4f67..f2dc9ba0c6490 100644 --- a/x-pack/legacy/plugins/graph/public/components/field_manager/field_picker.tsx +++ b/x-pack/plugins/graph/public/components/field_manager/field_picker.tsx @@ -9,7 +9,7 @@ import { EuiPopover, EuiSelectable, EuiBadge } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import classNames from 'classnames'; import { WorkspaceField } from '../../types'; -import { FieldIcon } from '../../../../../../../src/plugins/kibana_react/public'; +import { FieldIcon } from '../../../../../../src/plugins/kibana_react/public'; export interface FieldPickerProps { fieldMap: Record; diff --git a/x-pack/legacy/plugins/graph/public/components/field_manager/index.ts b/x-pack/plugins/graph/public/components/field_manager/index.ts similarity index 100% rename from x-pack/legacy/plugins/graph/public/components/field_manager/index.ts rename to x-pack/plugins/graph/public/components/field_manager/index.ts diff --git a/x-pack/legacy/plugins/graph/public/components/graph_title.tsx b/x-pack/plugins/graph/public/components/graph_title.tsx similarity index 100% rename from x-pack/legacy/plugins/graph/public/components/graph_title.tsx rename to x-pack/plugins/graph/public/components/graph_title.tsx diff --git a/x-pack/legacy/plugins/graph/public/components/graph_visualization/__snapshots__/graph_visualization.test.tsx.snap b/x-pack/plugins/graph/public/components/graph_visualization/__snapshots__/graph_visualization.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/graph/public/components/graph_visualization/__snapshots__/graph_visualization.test.tsx.snap rename to x-pack/plugins/graph/public/components/graph_visualization/__snapshots__/graph_visualization.test.tsx.snap diff --git a/x-pack/legacy/plugins/graph/public/components/graph_visualization/_graph_visualization.scss b/x-pack/plugins/graph/public/components/graph_visualization/_graph_visualization.scss similarity index 100% rename from x-pack/legacy/plugins/graph/public/components/graph_visualization/_graph_visualization.scss rename to x-pack/plugins/graph/public/components/graph_visualization/_graph_visualization.scss diff --git a/x-pack/legacy/plugins/graph/public/components/graph_visualization/_index.scss b/x-pack/plugins/graph/public/components/graph_visualization/_index.scss similarity index 100% rename from x-pack/legacy/plugins/graph/public/components/graph_visualization/_index.scss rename to x-pack/plugins/graph/public/components/graph_visualization/_index.scss diff --git a/x-pack/legacy/plugins/graph/public/components/graph_visualization/graph_visualization.test.tsx b/x-pack/plugins/graph/public/components/graph_visualization/graph_visualization.test.tsx similarity index 100% rename from x-pack/legacy/plugins/graph/public/components/graph_visualization/graph_visualization.test.tsx rename to x-pack/plugins/graph/public/components/graph_visualization/graph_visualization.test.tsx diff --git a/x-pack/legacy/plugins/graph/public/components/graph_visualization/graph_visualization.tsx b/x-pack/plugins/graph/public/components/graph_visualization/graph_visualization.tsx similarity index 100% rename from x-pack/legacy/plugins/graph/public/components/graph_visualization/graph_visualization.tsx rename to x-pack/plugins/graph/public/components/graph_visualization/graph_visualization.tsx diff --git a/x-pack/legacy/plugins/graph/public/components/graph_visualization/index.ts b/x-pack/plugins/graph/public/components/graph_visualization/index.ts similarity index 100% rename from x-pack/legacy/plugins/graph/public/components/graph_visualization/index.ts rename to x-pack/plugins/graph/public/components/graph_visualization/index.ts diff --git a/x-pack/legacy/plugins/graph/public/components/guidance_panel/_guidance_panel.scss b/x-pack/plugins/graph/public/components/guidance_panel/_guidance_panel.scss similarity index 100% rename from x-pack/legacy/plugins/graph/public/components/guidance_panel/_guidance_panel.scss rename to x-pack/plugins/graph/public/components/guidance_panel/_guidance_panel.scss diff --git a/x-pack/legacy/plugins/graph/public/components/guidance_panel/_index.scss b/x-pack/plugins/graph/public/components/guidance_panel/_index.scss similarity index 100% rename from x-pack/legacy/plugins/graph/public/components/guidance_panel/_index.scss rename to x-pack/plugins/graph/public/components/guidance_panel/_index.scss diff --git a/x-pack/legacy/plugins/graph/public/components/guidance_panel/guidance_panel.tsx b/x-pack/plugins/graph/public/components/guidance_panel/guidance_panel.tsx similarity index 98% rename from x-pack/legacy/plugins/graph/public/components/guidance_panel/guidance_panel.tsx rename to x-pack/plugins/graph/public/components/guidance_panel/guidance_panel.tsx index d1fcbea2ff5b7..3990abfe87ab3 100644 --- a/x-pack/legacy/plugins/graph/public/components/guidance_panel/guidance_panel.tsx +++ b/x-pack/plugins/graph/public/components/guidance_panel/guidance_panel.tsx @@ -30,7 +30,7 @@ import { import { IndexPatternSavedObject } from '../../types'; import { openSourceModal } from '../../services/source_modal'; -import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; +import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; export interface GuidancePanelProps { onFillWorkspace: () => void; diff --git a/x-pack/legacy/plugins/graph/public/components/guidance_panel/index.ts b/x-pack/plugins/graph/public/components/guidance_panel/index.ts similarity index 100% rename from x-pack/legacy/plugins/graph/public/components/guidance_panel/index.ts rename to x-pack/plugins/graph/public/components/guidance_panel/index.ts diff --git a/x-pack/legacy/plugins/graph/public/components/helpers.ts b/x-pack/plugins/graph/public/components/helpers.ts similarity index 100% rename from x-pack/legacy/plugins/graph/public/components/helpers.ts rename to x-pack/plugins/graph/public/components/helpers.ts diff --git a/x-pack/legacy/plugins/graph/public/components/legacy_icon/_index.scss b/x-pack/plugins/graph/public/components/legacy_icon/_index.scss similarity index 100% rename from x-pack/legacy/plugins/graph/public/components/legacy_icon/_index.scss rename to x-pack/plugins/graph/public/components/legacy_icon/_index.scss diff --git a/x-pack/legacy/plugins/graph/public/components/legacy_icon/_legacy_icon.scss b/x-pack/plugins/graph/public/components/legacy_icon/_legacy_icon.scss similarity index 100% rename from x-pack/legacy/plugins/graph/public/components/legacy_icon/_legacy_icon.scss rename to x-pack/plugins/graph/public/components/legacy_icon/_legacy_icon.scss diff --git a/x-pack/legacy/plugins/graph/public/components/legacy_icon/index.ts b/x-pack/plugins/graph/public/components/legacy_icon/index.ts similarity index 100% rename from x-pack/legacy/plugins/graph/public/components/legacy_icon/index.ts rename to x-pack/plugins/graph/public/components/legacy_icon/index.ts diff --git a/x-pack/legacy/plugins/graph/public/components/legacy_icon/legacy_icon.tsx b/x-pack/plugins/graph/public/components/legacy_icon/legacy_icon.tsx similarity index 100% rename from x-pack/legacy/plugins/graph/public/components/legacy_icon/legacy_icon.tsx rename to x-pack/plugins/graph/public/components/legacy_icon/legacy_icon.tsx diff --git a/x-pack/legacy/plugins/graph/public/components/listing.tsx b/x-pack/plugins/graph/public/components/listing.tsx similarity index 98% rename from x-pack/legacy/plugins/graph/public/components/listing.tsx rename to x-pack/plugins/graph/public/components/listing.tsx index 5fa6111b1a244..aeecc3ab103f3 100644 --- a/x-pack/legacy/plugins/graph/public/components/listing.tsx +++ b/x-pack/plugins/graph/public/components/listing.tsx @@ -10,7 +10,7 @@ import React, { Fragment } from 'react'; import { EuiEmptyPrompt, EuiLink, EuiButton } from '@elastic/eui'; import { CoreStart, ApplicationStart } from 'kibana/public'; -import { TableListView } from '../../../../../../src/plugins/kibana_react/public'; +import { TableListView } from '../../../../../src/plugins/kibana_react/public'; import { GraphWorkspaceSavedObject } from '../types'; export interface ListingProps { diff --git a/x-pack/legacy/plugins/graph/public/components/save_modal.tsx b/x-pack/plugins/graph/public/components/save_modal.tsx similarity index 96% rename from x-pack/legacy/plugins/graph/public/components/save_modal.tsx rename to x-pack/plugins/graph/public/components/save_modal.tsx index a7329c10e93d7..c4459fb1a794f 100644 --- a/x-pack/legacy/plugins/graph/public/components/save_modal.tsx +++ b/x-pack/plugins/graph/public/components/save_modal.tsx @@ -7,10 +7,7 @@ import React, { useState } from 'react'; import { EuiFormRow, EuiTextArea, EuiCallOut, EuiSpacer, EuiSwitch } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { - SavedObjectSaveModal, - OnSaveProps, -} from '../../../../../../src/plugins/saved_objects/public'; +import { SavedObjectSaveModal, OnSaveProps } from '../../../../../src/plugins/saved_objects/public'; import { GraphSavePolicy } from '../types/config'; diff --git a/x-pack/legacy/plugins/graph/public/components/search_bar.test.tsx b/x-pack/plugins/graph/public/components/search_bar.test.tsx similarity index 95% rename from x-pack/legacy/plugins/graph/public/components/search_bar.test.tsx rename to x-pack/plugins/graph/public/components/search_bar.test.tsx index 95b7dd22e9fcf..10778124e2011 100644 --- a/x-pack/legacy/plugins/graph/public/components/search_bar.test.tsx +++ b/x-pack/plugins/graph/public/components/search_bar.test.tsx @@ -9,9 +9,9 @@ import { SearchBar, OuterSearchBarProps } from './search_bar'; import React, { ReactElement } from 'react'; import { CoreStart } from 'src/core/public'; import { act } from 'react-dom/test-utils'; -import { IndexPattern, QueryStringInput } from '../../../../../../src/plugins/data/public'; +import { IndexPattern, QueryStringInput } from '../../../../../src/plugins/data/public'; -import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; +import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public'; import { I18nProvider } from '@kbn/i18n/react'; import { openSourceModal } from '../services/source_modal'; diff --git a/x-pack/legacy/plugins/graph/public/components/search_bar.tsx b/x-pack/plugins/graph/public/components/search_bar.tsx similarity index 98% rename from x-pack/legacy/plugins/graph/public/components/search_bar.tsx rename to x-pack/plugins/graph/public/components/search_bar.tsx index c7c5830cadfe1..ab6d94a78ceec 100644 --- a/x-pack/legacy/plugins/graph/public/components/search_bar.tsx +++ b/x-pack/plugins/graph/public/components/search_bar.tsx @@ -18,14 +18,14 @@ import { IndexpatternDatasource, } from '../state_management'; -import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; +import { useKibana } from '../../../../../src/plugins/kibana_react/public'; import { IndexPattern, QueryStringInput, IDataPluginServices, Query, esKuery, -} from '../../../../../../src/plugins/data/public'; +} from '../../../../../src/plugins/data/public'; export interface OuterSearchBarProps { isLoading: boolean; diff --git a/x-pack/legacy/plugins/graph/public/components/settings/_index.scss b/x-pack/plugins/graph/public/components/settings/_index.scss similarity index 100% rename from x-pack/legacy/plugins/graph/public/components/settings/_index.scss rename to x-pack/plugins/graph/public/components/settings/_index.scss diff --git a/x-pack/legacy/plugins/graph/public/components/settings/_legacy_icon.scss b/x-pack/plugins/graph/public/components/settings/_legacy_icon.scss similarity index 100% rename from x-pack/legacy/plugins/graph/public/components/settings/_legacy_icon.scss rename to x-pack/plugins/graph/public/components/settings/_legacy_icon.scss diff --git a/x-pack/legacy/plugins/graph/public/components/settings/_url_template_list.scss b/x-pack/plugins/graph/public/components/settings/_url_template_list.scss similarity index 100% rename from x-pack/legacy/plugins/graph/public/components/settings/_url_template_list.scss rename to x-pack/plugins/graph/public/components/settings/_url_template_list.scss diff --git a/x-pack/legacy/plugins/graph/public/components/settings/advanced_settings_form.tsx b/x-pack/plugins/graph/public/components/settings/advanced_settings_form.tsx similarity index 100% rename from x-pack/legacy/plugins/graph/public/components/settings/advanced_settings_form.tsx rename to x-pack/plugins/graph/public/components/settings/advanced_settings_form.tsx diff --git a/x-pack/legacy/plugins/graph/public/components/settings/blacklist_form.tsx b/x-pack/plugins/graph/public/components/settings/blacklist_form.tsx similarity index 100% rename from x-pack/legacy/plugins/graph/public/components/settings/blacklist_form.tsx rename to x-pack/plugins/graph/public/components/settings/blacklist_form.tsx diff --git a/x-pack/legacy/plugins/graph/public/components/settings/index.ts b/x-pack/plugins/graph/public/components/settings/index.ts similarity index 100% rename from x-pack/legacy/plugins/graph/public/components/settings/index.ts rename to x-pack/plugins/graph/public/components/settings/index.ts diff --git a/x-pack/legacy/plugins/graph/public/components/settings/settings.test.tsx b/x-pack/plugins/graph/public/components/settings/settings.test.tsx similarity index 100% rename from x-pack/legacy/plugins/graph/public/components/settings/settings.test.tsx rename to x-pack/plugins/graph/public/components/settings/settings.test.tsx diff --git a/x-pack/legacy/plugins/graph/public/components/settings/settings.tsx b/x-pack/plugins/graph/public/components/settings/settings.tsx similarity index 100% rename from x-pack/legacy/plugins/graph/public/components/settings/settings.tsx rename to x-pack/plugins/graph/public/components/settings/settings.tsx diff --git a/x-pack/legacy/plugins/graph/public/components/settings/url_template_form.tsx b/x-pack/plugins/graph/public/components/settings/url_template_form.tsx similarity index 100% rename from x-pack/legacy/plugins/graph/public/components/settings/url_template_form.tsx rename to x-pack/plugins/graph/public/components/settings/url_template_form.tsx diff --git a/x-pack/legacy/plugins/graph/public/components/settings/url_template_list.tsx b/x-pack/plugins/graph/public/components/settings/url_template_list.tsx similarity index 100% rename from x-pack/legacy/plugins/graph/public/components/settings/url_template_list.tsx rename to x-pack/plugins/graph/public/components/settings/url_template_list.tsx diff --git a/x-pack/legacy/plugins/graph/public/components/settings/use_list_keys.test.tsx b/x-pack/plugins/graph/public/components/settings/use_list_keys.test.tsx similarity index 100% rename from x-pack/legacy/plugins/graph/public/components/settings/use_list_keys.test.tsx rename to x-pack/plugins/graph/public/components/settings/use_list_keys.test.tsx diff --git a/x-pack/legacy/plugins/graph/public/components/settings/use_list_keys.ts b/x-pack/plugins/graph/public/components/settings/use_list_keys.ts similarity index 100% rename from x-pack/legacy/plugins/graph/public/components/settings/use_list_keys.ts rename to x-pack/plugins/graph/public/components/settings/use_list_keys.ts diff --git a/x-pack/legacy/plugins/graph/public/components/source_modal.tsx b/x-pack/plugins/graph/public/components/source_modal.tsx similarity index 100% rename from x-pack/legacy/plugins/graph/public/components/source_modal.tsx rename to x-pack/plugins/graph/public/components/source_modal.tsx diff --git a/x-pack/legacy/plugins/graph/public/components/source_picker.tsx b/x-pack/plugins/graph/public/components/source_picker.tsx similarity index 94% rename from x-pack/legacy/plugins/graph/public/components/source_picker.tsx rename to x-pack/plugins/graph/public/components/source_picker.tsx index 65a431202fc98..9172f6ba1c65c 100644 --- a/x-pack/legacy/plugins/graph/public/components/source_picker.tsx +++ b/x-pack/plugins/graph/public/components/source_picker.tsx @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import { CoreStart } from 'src/core/public'; -import { SavedObjectFinderUi } from '../../../../../../src/plugins/saved_objects/public'; +import { SavedObjectFinderUi } from '../../../../../src/plugins/saved_objects/public'; import { IndexPatternSavedObject } from '../types'; export interface SourcePickerProps { diff --git a/x-pack/legacy/plugins/graph/public/components/venn_diagram/_index.scss b/x-pack/plugins/graph/public/components/venn_diagram/_index.scss similarity index 100% rename from x-pack/legacy/plugins/graph/public/components/venn_diagram/_index.scss rename to x-pack/plugins/graph/public/components/venn_diagram/_index.scss diff --git a/x-pack/legacy/plugins/graph/public/components/venn_diagram/_venn_diagram.scss b/x-pack/plugins/graph/public/components/venn_diagram/_venn_diagram.scss similarity index 100% rename from x-pack/legacy/plugins/graph/public/components/venn_diagram/_venn_diagram.scss rename to x-pack/plugins/graph/public/components/venn_diagram/_venn_diagram.scss diff --git a/x-pack/legacy/plugins/graph/public/components/venn_diagram/index.ts b/x-pack/plugins/graph/public/components/venn_diagram/index.ts similarity index 100% rename from x-pack/legacy/plugins/graph/public/components/venn_diagram/index.ts rename to x-pack/plugins/graph/public/components/venn_diagram/index.ts diff --git a/x-pack/legacy/plugins/graph/public/components/venn_diagram/venn_diagram.test.tsx b/x-pack/plugins/graph/public/components/venn_diagram/venn_diagram.test.tsx similarity index 100% rename from x-pack/legacy/plugins/graph/public/components/venn_diagram/venn_diagram.test.tsx rename to x-pack/plugins/graph/public/components/venn_diagram/venn_diagram.test.tsx diff --git a/x-pack/legacy/plugins/graph/public/components/venn_diagram/venn_diagram.tsx b/x-pack/plugins/graph/public/components/venn_diagram/venn_diagram.tsx similarity index 100% rename from x-pack/legacy/plugins/graph/public/components/venn_diagram/venn_diagram.tsx rename to x-pack/plugins/graph/public/components/venn_diagram/venn_diagram.tsx diff --git a/x-pack/legacy/plugins/graph/public/helpers/as_observable.ts b/x-pack/plugins/graph/public/helpers/as_observable.ts similarity index 100% rename from x-pack/legacy/plugins/graph/public/helpers/as_observable.ts rename to x-pack/plugins/graph/public/helpers/as_observable.ts diff --git a/x-pack/legacy/plugins/graph/public/helpers/format_http_error.ts b/x-pack/plugins/graph/public/helpers/format_http_error.ts similarity index 100% rename from x-pack/legacy/plugins/graph/public/helpers/format_http_error.ts rename to x-pack/plugins/graph/public/helpers/format_http_error.ts diff --git a/x-pack/legacy/plugins/graph/public/helpers/kql_encoder.test.ts b/x-pack/plugins/graph/public/helpers/kql_encoder.test.ts similarity index 100% rename from x-pack/legacy/plugins/graph/public/helpers/kql_encoder.test.ts rename to x-pack/plugins/graph/public/helpers/kql_encoder.test.ts diff --git a/x-pack/legacy/plugins/graph/public/helpers/kql_encoder.ts b/x-pack/plugins/graph/public/helpers/kql_encoder.ts similarity index 100% rename from x-pack/legacy/plugins/graph/public/helpers/kql_encoder.ts rename to x-pack/plugins/graph/public/helpers/kql_encoder.ts diff --git a/x-pack/legacy/plugins/graph/public/helpers/outlink_encoders.ts b/x-pack/plugins/graph/public/helpers/outlink_encoders.ts similarity index 100% rename from x-pack/legacy/plugins/graph/public/helpers/outlink_encoders.ts rename to x-pack/plugins/graph/public/helpers/outlink_encoders.ts diff --git a/x-pack/legacy/plugins/graph/public/helpers/style_choices.ts b/x-pack/plugins/graph/public/helpers/style_choices.ts similarity index 100% rename from x-pack/legacy/plugins/graph/public/helpers/style_choices.ts rename to x-pack/plugins/graph/public/helpers/style_choices.ts diff --git a/x-pack/legacy/plugins/graph/public/helpers/url_template.ts b/x-pack/plugins/graph/public/helpers/url_template.ts similarity index 100% rename from x-pack/legacy/plugins/graph/public/helpers/url_template.ts rename to x-pack/plugins/graph/public/helpers/url_template.ts diff --git a/x-pack/legacy/plugins/graph/public/index.scss b/x-pack/plugins/graph/public/index.scss similarity index 71% rename from x-pack/legacy/plugins/graph/public/index.scss rename to x-pack/plugins/graph/public/index.scss index 067b2c300626d..f4e38de3e93a4 100644 --- a/x-pack/legacy/plugins/graph/public/index.scss +++ b/x-pack/plugins/graph/public/index.scss @@ -1,6 +1,3 @@ -// Import the EUI global scope so we can use EUI constants -@import 'src/legacy/ui/public/styles/_styling_constants'; - /* Graph plugin styles */ // Prefix all styles with "gph" to avoid conflicts. diff --git a/x-pack/plugins/graph/public/index.ts b/x-pack/plugins/graph/public/index.ts index 7b2ce67631713..690d2e88dd9c9 100644 --- a/x-pack/plugins/graph/public/index.ts +++ b/x-pack/plugins/graph/public/index.ts @@ -10,5 +10,3 @@ import { ConfigSchema } from '../config'; export const plugin = (initializerContext: PluginInitializerContext) => new GraphPlugin(initializerContext); - -export { GraphSetup } from './plugin'; diff --git a/x-pack/plugins/graph/public/plugin.ts b/x-pack/plugins/graph/public/plugin.ts index e911b400349f8..5521de705b6ec 100644 --- a/x-pack/plugins/graph/public/plugin.ts +++ b/x-pack/plugins/graph/public/plugin.ts @@ -6,8 +6,14 @@ import { i18n } from '@kbn/i18n'; import { CoreSetup, CoreStart } from 'kibana/public'; -import { Plugin } from 'src/core/public'; +import { AppMountParameters, Plugin } from 'src/core/public'; import { PluginInitializerContext } from 'kibana/public'; + +import { Storage } from '../../../../src/plugins/kibana_utils/public'; +import { initAngularBootstrap } from '../../../../src/plugins/kibana_legacy/public'; +import { NavigationPublicPluginStart as NavigationStart } from '../../../../src/plugins/navigation/public'; +import { DataPublicPluginStart } from '../../../../src/plugins/data/public'; + import { toggleNavLink } from './services/toggle_nav_link'; import { LicensingPluginSetup } from '../../licensing/public'; import { checkLicense } from '../common/check_license'; @@ -15,6 +21,7 @@ import { FeatureCatalogueCategory, HomePublicPluginSetup, } from '../../../../src/plugins/home/public'; +import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/utils'; import { ConfigSchema } from '../config'; export interface GraphPluginSetupDependencies { @@ -22,12 +29,21 @@ export interface GraphPluginSetupDependencies { home?: HomePublicPluginSetup; } -export class GraphPlugin implements Plugin<{ config: Readonly }, void> { +export interface GraphPluginStartDependencies { + navigation: NavigationStart; + data: DataPublicPluginStart; +} + +export class GraphPlugin + implements Plugin { private licensing: LicensingPluginSetup | null = null; constructor(private initializerContext: PluginInitializerContext) {} - setup(core: CoreSetup, { licensing, home }: GraphPluginSetupDependencies) { + setup( + core: CoreSetup, + { licensing, home }: GraphPluginSetupDependencies + ) { this.licensing = licensing; if (home) { @@ -44,15 +60,42 @@ export class GraphPlugin implements Plugin<{ config: Readonly }, v }); } - return { - /** - * The configuration is temporarily exposed to allow the legacy graph plugin to consume - * the setting. Once the graph plugin is migrated completely, this will become an implementation - * detail. - * @deprecated - */ - config: this.initializerContext.config.get(), - }; + const config = this.initializerContext.config.get(); + + initAngularBootstrap(); + core.application.register({ + id: 'graph', + title: 'Graph', + order: 9000, + appRoute: '/app/graph', + euiIconType: 'graphApp', + category: DEFAULT_APP_CATEGORIES.analyze, + mount: async (params: AppMountParameters) => { + const [coreStart, pluginsStart] = await core.getStartServices(); + const { renderApp } = await import('./application'); + return renderApp({ + ...params, + pluginInitializerContext: this.initializerContext, + licensing, + core: coreStart, + navigation: pluginsStart.navigation, + data: pluginsStart.data, + savedObjectsClient: coreStart.savedObjects.client, + addBasePath: core.http.basePath.prepend, + getBasePath: core.http.basePath.get, + canEditDrillDownUrls: config.canEditDrillDownUrls, + graphSavePolicy: config.savePolicy, + storage: new Storage(window.localStorage), + capabilities: coreStart.application.capabilities.graph, + coreStart, + chrome: coreStart.chrome, + config: coreStart.uiSettings, + toastNotifications: coreStart.notifications.toasts, + indexPatterns: pluginsStart.data!.indexPatterns, + overlays: coreStart.overlays, + }); + }, + }); } start(core: CoreStart) { @@ -66,5 +109,3 @@ export class GraphPlugin implements Plugin<{ config: Readonly }, v stop() {} } - -export type GraphSetup = ReturnType; diff --git a/x-pack/legacy/plugins/graph/public/services/fetch_top_nodes.test.ts b/x-pack/plugins/graph/public/services/fetch_top_nodes.test.ts similarity index 100% rename from x-pack/legacy/plugins/graph/public/services/fetch_top_nodes.test.ts rename to x-pack/plugins/graph/public/services/fetch_top_nodes.test.ts diff --git a/x-pack/legacy/plugins/graph/public/services/fetch_top_nodes.ts b/x-pack/plugins/graph/public/services/fetch_top_nodes.ts similarity index 100% rename from x-pack/legacy/plugins/graph/public/services/fetch_top_nodes.ts rename to x-pack/plugins/graph/public/services/fetch_top_nodes.ts diff --git a/x-pack/legacy/plugins/graph/public/services/index_pattern_cache.ts b/x-pack/plugins/graph/public/services/index_pattern_cache.ts similarity index 90% rename from x-pack/legacy/plugins/graph/public/services/index_pattern_cache.ts rename to x-pack/plugins/graph/public/services/index_pattern_cache.ts index 9bbda0b551193..9cc466b9c20ab 100644 --- a/x-pack/legacy/plugins/graph/public/services/index_pattern_cache.ts +++ b/x-pack/plugins/graph/public/services/index_pattern_cache.ts @@ -5,7 +5,7 @@ */ import { IndexPatternProvider } from '../types'; -import { IndexPattern } from '../../../../../../src/plugins/data/public'; +import { IndexPattern } from '../../../../../src/plugins/data/public'; export function createCachedIndexPatternProvider( indexPatternGetter: (id: string) => Promise diff --git a/x-pack/legacy/plugins/graph/public/services/persistence/deserialize.test.ts b/x-pack/plugins/graph/public/services/persistence/deserialize.test.ts similarity index 98% rename from x-pack/legacy/plugins/graph/public/services/persistence/deserialize.test.ts rename to x-pack/plugins/graph/public/services/persistence/deserialize.test.ts index efef3d246ac98..3dda41fcdbdb6 100644 --- a/x-pack/legacy/plugins/graph/public/services/persistence/deserialize.test.ts +++ b/x-pack/plugins/graph/public/services/persistence/deserialize.test.ts @@ -8,7 +8,7 @@ import { GraphWorkspaceSavedObject, Workspace } from '../../types'; import { savedWorkspaceToAppState } from './deserialize'; import { createWorkspace } from '../../angular/graph_client_workspace'; import { outlinkEncoders } from '../../helpers/outlink_encoders'; -import { IndexPattern } from '../../../../../../../src/plugins/data/public'; +import { IndexPattern } from '../../../../../../src/plugins/data/public'; describe('deserialize', () => { let savedWorkspace: GraphWorkspaceSavedObject; diff --git a/x-pack/legacy/plugins/graph/public/services/persistence/deserialize.ts b/x-pack/plugins/graph/public/services/persistence/deserialize.ts similarity index 99% rename from x-pack/legacy/plugins/graph/public/services/persistence/deserialize.ts rename to x-pack/plugins/graph/public/services/persistence/deserialize.ts index 947e56a6de6eb..06106ed4c4f3f 100644 --- a/x-pack/legacy/plugins/graph/public/services/persistence/deserialize.ts +++ b/x-pack/plugins/graph/public/services/persistence/deserialize.ts @@ -27,7 +27,7 @@ import { import { IndexPattern, indexPatterns as indexPatternsUtils, -} from '../../../../../../../src/plugins/data/public'; +} from '../../../../../../src/plugins/data/public'; const defaultAdvancedSettings: AdvancedSettings = { useSignificance: true, diff --git a/x-pack/legacy/plugins/graph/public/services/persistence/index.ts b/x-pack/plugins/graph/public/services/persistence/index.ts similarity index 100% rename from x-pack/legacy/plugins/graph/public/services/persistence/index.ts rename to x-pack/plugins/graph/public/services/persistence/index.ts diff --git a/x-pack/legacy/plugins/graph/public/services/persistence/saved_workspace.ts b/x-pack/plugins/graph/public/services/persistence/saved_workspace.ts similarity index 97% rename from x-pack/legacy/plugins/graph/public/services/persistence/saved_workspace.ts rename to x-pack/plugins/graph/public/services/persistence/saved_workspace.ts index 025d5e6935902..e2bd885dc7209 100644 --- a/x-pack/legacy/plugins/graph/public/services/persistence/saved_workspace.ts +++ b/x-pack/plugins/graph/public/services/persistence/saved_workspace.ts @@ -9,7 +9,7 @@ import { SavedObject, createSavedObjectClass, SavedObjectKibanaServices, -} from '../../../../../../../src/plugins/saved_objects/public'; +} from '../../../../../../src/plugins/saved_objects/public'; export interface SavedWorkspace extends SavedObject { wsState?: string; diff --git a/x-pack/legacy/plugins/graph/public/services/persistence/saved_workspace_loader.ts b/x-pack/plugins/graph/public/services/persistence/saved_workspace_loader.ts similarity index 95% rename from x-pack/legacy/plugins/graph/public/services/persistence/saved_workspace_loader.ts rename to x-pack/plugins/graph/public/services/persistence/saved_workspace_loader.ts index d9bb119006e78..fb64fbadfbf7c 100644 --- a/x-pack/legacy/plugins/graph/public/services/persistence/saved_workspace_loader.ts +++ b/x-pack/plugins/graph/public/services/persistence/saved_workspace_loader.ts @@ -7,7 +7,7 @@ import { IBasePath } from 'kibana/public'; import { i18n } from '@kbn/i18n'; -import { SavedObjectKibanaServices } from '../../../../../../../src/plugins/saved_objects/public'; +import { SavedObjectKibanaServices } from '../../../../../../src/plugins/saved_objects/public'; import { createSavedWorkspaceClass } from './saved_workspace'; export function createSavedWorkspacesLoader( diff --git a/x-pack/legacy/plugins/graph/public/services/persistence/saved_workspace_references.test.ts b/x-pack/plugins/graph/public/services/persistence/saved_workspace_references.test.ts similarity index 100% rename from x-pack/legacy/plugins/graph/public/services/persistence/saved_workspace_references.test.ts rename to x-pack/plugins/graph/public/services/persistence/saved_workspace_references.test.ts diff --git a/x-pack/legacy/plugins/graph/public/services/persistence/saved_workspace_references.ts b/x-pack/plugins/graph/public/services/persistence/saved_workspace_references.ts similarity index 100% rename from x-pack/legacy/plugins/graph/public/services/persistence/saved_workspace_references.ts rename to x-pack/plugins/graph/public/services/persistence/saved_workspace_references.ts diff --git a/x-pack/legacy/plugins/graph/public/services/persistence/serialize.test.ts b/x-pack/plugins/graph/public/services/persistence/serialize.test.ts similarity index 100% rename from x-pack/legacy/plugins/graph/public/services/persistence/serialize.test.ts rename to x-pack/plugins/graph/public/services/persistence/serialize.test.ts diff --git a/x-pack/legacy/plugins/graph/public/services/persistence/serialize.ts b/x-pack/plugins/graph/public/services/persistence/serialize.ts similarity index 100% rename from x-pack/legacy/plugins/graph/public/services/persistence/serialize.ts rename to x-pack/plugins/graph/public/services/persistence/serialize.ts diff --git a/x-pack/legacy/plugins/graph/public/services/save_modal.tsx b/x-pack/plugins/graph/public/services/save_modal.tsx similarity index 100% rename from x-pack/legacy/plugins/graph/public/services/save_modal.tsx rename to x-pack/plugins/graph/public/services/save_modal.tsx diff --git a/x-pack/legacy/plugins/graph/public/services/source_modal.tsx b/x-pack/plugins/graph/public/services/source_modal.tsx similarity index 100% rename from x-pack/legacy/plugins/graph/public/services/source_modal.tsx rename to x-pack/plugins/graph/public/services/source_modal.tsx diff --git a/x-pack/legacy/plugins/graph/public/services/url.ts b/x-pack/plugins/graph/public/services/url.ts similarity index 100% rename from x-pack/legacy/plugins/graph/public/services/url.ts rename to x-pack/plugins/graph/public/services/url.ts diff --git a/x-pack/legacy/plugins/graph/public/state_management/advanced_settings.ts b/x-pack/plugins/graph/public/state_management/advanced_settings.ts similarity index 100% rename from x-pack/legacy/plugins/graph/public/state_management/advanced_settings.ts rename to x-pack/plugins/graph/public/state_management/advanced_settings.ts diff --git a/x-pack/legacy/plugins/graph/public/state_management/datasource.sagas.ts b/x-pack/plugins/graph/public/state_management/datasource.sagas.ts similarity index 96% rename from x-pack/legacy/plugins/graph/public/state_management/datasource.sagas.ts rename to x-pack/plugins/graph/public/state_management/datasource.sagas.ts index 34d39e71dec55..018b3b42b9157 100644 --- a/x-pack/legacy/plugins/graph/public/state_management/datasource.sagas.ts +++ b/x-pack/plugins/graph/public/state_management/datasource.sagas.ts @@ -17,7 +17,7 @@ import { setDatasource, requestDatasource, } from './datasource'; -import { IndexPattern } from '../../../../../../src/plugins/data/public'; +import { IndexPattern } from '../../../../../src/plugins/data/public'; /** * Saga loading field information when the datasource is switched. This will overwrite current settings diff --git a/x-pack/legacy/plugins/graph/public/state_management/datasource.test.ts b/x-pack/plugins/graph/public/state_management/datasource.test.ts similarity index 97% rename from x-pack/legacy/plugins/graph/public/state_management/datasource.test.ts rename to x-pack/plugins/graph/public/state_management/datasource.test.ts index 041098a9aaae5..84f3741604e20 100644 --- a/x-pack/legacy/plugins/graph/public/state_management/datasource.test.ts +++ b/x-pack/plugins/graph/public/state_management/datasource.test.ts @@ -10,7 +10,7 @@ import { datasourceSelector, requestDatasource } from './datasource'; import { datasourceSaga } from './datasource.sagas'; import { fieldsSelector } from './fields'; import { updateSettings } from './advanced_settings'; -import { IndexPattern } from '../../../../../../src/plugins/data/public'; +import { IndexPattern } from '../../../../../src/plugins/data/public'; const waitForPromise = () => new Promise(r => setTimeout(r)); diff --git a/x-pack/legacy/plugins/graph/public/state_management/datasource.ts b/x-pack/plugins/graph/public/state_management/datasource.ts similarity index 100% rename from x-pack/legacy/plugins/graph/public/state_management/datasource.ts rename to x-pack/plugins/graph/public/state_management/datasource.ts diff --git a/x-pack/legacy/plugins/graph/public/state_management/fields.ts b/x-pack/plugins/graph/public/state_management/fields.ts similarity index 100% rename from x-pack/legacy/plugins/graph/public/state_management/fields.ts rename to x-pack/plugins/graph/public/state_management/fields.ts diff --git a/x-pack/legacy/plugins/graph/public/state_management/global.ts b/x-pack/plugins/graph/public/state_management/global.ts similarity index 100% rename from x-pack/legacy/plugins/graph/public/state_management/global.ts rename to x-pack/plugins/graph/public/state_management/global.ts diff --git a/x-pack/legacy/plugins/graph/public/state_management/helpers.ts b/x-pack/plugins/graph/public/state_management/helpers.ts similarity index 100% rename from x-pack/legacy/plugins/graph/public/state_management/helpers.ts rename to x-pack/plugins/graph/public/state_management/helpers.ts diff --git a/x-pack/legacy/plugins/graph/public/state_management/index.ts b/x-pack/plugins/graph/public/state_management/index.ts similarity index 100% rename from x-pack/legacy/plugins/graph/public/state_management/index.ts rename to x-pack/plugins/graph/public/state_management/index.ts diff --git a/x-pack/legacy/plugins/graph/public/state_management/legacy.test.ts b/x-pack/plugins/graph/public/state_management/legacy.test.ts similarity index 100% rename from x-pack/legacy/plugins/graph/public/state_management/legacy.test.ts rename to x-pack/plugins/graph/public/state_management/legacy.test.ts diff --git a/x-pack/legacy/plugins/graph/public/state_management/meta_data.test.ts b/x-pack/plugins/graph/public/state_management/meta_data.test.ts similarity index 100% rename from x-pack/legacy/plugins/graph/public/state_management/meta_data.test.ts rename to x-pack/plugins/graph/public/state_management/meta_data.test.ts diff --git a/x-pack/legacy/plugins/graph/public/state_management/meta_data.ts b/x-pack/plugins/graph/public/state_management/meta_data.ts similarity index 100% rename from x-pack/legacy/plugins/graph/public/state_management/meta_data.ts rename to x-pack/plugins/graph/public/state_management/meta_data.ts diff --git a/x-pack/legacy/plugins/graph/public/state_management/mocks.ts b/x-pack/plugins/graph/public/state_management/mocks.ts similarity index 94% rename from x-pack/legacy/plugins/graph/public/state_management/mocks.ts rename to x-pack/plugins/graph/public/state_management/mocks.ts index 01d6927b9b886..d06f8a7b3ef0b 100644 --- a/x-pack/legacy/plugins/graph/public/state_management/mocks.ts +++ b/x-pack/plugins/graph/public/state_management/mocks.ts @@ -10,7 +10,7 @@ import { createStore, applyMiddleware, AnyAction } from 'redux'; import { ChromeStart } from 'kibana/public'; import { GraphStoreDependencies, createRootReducer, GraphStore, GraphState } from './store'; import { Workspace, GraphWorkspaceSavedObject, IndexPatternSavedObject } from '../types'; -import { IndexPattern } from '../../../../../../src/plugins/data/public'; +import { IndexPattern } from '../../../../../src/plugins/data/public'; jest.mock('ui/new_platform'); @@ -49,7 +49,7 @@ export function createMockGraphStore({ } as unknown) as GraphWorkspaceSavedObject; const mockedDeps: jest.Mocked = { - basePath: 'basepath', + addBasePath: jest.fn((url: string) => url), changeUrl: jest.fn(), chrome: ({ setBreadcrumbs: jest.fn(), @@ -83,7 +83,7 @@ export function createMockGraphStore({ }; const sagaMiddleware = createSagaMiddleware(); - const rootReducer = createRootReducer(mockedDeps.basePath); + const rootReducer = createRootReducer(mockedDeps.addBasePath); const initializedRootReducer = (state: GraphState | undefined, action: AnyAction) => rootReducer(state || (initialStateOverwrites as GraphState), action); diff --git a/x-pack/legacy/plugins/graph/public/state_management/persistence.test.ts b/x-pack/plugins/graph/public/state_management/persistence.test.ts similarity index 100% rename from x-pack/legacy/plugins/graph/public/state_management/persistence.test.ts rename to x-pack/plugins/graph/public/state_management/persistence.test.ts diff --git a/x-pack/legacy/plugins/graph/public/state_management/persistence.ts b/x-pack/plugins/graph/public/state_management/persistence.ts similarity index 100% rename from x-pack/legacy/plugins/graph/public/state_management/persistence.ts rename to x-pack/plugins/graph/public/state_management/persistence.ts diff --git a/x-pack/legacy/plugins/graph/public/state_management/store.ts b/x-pack/plugins/graph/public/state_management/store.ts similarity index 93% rename from x-pack/legacy/plugins/graph/public/state_management/store.ts rename to x-pack/plugins/graph/public/state_management/store.ts index ecb7335fee5aa..4aeef0338923b 100644 --- a/x-pack/legacy/plugins/graph/public/state_management/store.ts +++ b/x-pack/plugins/graph/public/state_management/store.ts @@ -46,7 +46,7 @@ export interface GraphState { } export interface GraphStoreDependencies { - basePath: string; + addBasePath: (url: string) => string; indexPatternProvider: IndexPatternProvider; indexPatterns: IndexPatternSavedObject[]; createWorkspace: (index: string, advancedSettings: AdvancedSettings) => void; @@ -65,10 +65,10 @@ export interface GraphStoreDependencies { I18nContext: I18nStart['Context']; } -export function createRootReducer(basePath: string) { +export function createRootReducer(addBasePath: (url: string) => string) { return combineReducers({ fields: fieldsReducer, - urlTemplates: urlTemplatesReducer(basePath), + urlTemplates: urlTemplatesReducer(addBasePath), advancedSettings: advancedSettingsReducer, datasource: datasourceReducer, metaData: metaDataReducer, @@ -91,7 +91,7 @@ function registerSagas(sagaMiddleware: SagaMiddleware, deps: GraphStoreD export const createGraphStore = (deps: GraphStoreDependencies) => { const sagaMiddleware = createSagaMiddleware(); - const rootReducer = createRootReducer(deps.basePath); + const rootReducer = createRootReducer(deps.addBasePath); const store = createStore(rootReducer, applyMiddleware(sagaMiddleware)); diff --git a/x-pack/legacy/plugins/graph/public/state_management/url_templates.test.ts b/x-pack/plugins/graph/public/state_management/url_templates.test.ts similarity index 91% rename from x-pack/legacy/plugins/graph/public/state_management/url_templates.test.ts rename to x-pack/plugins/graph/public/state_management/url_templates.test.ts index c4a3b0fb776a0..c265b2ec277d2 100644 --- a/x-pack/legacy/plugins/graph/public/state_management/url_templates.test.ts +++ b/x-pack/plugins/graph/public/state_management/url_templates.test.ts @@ -10,9 +10,11 @@ import { outlinkEncoders } from '../helpers/outlink_encoders'; import { UrlTemplate } from '../types'; describe('url_templates', () => { + const addBasePath = (url: string) => url; + describe('reducer', () => { it('should create a default template as soon as datasource is known', () => { - const templates = urlTemplatesReducer('basepath')( + const templates = urlTemplatesReducer(addBasePath)( [], requestDatasource({ type: 'indexpattern', @@ -28,7 +30,7 @@ describe('url_templates', () => { }); it('should keep non-default templates when switching datasource', () => { - const templates = urlTemplatesReducer('basepath')( + const templates = urlTemplatesReducer(addBasePath)( [ { description: 'default template', @@ -52,7 +54,7 @@ describe('url_templates', () => { }); it('should remove isDefault flag when saving a template even if it is spreaded in', () => { - const templates = urlTemplatesReducer('basepath')( + const templates = urlTemplatesReducer(addBasePath)( [ { description: 'abc', diff --git a/x-pack/legacy/plugins/graph/public/state_management/url_templates.ts b/x-pack/plugins/graph/public/state_management/url_templates.ts similarity index 82% rename from x-pack/legacy/plugins/graph/public/state_management/url_templates.ts rename to x-pack/plugins/graph/public/state_management/url_templates.ts index eac29d0ec9116..a0fb9503421a4 100644 --- a/x-pack/legacy/plugins/graph/public/state_management/url_templates.ts +++ b/x-pack/plugins/graph/public/state_management/url_templates.ts @@ -6,10 +6,10 @@ import actionCreatorFactory from 'typescript-fsa'; import { reducerWithInitialState } from 'typescript-fsa-reducers/dist'; -import { KibanaParsedUrl } from 'ui/url/kibana_parsed_url'; import { i18n } from '@kbn/i18n'; import rison from 'rison-node'; import { takeEvery, select } from 'redux-saga/effects'; +import { format, parse } from 'url'; import { GraphState, GraphStoreDependencies } from './store'; import { UrlTemplate } from '../types'; import { reset } from './global'; @@ -17,6 +17,7 @@ import { setDatasource, IndexpatternDatasource, requestDatasource } from './data import { outlinkEncoders } from '../helpers/outlink_encoders'; import { urlTemplatePlaceholder } from '../helpers/url_template'; import { matchesOne } from './helpers'; +import { modifyUrl } from '../../../../../src/core/utils'; const actionCreator = actionCreatorFactory('x-pack/graph/urlTemplates'); @@ -32,30 +33,32 @@ const initialTemplates: UrlTemplatesState = []; function generateDefaultTemplate( datasource: IndexpatternDatasource, - basePath: string + addBasePath: (url: string) => string ): UrlTemplate { - const kUrl = new KibanaParsedUrl({ - appId: 'kibana', - basePath, - appPath: '/discover', - }); - - kUrl.addQueryParameter( - '_a', - rison.encode({ + const appPath = modifyUrl('/discover', parsed => { + parsed.query._a = rison.encode({ columns: ['_source'], index: datasource.id, interval: 'auto', query: { language: 'kuery', query: urlTemplatePlaceholder }, sort: ['_score', 'desc'], - }) - ); + }); + }); + const parsedAppPath = parse(`/app/kibana#${appPath}`, true, true); + const formattedAppPath = format({ + protocol: parsedAppPath.protocol, + host: parsedAppPath.host, + pathname: parsedAppPath.pathname, + query: parsedAppPath.query, + hash: parsedAppPath.hash, + }); // replace the URI encoded version of the tag with the unescaped version // so it can be found with String.replace, regexp, etc. - const discoverUrl = kUrl - .getRootRelativePath() - .replace(encodeURIComponent(urlTemplatePlaceholder), urlTemplatePlaceholder); + const discoverUrl = addBasePath(formattedAppPath).replace( + encodeURIComponent(urlTemplatePlaceholder), + urlTemplatePlaceholder + ); return { url: discoverUrl, @@ -68,7 +71,7 @@ function generateDefaultTemplate( }; } -export const urlTemplatesReducer = (basePath: string) => +export const urlTemplatesReducer = (addBasePath: (url: string) => string) => reducerWithInitialState(initialTemplates) .case(reset, () => initialTemplates) .cases([requestDatasource, setDatasource], (templates, datasource) => { @@ -76,7 +79,7 @@ export const urlTemplatesReducer = (basePath: string) => return initialTemplates; } const customTemplates = templates.filter(template => !template.isDefault); - return [...customTemplates, generateDefaultTemplate(datasource, basePath)]; + return [...customTemplates, generateDefaultTemplate(datasource, addBasePath)]; }) .case(loadTemplates, (_currentTemplates, newTemplates) => newTemplates) .case(saveTemplate, (templates, { index: indexToUpdate, template: updatedTemplate }) => { diff --git a/x-pack/legacy/plugins/graph/public/state_management/workspace.ts b/x-pack/plugins/graph/public/state_management/workspace.ts similarity index 100% rename from x-pack/legacy/plugins/graph/public/state_management/workspace.ts rename to x-pack/plugins/graph/public/state_management/workspace.ts diff --git a/x-pack/legacy/plugins/graph/public/types/app_state.ts b/x-pack/plugins/graph/public/types/app_state.ts similarity index 94% rename from x-pack/legacy/plugins/graph/public/types/app_state.ts rename to x-pack/plugins/graph/public/types/app_state.ts index 876f2cf23b53a..21e584182785a 100644 --- a/x-pack/legacy/plugins/graph/public/types/app_state.ts +++ b/x-pack/plugins/graph/public/types/app_state.ts @@ -7,7 +7,7 @@ import { SimpleSavedObject } from 'src/core/public'; import { FontawesomeIcon } from '../helpers/style_choices'; import { OutlinkEncoder } from '../helpers/outlink_encoders'; -import { IndexPattern } from '../../../../../../src/plugins/data/public'; +import { IndexPattern } from '../../../../../src/plugins/data/public'; export interface UrlTemplate { url: string; diff --git a/x-pack/legacy/plugins/graph/public/types/config.ts b/x-pack/plugins/graph/public/types/config.ts similarity index 100% rename from x-pack/legacy/plugins/graph/public/types/config.ts rename to x-pack/plugins/graph/public/types/config.ts diff --git a/x-pack/legacy/plugins/graph/public/types/index.ts b/x-pack/plugins/graph/public/types/index.ts similarity index 100% rename from x-pack/legacy/plugins/graph/public/types/index.ts rename to x-pack/plugins/graph/public/types/index.ts diff --git a/x-pack/legacy/plugins/graph/public/types/persistence.ts b/x-pack/plugins/graph/public/types/persistence.ts similarity index 95% rename from x-pack/legacy/plugins/graph/public/types/persistence.ts rename to x-pack/plugins/graph/public/types/persistence.ts index cdaee5db202d8..b0209153c82e3 100644 --- a/x-pack/legacy/plugins/graph/public/types/persistence.ts +++ b/x-pack/plugins/graph/public/types/persistence.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SavedObject } from '../../../../../../src/plugins/saved_objects/public'; +import { SavedObject } from '../../../../../src/plugins/saved_objects/public'; import { AdvancedSettings, UrlTemplate, WorkspaceField } from './app_state'; import { WorkspaceNode, WorkspaceEdge } from './workspace_state'; diff --git a/x-pack/legacy/plugins/graph/public/types/workspace_state.ts b/x-pack/plugins/graph/public/types/workspace_state.ts similarity index 97% rename from x-pack/legacy/plugins/graph/public/types/workspace_state.ts rename to x-pack/plugins/graph/public/types/workspace_state.ts index 37a962fd569ce..8c4178eda890f 100644 --- a/x-pack/legacy/plugins/graph/public/types/workspace_state.ts +++ b/x-pack/plugins/graph/public/types/workspace_state.ts @@ -6,7 +6,7 @@ import { FontawesomeIcon } from '../helpers/style_choices'; import { WorkspaceField, AdvancedSettings } from './app_state'; -import { JsonObject } from '../../../../../../src/plugins/kibana_utils/public'; +import { JsonObject } from '../../../../../src/plugins/kibana_utils/public'; export interface WorkspaceNode { x: number; diff --git a/x-pack/plugins/ml/__mocks__/shared_imports.ts b/x-pack/plugins/ml/__mocks__/shared_imports.ts index d044ab409eb7a..f5fbbf32d30d7 100644 --- a/x-pack/plugins/ml/__mocks__/shared_imports.ts +++ b/x-pack/plugins/ml/__mocks__/shared_imports.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export function XJsonMode() {} +export const XJsonMode = jest.fn(); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts index f87578c4bce48..9c239df357163 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts @@ -19,25 +19,36 @@ export type IndexName = string; export type IndexPattern = string; export type DataFrameAnalyticsId = string; +export enum ANALYSIS_CONFIG_TYPE { + OUTLIER_DETECTION = 'outlier_detection', + REGRESSION = 'regression', + CLASSIFICATION = 'classification', +} + interface OutlierAnalysis { + [key: string]: {}; outlier_detection: {}; } -interface RegressionAnalysis { - regression: { - dependent_variable: string; - training_percent?: number; - prediction_field_name?: string; - }; +interface Regression { + dependent_variable: string; + training_percent?: number; + prediction_field_name?: string; +} +export interface RegressionAnalysis { + [key: string]: Regression; + regression: Regression; } -interface ClassificationAnalysis { - classification: { - dependent_variable: string; - training_percent?: number; - num_top_classes?: string; - prediction_field_name?: string; - }; +interface Classification { + dependent_variable: string; + training_percent?: number; + num_top_classes?: string; + prediction_field_name?: string; +} +export interface ClassificationAnalysis { + [key: string]: Classification; + classification: Classification; } export interface LoadExploreDataArg { @@ -136,13 +147,6 @@ type AnalysisConfig = | ClassificationAnalysis | GenericAnalysis; -export enum ANALYSIS_CONFIG_TYPE { - OUTLIER_DETECTION = 'outlier_detection', - REGRESSION = 'regression', - CLASSIFICATION = 'classification', - UNKNOWN = 'unknown', -} - export const getAnalysisType = (analysis: AnalysisConfig) => { const keys = Object.keys(analysis); @@ -150,7 +154,7 @@ export const getAnalysisType = (analysis: AnalysisConfig) => { return keys[0]; } - return ANALYSIS_CONFIG_TYPE.UNKNOWN; + return 'unknown'; }; export const getDependentVar = (analysis: AnalysisConfig) => { @@ -245,6 +249,7 @@ export interface DataFrameAnalyticsConfig { }; source: { index: IndexName | IndexName[]; + query?: any; }; analysis: AnalysisConfig; analyzed_fields: { @@ -254,6 +259,7 @@ export interface DataFrameAnalyticsConfig { model_memory_limit: string; create_time: number; version: string; + allow_lazy_start?: boolean; } export enum REFRESH_ANALYTICS_LIST_STATE { diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_clone.test.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_clone.test.ts new file mode 100644 index 0000000000000..6225bca592be3 --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_clone.test.ts @@ -0,0 +1,254 @@ +/* + * 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 { isAdvancedConfig } from './action_clone'; + +describe('Analytics job clone action', () => { + describe('isAdvancedConfig', () => { + test('should detect a classification job created with the form', () => { + const formCreatedClassificationJob = { + description: "Classification job with 'bank-marketing' dataset", + source: { + index: ['bank-marketing'], + query: { + match_all: {}, + }, + }, + dest: { + index: 'dest_bank_1', + results_field: 'ml', + }, + analysis: { + classification: { + dependent_variable: 'y', + num_top_classes: 2, + prediction_field_name: 'y_prediction', + training_percent: 2, + randomize_seed: 6233212276062807000, + }, + }, + analyzed_fields: { + includes: [], + excludes: [], + }, + model_memory_limit: '350mb', + allow_lazy_start: false, + }; + + expect(isAdvancedConfig(formCreatedClassificationJob)).toBe(false); + }); + + test('should detect a outlier_detection job created with the form', () => { + const formCreatedOutlierDetectionJob = { + description: "Outlier detection job with 'glass' dataset", + source: { + index: ['glass_withoutdupl_norm'], + query: { + match_all: {}, + }, + }, + dest: { + index: 'dest_glass_1', + results_field: 'ml', + }, + analysis: { + outlier_detection: { + compute_feature_influence: true, + outlier_fraction: 0.05, + standardization_enabled: true, + }, + }, + analyzed_fields: { + includes: [], + excludes: ['id', 'outlier'], + }, + model_memory_limit: '1mb', + allow_lazy_start: false, + }; + expect(isAdvancedConfig(formCreatedOutlierDetectionJob)).toBe(false); + }); + + test('should detect a regression job created with the form', () => { + const formCreatedRegressionJob = { + description: "Regression job with 'electrical-grid-stability' dataset", + source: { + index: ['electrical-grid-stability'], + query: { + match_all: {}, + }, + }, + dest: { + index: 'dest_grid_1', + results_field: 'ml', + }, + analysis: { + regression: { + dependent_variable: 'stab', + prediction_field_name: 'stab_prediction', + training_percent: 20, + randomize_seed: -2228827740028660200, + }, + }, + analyzed_fields: { + includes: [], + excludes: [], + }, + model_memory_limit: '150mb', + allow_lazy_start: false, + }; + + expect(isAdvancedConfig(formCreatedRegressionJob)).toBe(false); + }); + + test('should detect advanced classification job', () => { + const advancedClassificationJob = { + description: "Classification job with 'bank-marketing' dataset", + source: { + index: ['bank-marketing'], + query: { + match_all: {}, + }, + }, + dest: { + index: 'dest_bank_1', + results_field: 'CUSTOM_RESULT_FIELD', + }, + analysis: { + classification: { + dependent_variable: 'y', + num_top_classes: 2, + prediction_field_name: 'y_prediction', + training_percent: 2, + randomize_seed: 6233212276062807000, + }, + }, + analyzed_fields: { + includes: [], + excludes: [], + }, + model_memory_limit: '350mb', + allow_lazy_start: false, + }; + + expect(isAdvancedConfig(advancedClassificationJob)).toBe(true); + }); + + test('should detect advanced outlier_detection job', () => { + const advancedOutlierDetectionJob = { + description: "Outlier detection job with 'glass' dataset", + source: { + index: ['glass_withoutdupl_norm'], + query: { + // TODO check default for `match` + match_all: {}, + }, + }, + dest: { + index: 'dest_glass_1', + results_field: 'ml', + }, + analysis: { + outlier_detection: { + compute_feature_influence: false, + outlier_fraction: 0.05, + standardization_enabled: true, + }, + }, + analyzed_fields: { + includes: [], + excludes: ['id', 'outlier'], + }, + model_memory_limit: '1mb', + allow_lazy_start: false, + }; + expect(isAdvancedConfig(advancedOutlierDetectionJob)).toBe(true); + }); + + test('should detect a custom query', () => { + const advancedRegressionJob = { + description: "Regression job with 'electrical-grid-stability' dataset", + source: { + index: ['electrical-grid-stability'], + query: { + match: { + custom_field: 'custom_match', + }, + }, + }, + dest: { + index: 'dest_grid_1', + results_field: 'ml', + }, + analysis: { + regression: { + dependent_variable: 'stab', + prediction_field_name: 'stab_prediction', + training_percent: 20, + randomize_seed: -2228827740028660200, + }, + }, + analyzed_fields: { + includes: [], + excludes: [], + }, + model_memory_limit: '150mb', + allow_lazy_start: false, + }; + + expect(isAdvancedConfig(advancedRegressionJob)).toBe(true); + }); + + test('should detect custom analysis settings', () => { + const config = { + description: "Classification clone with 'bank-marketing' dataset", + source: { + index: 'bank-marketing', + }, + dest: { + index: 'bank_classification4', + }, + analyzed_fields: { + excludes: [], + }, + analysis: { + classification: { + dependent_variable: 'y', + training_percent: 71, + max_trees: 1500, + }, + }, + model_memory_limit: '400mb', + }; + + expect(isAdvancedConfig(config)).toBe(true); + }); + + test('should detect as advanced if the prop is unknown', () => { + const config = { + description: "Classification clone with 'bank-marketing' dataset", + source: { + index: 'bank-marketing', + }, + dest: { + index: 'bank_classification4', + }, + analyzed_fields: { + excludes: [], + }, + analysis: { + classification: { + dependent_variable: 'y', + training_percent: 71, + maximum_number_trees: 1500, + }, + }, + model_memory_limit: '400mb', + }; + + expect(isAdvancedConfig(config)).toBe(true); + }); + }); +}); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_clone.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_clone.tsx new file mode 100644 index 0000000000000..7199453a15d7f --- /dev/null +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_clone.tsx @@ -0,0 +1,327 @@ +/* + * 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, { FC } from 'react'; +import { isEqual } from 'lodash'; +import { i18n } from '@kbn/i18n'; +import { DataFrameAnalyticsConfig, isOutlierAnalysis } from '../../../../common'; +import { isClassificationAnalysis, isRegressionAnalysis } from '../../../../common/analytics'; +import { CreateAnalyticsFormProps } from '../../hooks/use_create_analytics_form'; +import { State } from '../../hooks/use_create_analytics_form/state'; +import { DataFrameAnalyticsListRow } from './common'; + +interface PropDefinition { + /** + * Indicates if the property is optional + */ + optional: boolean; + /** + * Corresponding property from the form + */ + formKey?: keyof State['form']; + /** + * Default value of the property + */ + defaultValue?: any; + /** + * Indicates if the value has to be ignored + * during detecting advanced configuration + */ + ignore?: boolean; +} + +function isPropDefinition(a: PropDefinition | object): a is PropDefinition { + return a.hasOwnProperty('optional'); +} + +interface AnalyticsJobMetaData { + [key: string]: PropDefinition | AnalyticsJobMetaData; +} + +/** + * Provides a config definition. + */ +const getAnalyticsJobMeta = (config: CloneDataFrameAnalyticsConfig): AnalyticsJobMetaData => ({ + allow_lazy_start: { + optional: true, + defaultValue: false, + }, + description: { + optional: true, + formKey: 'description', + }, + analysis: { + ...(isClassificationAnalysis(config.analysis) + ? { + classification: { + dependent_variable: { + optional: false, + formKey: 'dependentVariable', + }, + training_percent: { + optional: true, + formKey: 'trainingPercent', + }, + eta: { + optional: true, + }, + feature_bag_fraction: { + optional: true, + }, + max_trees: { + optional: true, + }, + gamma: { + optional: true, + }, + lambda: { + optional: true, + }, + num_top_classes: { + optional: true, + defaultValue: 2, + }, + prediction_field_name: { + optional: true, + defaultValue: `${config.analysis.classification.dependent_variable}_prediction`, + }, + randomize_seed: { + optional: true, + // By default it is randomly generated + ignore: true, + }, + num_top_feature_importance_values: { + optional: true, + }, + }, + } + : {}), + ...(isOutlierAnalysis(config.analysis) + ? { + outlier_detection: { + standardization_enabled: { + defaultValue: true, + optional: true, + }, + compute_feature_influence: { + defaultValue: true, + optional: true, + }, + outlier_fraction: { + defaultValue: 0.05, + optional: true, + }, + feature_influence_threshold: { + optional: true, + }, + method: { + optional: true, + }, + n_neighbors: { + optional: true, + }, + }, + } + : {}), + ...(isRegressionAnalysis(config.analysis) + ? { + regression: { + dependent_variable: { + optional: false, + formKey: 'dependentVariable', + }, + training_percent: { + optional: true, + formKey: 'trainingPercent', + }, + eta: { + optional: true, + }, + feature_bag_fraction: { + optional: true, + }, + max_trees: { + optional: true, + }, + gamma: { + optional: true, + }, + lambda: { + optional: true, + }, + prediction_field_name: { + optional: true, + defaultValue: `${config.analysis.regression.dependent_variable}_prediction`, + }, + num_top_feature_importance_values: { + optional: true, + }, + randomize_seed: { + optional: true, + // By default it is randomly generated + ignore: true, + }, + }, + } + : {}), + }, + analyzed_fields: { + excludes: { + optional: true, + formKey: 'excludes', + defaultValue: [], + }, + includes: { + optional: true, + defaultValue: [], + }, + }, + source: { + index: { + formKey: 'sourceIndex', + optional: false, + }, + query: { + optional: true, + defaultValue: { + match_all: {}, + }, + }, + _source: { + optional: true, + }, + }, + dest: { + index: { + optional: false, + formKey: 'destinationIndex', + }, + results_field: { + optional: true, + defaultValue: 'ml', + }, + }, + model_memory_limit: { + optional: true, + formKey: 'modelMemoryLimit', + }, +}); + +/** + * Detects if analytics job configuration were created with + * the advanced editor and not supported by the regular form. + */ +export function isAdvancedConfig(config: any, meta?: AnalyticsJobMetaData): boolean; +export function isAdvancedConfig( + config: CloneDataFrameAnalyticsConfig, + meta: AnalyticsJobMetaData = getAnalyticsJobMeta(config) +): boolean { + for (const configKey in config) { + if (config.hasOwnProperty(configKey)) { + const fieldConfig = config[configKey as keyof typeof config]; + const fieldMeta = meta[configKey as keyof typeof meta]; + + if (!fieldMeta) { + // eslint-disable-next-line no-console + console.info(`Property "${configKey}" is unknown.`); + return true; + } + + if (isPropDefinition(fieldMeta)) { + const isAdvancedSetting = + fieldMeta.formKey === undefined && + fieldMeta.ignore !== true && + !isEqual(fieldMeta.defaultValue, fieldConfig); + + if (isAdvancedSetting) { + // eslint-disable-next-line no-console + console.info( + `Property "${configKey}" is not supported by the form or has a different value to the default.` + ); + return true; + } + } else if (isAdvancedConfig(fieldConfig, fieldMeta)) { + return true; + } + } + } + return false; +} + +export type CloneDataFrameAnalyticsConfig = Omit< + DataFrameAnalyticsConfig, + 'id' | 'version' | 'create_time' +>; + +export function extractCloningConfig( + originalConfig: DataFrameAnalyticsConfig +): CloneDataFrameAnalyticsConfig { + const { + // Omit non-relevant props from the configuration + id, + version, + create_time, + ...cloneConfig + } = originalConfig; + + // Reset the destination index + cloneConfig.dest.index = ''; + return cloneConfig; +} + +export function getCloneAction(createAnalyticsForm: CreateAnalyticsFormProps) { + const buttonText = i18n.translate('xpack.ml.dataframe.analyticsList.cloneJobButtonLabel', { + defaultMessage: 'Clone job', + }); + + const { actions } = createAnalyticsForm; + + const onClick = async (item: DataFrameAnalyticsListRow) => { + await actions.setJobClone(item.config); + }; + + return { + name: buttonText, + description: buttonText, + icon: 'copy', + onClick, + 'data-test-subj': 'mlAnalyticsJobCloneButton', + }; +} + +interface CloneActionProps { + item: DataFrameAnalyticsListRow; + createAnalyticsForm: CreateAnalyticsFormProps; +} + +/** + * Temp component to have Clone job button with the same look as the other actions. + * Replace with {@link getCloneAction} as soon as all the actions are refactored + * to support EuiContext with a valid DOM structure without nested buttons. + */ +export const CloneAction: FC = ({ createAnalyticsForm, item }) => { + const buttonText = i18n.translate('xpack.ml.dataframe.analyticsList.cloneJobButtonLabel', { + defaultMessage: 'Clone job', + }); + const { actions } = createAnalyticsForm; + const onClick = async () => { + await actions.setJobClone(item.config); + }; + + return ( + + {buttonText} + + ); +}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_delete.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_delete.tsx index 75841b52521bd..47fc84cf450c0 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_delete.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_delete.tsx @@ -54,6 +54,7 @@ export const DeleteAction: FC = ({ item }) => { iconType="trash" onClick={openModal} aria-label={buttonDeleteText} + style={{ padding: 0 }} > {buttonDeleteText} diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/actions.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/actions.tsx index eb87bfd96c149..0436bcfc36847 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/actions.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/actions.tsx @@ -19,6 +19,8 @@ import { isOutlierAnalysis, isClassificationAnalysis, } from '../../../../common/analytics'; +import { CreateAnalyticsFormProps } from '../../hooks/use_create_analytics_form'; +import { CloneAction } from './action_clone'; import { getResultsUrl, isDataFrameAnalyticsRunning, DataFrameAnalyticsListRow } from './common'; import { stopAnalytics } from '../../services/analytics_service'; @@ -57,7 +59,7 @@ export const AnalyticsViewAction = { }, }; -export const getActions = () => { +export const getActions = (createAnalyticsForm: CreateAnalyticsFormProps) => { const canStartStopDataFrameAnalytics: boolean = checkPermission('canStartStopDataFrameAnalytics'); return [ @@ -104,5 +106,10 @@ export const getActions = () => { return ; }, }, + { + render: (item: DataFrameAnalyticsListRow) => { + return ; + }, + }, ]; }; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx index 412779513e533..10be0a74e17e6 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx @@ -254,7 +254,8 @@ export const DataFrameAnalyticsList: FC = ({ expandedRowItemIds, setExpandedRowItemIds, isManagementTable, - isMlEnabledInSpace + isMlEnabledInSpace, + createAnalyticsForm ); const sorting = { @@ -375,6 +376,10 @@ export const DataFrameAnalyticsList: FC = ({ })} /> + + {!isManagementTable && createAnalyticsForm?.state.isModalVisible && ( + + )} ); }; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/columns.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/columns.tsx index 07ae2c176c363..00cd9e3f1e0dd 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/columns.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/columns.tsx @@ -20,6 +20,7 @@ import { } from '@elastic/eui'; import { getAnalysisType, DataFrameAnalyticsId } from '../../../../common'; +import { CreateAnalyticsFormProps } from '../../hooks/use_create_analytics_form'; import { getDataFrameAnalyticsProgress, isDataFrameAnalyticsFailed, @@ -125,9 +126,11 @@ export const getColumns = ( expandedRowItemIds: DataFrameAnalyticsId[], setExpandedRowItemIds: React.Dispatch>, isManagementTable: boolean = false, - isMlEnabledInSpace: boolean = true + isMlEnabledInSpace: boolean = true, + createAnalyticsForm?: CreateAnalyticsFormProps ) => { - const actions = isManagementTable === true ? [AnalyticsViewAction] : getActions(); + const actions = + isManagementTable === true ? [AnalyticsViewAction] : getActions(createAnalyticsForm!); function toggleDetails(item: DataFrameAnalyticsListRow) { const index = expandedRowItemIds.indexOf(item.config.id); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_advanced_editor/create_analytics_advanced_editor.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_advanced_editor/create_analytics_advanced_editor.tsx index 399fa4c816877..7675553515f84 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_advanced_editor/create_analytics_advanced_editor.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_advanced_editor/create_analytics_advanced_editor.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { FC, Fragment } from 'react'; +import React, { FC, Fragment, useEffect, useRef } from 'react'; import { EuiCallOut, @@ -41,6 +41,8 @@ export const CreateAnalyticsAdvancedEditor: FC = ({ ac jobIdValid, } = state.form; + const forceInput = useRef(null); + const onChange = (str: string) => { setAdvancedEditorRawString(str); try { @@ -51,6 +53,16 @@ export const CreateAnalyticsAdvancedEditor: FC = ({ ac } }; + // Temp effect to close the context menu popover on Clone button click + useEffect(() => { + if (forceInput.current === null) { + return; + } + const evt = document.createEvent('MouseEvents'); + evt.initEvent('mouseup', true, true); + forceInput.current.dispatchEvent(evt); + }, []); + return ( {requestMessages.map((requestMessage, i) => ( @@ -98,6 +110,11 @@ export const CreateAnalyticsAdvancedEditor: FC = ({ ac ]} > { + if (input) { + forceInput.current = input; + } + }} disabled={isJobCreated} placeholder="analytics job ID" value={jobId} diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_button/create_analytics_button.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_button/create_analytics_button.tsx index 0958dff7a3f51..e5054e8a6ad2c 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_button/create_analytics_button.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_button/create_analytics_button.tsx @@ -4,18 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Fragment, FC } from 'react'; - +import React, { FC } from 'react'; import { EuiButton, EuiToolTip } from '@elastic/eui'; - import { i18n } from '@kbn/i18n'; - import { createPermissionFailureMessage } from '../../../../../privilege/check_privilege'; - import { CreateAnalyticsFormProps } from '../../hooks/use_create_analytics_form'; -import { CreateAnalyticsFlyoutWrapper } from '../create_analytics_flyout_wrapper'; - export const CreateAnalyticsButton: FC = props => { const { disabled } = props.state; const { openModal } = props.actions; @@ -46,10 +40,5 @@ export const CreateAnalyticsButton: FC = props => { ); } - return ( - - {button} - - - ); + return button; }; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_flyout/create_analytics_flyout.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_flyout/create_analytics_flyout.tsx index e31c12e2c62d0..32384e1949d0a 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_flyout/create_analytics_flyout.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_flyout/create_analytics_flyout.tsx @@ -26,17 +26,22 @@ export const CreateAnalyticsFlyout: FC = ({ state, }) => { const { closeModal, createAnalyticsJob, startAnalyticsJob } = actions; - const { isJobCreated, isJobStarted, isModalButtonDisabled, isValid } = state; + const { isJobCreated, isJobStarted, isModalButtonDisabled, isValid, cloneJob } = state; + + const headerText = !!cloneJob + ? i18n.translate('xpack.ml.dataframe.analytics.clone.flyoutHeaderTitle', { + defaultMessage: 'Clone job from {job_id}', + values: { job_id: cloneJob.id }, + }) + : i18n.translate('xpack.ml.dataframe.analytics.create.flyoutHeaderTitle', { + defaultMessage: 'Create analytics job', + }); return ( -

- {i18n.translate('xpack.ml.dataframe.analytics.create.flyoutHeaderTitle', { - defaultMessage: 'Create analytics job', - })} -

+

{headerText}

{children} diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.tsx index 97484b9da8b68..8e7024d2a9147 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Fragment, FC, useEffect, useMemo } from 'react'; +import React, { Fragment, FC, useEffect, useMemo, useRef } from 'react'; import { EuiComboBox, @@ -23,14 +23,13 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { useMlKibana } from '../../../../../contexts/kibana'; import { ml } from '../../../../../services/ml_api_service'; -import { Field } from '../../../../../../../common/types/fields'; import { newJobCapsService } from '../../../../../services/new_job_capabilities_service'; import { useMlContext } from '../../../../../contexts/ml'; import { CreateAnalyticsFormProps } from '../../hooks/use_create_analytics_form'; import { - JOB_TYPES, DEFAULT_MODEL_MEMORY_LIMIT, getJobConfigFromFormState, + State, } from '../../hooks/use_create_analytics_form/state'; import { JOB_ID_MAX_LENGTH } from '../../../../../../../common/constants/validation'; import { Messages } from './messages'; @@ -38,7 +37,11 @@ import { JobType } from './job_type'; import { JobDescriptionInput } from './job_description'; import { getModelMemoryLimitErrors } from '../../hooks/use_create_analytics_form/reducer'; import { IndexPattern, indexPatterns } from '../../../../../../../../../../src/plugins/data/public'; -import { DfAnalyticsExplainResponse, FieldSelectionItem } from '../../../../common/analytics'; +import { + ANALYSIS_CONFIG_TYPE, + DfAnalyticsExplainResponse, + FieldSelectionItem, +} from '../../../../common/analytics'; import { shouldAddAsDepVarOption, OMIT_FIELDS } from './form_options_validation'; export const CreateAnalyticsForm: FC = ({ actions, state }) => { @@ -50,6 +53,9 @@ export const CreateAnalyticsForm: FC = ({ actions, sta const mlContext = useMlContext(); const { form, indexPatternsMap, isAdvancedEditorEnabled, isJobCreated, requestMessages } = state; + const forceInput = useRef(null); + const firstUpdate = useRef(true); + const { createIndexPattern, dependentVariable, @@ -91,7 +97,7 @@ export const CreateAnalyticsForm: FC = ({ actions, sta ]); const isJobTypeWithDepVar = - jobType === JOB_TYPES.REGRESSION || jobType === JOB_TYPES.CLASSIFICATION; + jobType === ANALYSIS_CONFIG_TYPE.REGRESSION || jobType === ANALYSIS_CONFIG_TYPE.CLASSIFICATION; // Find out if index pattern contain numeric fields. Provides a hint in the form // that an analytics jobs is not able to identify outliers if there are no numeric fields present. @@ -139,6 +145,10 @@ export const CreateAnalyticsForm: FC = ({ actions, sta }; const debouncedGetExplainData = debounce(async () => { + const shouldUpdateModelMemoryLimit = !firstUpdate.current || !modelMemoryLimit; + if (firstUpdate.current) { + firstUpdate.current = false; + } // Reset if sourceIndex or jobType changes (jobType requires dependent_variable to be set - // which won't be the case if switching from outlier detection) if (previousSourceIndex !== sourceIndex || previousJobType !== jobType) { @@ -157,7 +167,9 @@ export const CreateAnalyticsForm: FC = ({ actions, sta ); const expectedMemoryWithoutDisk = resp.memory_estimation?.expected_memory_without_disk; - setEstimatedModelMemoryLimit(expectedMemoryWithoutDisk); + if (shouldUpdateModelMemoryLimit) { + setEstimatedModelMemoryLimit(expectedMemoryWithoutDisk); + } // If sourceIndex has changed load analysis field options again if (previousSourceIndex !== sourceIndex || previousJobType !== jobType) { @@ -172,7 +184,7 @@ export const CreateAnalyticsForm: FC = ({ actions, sta } setFormState({ - ...(!modelMemoryLimit ? { modelMemoryLimit: expectedMemoryWithoutDisk } : {}), + ...(shouldUpdateModelMemoryLimit ? { modelMemoryLimit: expectedMemoryWithoutDisk } : {}), excludesOptions: analyzedFieldsOptions, loadingFieldOptions: false, fieldOptionsFetchFail: false, @@ -180,13 +192,13 @@ export const CreateAnalyticsForm: FC = ({ actions, sta }); } else { setFormState({ - ...(!modelMemoryLimit ? { modelMemoryLimit: expectedMemoryWithoutDisk } : {}), + ...(shouldUpdateModelMemoryLimit ? { modelMemoryLimit: expectedMemoryWithoutDisk } : {}), }); } } catch (e) { let errorMessage; if ( - jobType === JOB_TYPES.CLASSIFICATION && + jobType === ANALYSIS_CONFIG_TYPE.CLASSIFICATION && e.message !== undefined && e.message.includes('status_exception') && e.message.includes('must have at most') @@ -202,16 +214,15 @@ export const CreateAnalyticsForm: FC = ({ actions, sta fieldOptionsFetchFail: true, maxDistinctValuesError: errorMessage, loadingFieldOptions: false, - modelMemoryLimit: fallbackModelMemoryLimit, + ...(shouldUpdateModelMemoryLimit ? { modelMemoryLimit: fallbackModelMemoryLimit } : {}), }); } }, 400); - const loadDepVarOptions = async () => { + const loadDepVarOptions = async (formState: State['form']) => { setFormState({ loadingDepVarOptions: true, // clear when the source index changes - dependentVariable: '', maxDistinctValuesError: undefined, sourceIndexFieldsCheckFailed: false, sourceIndexContainsNumericalFields: true, @@ -222,23 +233,39 @@ export const CreateAnalyticsForm: FC = ({ actions, sta ); if (indexPattern !== undefined) { + const formStateUpdate: { + loadingDepVarOptions: boolean; + dependentVariableFetchFail: boolean; + dependentVariableOptions: State['form']['dependentVariableOptions']; + dependentVariable?: State['form']['dependentVariable']; + } = { + loadingDepVarOptions: false, + dependentVariableFetchFail: false, + dependentVariableOptions: [] as State['form']['dependentVariableOptions'], + }; + await newJobCapsService.initializeFromIndexPattern(indexPattern); // Get fields and filter for supported types for job type const { fields } = newJobCapsService; - const depVarOptions: EuiComboBoxOptionOption[] = []; - - fields.forEach((field: Field) => { + let resetDependentVariable = true; + for (const field of fields) { if (shouldAddAsDepVarOption(field, jobType)) { - depVarOptions.push({ label: field.id }); + formStateUpdate.dependentVariableOptions.push({ + label: field.id, + }); + + if (formState.dependentVariable === field.id) { + resetDependentVariable = false; + } } - }); + } - setFormState({ - dependentVariableOptions: depVarOptions, - loadingDepVarOptions: false, - dependentVariableFetchFail: false, - }); + if (resetDependentVariable) { + formStateUpdate.dependentVariable = ''; + } + + setFormState(formStateUpdate); } } catch (e) { setFormState({ loadingDepVarOptions: false, dependentVariableFetchFail: true }); @@ -284,10 +311,10 @@ export const CreateAnalyticsForm: FC = ({ actions, sta useEffect(() => { if (isJobTypeWithDepVar && sourceIndexNameEmpty === false) { - loadDepVarOptions(); + loadDepVarOptions(form); } - if (jobType === JOB_TYPES.OUTLIER_DETECTION && sourceIndexNameEmpty === false) { + if (jobType === ANALYSIS_CONFIG_TYPE.OUTLIER_DETECTION && sourceIndexNameEmpty === false) { validateSourceIndexFields(); } }, [sourceIndex, jobType, sourceIndexNameEmpty]); @@ -297,7 +324,8 @@ export const CreateAnalyticsForm: FC = ({ actions, sta jobType !== undefined && sourceIndex !== '' && sourceIndexNameValid === true; const hasRequiredAnalysisFields = - (isJobTypeWithDepVar && dependentVariable !== '') || jobType === JOB_TYPES.OUTLIER_DETECTION; + (isJobTypeWithDepVar && dependentVariable !== '') || + jobType === ANALYSIS_CONFIG_TYPE.OUTLIER_DETECTION; if (hasBasicRequiredFields && hasRequiredAnalysisFields) { debouncedGetExplainData(); @@ -308,6 +336,16 @@ export const CreateAnalyticsForm: FC = ({ actions, sta }; }, [jobType, sourceIndex, sourceIndexNameEmpty, dependentVariable, trainingPercent]); + // Temp effect to close the context menu popover on Clone button click + useEffect(() => { + if (forceInput.current === null) { + return; + } + const evt = document.createEvent('MouseEvents'); + evt.initEvent('mouseup', true, true); + forceInput.current.dispatchEvent(evt); + }, []); + return ( @@ -375,6 +413,11 @@ export const CreateAnalyticsForm: FC = ({ actions, sta ]} > { + if (input) { + forceInput.current = input; + } + }} disabled={isJobCreated} placeholder={i18n.translate('xpack.ml.dataframe.analytics.create.jobIdPlaceholder', { defaultMessage: 'Job ID', @@ -495,7 +538,8 @@ export const CreateAnalyticsForm: FC = ({ actions, sta data-test-subj="mlAnalyticsCreateJobFlyoutDestinationIndexInput" /> - {(jobType === JOB_TYPES.REGRESSION || jobType === JOB_TYPES.CLASSIFICATION) && ( + {(jobType === ANALYSIS_CONFIG_TYPE.REGRESSION || + jobType === ANALYSIS_CONFIG_TYPE.CLASSIFICATION) && ( = ({ description, setFormState }) => label={i18n.translate('xpack.ml.dataframe.analytics.create.jobDescription.label', { defaultMessage: 'Job description', })} - helpText={helpText} > { const value = e.target.value; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/job_type.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/job_type.tsx index ffed1ebf522f4..0269ae2915d57 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/job_type.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/job_type.tsx @@ -8,8 +8,9 @@ import React, { Fragment, FC } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiFormRow, EuiSelect } from '@elastic/eui'; +import { ANALYSIS_CONFIG_TYPE } from '../../../../common'; -import { AnalyticsJobType, JOB_TYPES } from '../../hooks/use_create_analytics_form/state'; +import { AnalyticsJobType } from '../../hooks/use_create_analytics_form/state'; interface Props { type: AnalyticsJobType; @@ -42,9 +43,9 @@ export const JobType: FC = ({ type, setFormState }) => { ); const helpText = { - outlier_detection: outlierHelpText, - regression: regressionHelpText, - classification: classificationHelpText, + [ANALYSIS_CONFIG_TYPE.REGRESSION]: regressionHelpText, + [ANALYSIS_CONFIG_TYPE.OUTLIER_DETECTION]: outlierHelpText, + [ANALYSIS_CONFIG_TYPE.CLASSIFICATION]: classificationHelpText, }; return ( @@ -56,7 +57,7 @@ export const JobType: FC = ({ type, setFormState }) => { helpText={type !== undefined ? helpText[type] : ''} > ({ + options={Object.values(ANALYSIS_CONFIG_TYPE).map(jobType => ({ value: jobType, text: jobType.replace(/_/g, ' '), }))} diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/actions.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/actions.ts index 70228f0238fda..8cedc38b1b59b 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/actions.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/actions.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { DataFrameAnalyticsConfig } from '../../../../common'; import { FormMessage, State, SourceIndexMap } from './state'; export enum ACTION { @@ -25,6 +26,7 @@ export enum ACTION { SET_JOB_IDS, SWITCH_TO_ADVANCED_EDITOR, SET_ESTIMATED_MODEL_MEMORY_LIMIT, + SET_JOB_CLONE, } export type Action = @@ -61,13 +63,14 @@ export type Action = | { type: ACTION.SET_IS_MODAL_VISIBLE; isModalVisible: State['isModalVisible'] } | { type: ACTION.SET_JOB_CONFIG; payload: State['jobConfig'] } | { type: ACTION.SET_JOB_IDS; jobIds: State['jobIds'] } - | { type: ACTION.SET_ESTIMATED_MODEL_MEMORY_LIMIT; value: State['estimatedModelMemoryLimit'] }; + | { type: ACTION.SET_ESTIMATED_MODEL_MEMORY_LIMIT; value: State['estimatedModelMemoryLimit'] } + | { type: ACTION.SET_JOB_CLONE; cloneJob: DataFrameAnalyticsConfig }; // Actions wrapping the dispatcher exposed by the custom hook export interface ActionDispatchers { closeModal: () => void; createAnalyticsJob: () => void; - openModal: () => void; + openModal: () => Promise; resetAdvancedEditorMessages: () => void; setAdvancedEditorRawString: (payload: State['advancedEditorRawString']) => void; setFormState: (payload: Partial) => void; @@ -76,4 +79,5 @@ export interface ActionDispatchers { startAnalyticsJob: () => void; switchToAdvancedEditor: () => void; setEstimatedModelMemoryLimit: (value: State['estimatedModelMemoryLimit']) => void; + setJobClone: (cloneJob: DataFrameAnalyticsConfig) => Promise; } diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.test.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.test.ts index 5c989f7248a9e..8112a0fdb9e29 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.test.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.test.ts @@ -6,11 +6,11 @@ import { merge } from 'lodash'; -import { DataFrameAnalyticsConfig } from '../../../../common'; +import { ANALYSIS_CONFIG_TYPE, DataFrameAnalyticsConfig } from '../../../../common'; import { ACTION } from './actions'; import { reducer, validateAdvancedEditor, validateMinMML } from './reducer'; -import { getInitialState, JOB_TYPES } from './state'; +import { getInitialState } from './state'; type SourceIndex = DataFrameAnalyticsConfig['source']['index']; @@ -52,7 +52,7 @@ describe('useCreateAnalyticsForm', () => { destinationIndex: 'the-destination-index', jobId: 'the-analytics-job-id', sourceIndex: 'the-source-index', - jobType: JOB_TYPES.OUTLIER_DETECTION, + jobType: ANALYSIS_CONFIG_TYPE.OUTLIER_DETECTION, modelMemoryLimit: '200mb', }, }); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts index 5f21f17b92735..d045749a1a0dd 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts @@ -8,10 +8,11 @@ import { i18n } from '@kbn/i18n'; import { memoize } from 'lodash'; // @ts-ignore import numeral from '@elastic/numeral'; +import { isEmpty } from 'lodash'; import { isValidIndexName } from '../../../../../../../common/util/es_utils'; import { Action, ACTION } from './actions'; -import { getInitialState, getJobConfigFromFormState, State, JOB_TYPES } from './state'; +import { getInitialState, getJobConfigFromFormState, State } from './state'; import { isJobIdValid, validateModelMemoryLimitUnits, @@ -30,6 +31,7 @@ import { getDependentVar, isRegressionAnalysis, isClassificationAnalysis, + ANALYSIS_CONFIG_TYPE, } from '../../../../common/analytics'; import { indexPatterns } from '../../../../../../../../../../src/plugins/data/public'; @@ -142,7 +144,7 @@ export const validateAdvancedEditor = (state: State): State => { if ( jobConfig.analysis === undefined && - (jobType === JOB_TYPES.CLASSIFICATION || jobType === JOB_TYPES.REGRESSION) + (jobType === ANALYSIS_CONFIG_TYPE.CLASSIFICATION || jobType === ANALYSIS_CONFIG_TYPE.REGRESSION) ) { dependentVariableEmpty = true; } @@ -315,7 +317,8 @@ const validateForm = (state: State): State => { const jobTypeEmpty = jobType === undefined; const dependentVariableEmpty = - (jobType === JOB_TYPES.REGRESSION || jobType === JOB_TYPES.CLASSIFICATION) && + (jobType === ANALYSIS_CONFIG_TYPE.REGRESSION || + jobType === ANALYSIS_CONFIG_TYPE.CLASSIFICATION) && dependentVariable === ''; const mmlValidationResult = validateMml(estimatedModelMemoryLimit, modelMemoryLimit); @@ -437,7 +440,11 @@ export function reducer(state: State, action: Action): State { } case ACTION.SWITCH_TO_ADVANCED_EDITOR: - const jobConfig = getJobConfigFromFormState(state.form); + let { jobConfig } = state; + const isJobConfigEmpty = isEmpty(state.jobConfig); + if (isJobConfigEmpty) { + jobConfig = getJobConfigFromFormState(state.form); + } return validateAdvancedEditor({ ...state, advancedEditorRawString: JSON.stringify(jobConfig, null, 2), @@ -450,6 +457,12 @@ export function reducer(state: State, action: Action): State { ...state, estimatedModelMemoryLimit: action.value, }; + + case ACTION.SET_JOB_CLONE: + return { + ...state, + cloneJob: action.cloneJob, + }; } return state; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts index 170700d35e651..515e0e42bd873 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts @@ -7,9 +7,16 @@ import { EuiComboBoxOptionOption } from '@elastic/eui'; import { DeepPartial } from '../../../../../../../common/types/common'; import { checkPermission } from '../../../../../privilege/check_privilege'; -import { mlNodesAvailable } from '../../../../../ml_nodes_check/check_ml_nodes'; +import { mlNodesAvailable } from '../../../../../ml_nodes_check'; -import { DataFrameAnalyticsId, DataFrameAnalyticsConfig } from '../../../../common'; +import { + isClassificationAnalysis, + isRegressionAnalysis, + DataFrameAnalyticsId, + DataFrameAnalyticsConfig, + ANALYSIS_CONFIG_TYPE, +} from '../../../../common/analytics'; +import { CloneDataFrameAnalyticsConfig } from '../../components/analytics_list/action_clone'; export enum DEFAULT_MODEL_MEMORY_LIMIT { regression = '100mb', @@ -21,7 +28,7 @@ export enum DEFAULT_MODEL_MEMORY_LIMIT { export type EsIndexName = string; export type DependentVariable = string; export type IndexPatternTitle = string; -export type AnalyticsJobType = JOB_TYPES | undefined; +export type AnalyticsJobType = ANALYSIS_CONFIG_TYPE | undefined; type IndexPatternId = string; export type SourceIndexMap = Record< IndexPatternTitle, @@ -33,12 +40,6 @@ export interface FormMessage { message: string; } -export enum JOB_TYPES { - OUTLIER_DETECTION = 'outlier_detection', - REGRESSION = 'regression', - CLASSIFICATION = 'classification', -} - export interface State { advancedEditorMessages: FormMessage[]; advancedEditorRawString: string; @@ -90,6 +91,7 @@ export interface State { jobIds: DataFrameAnalyticsId[]; requestMessages: FormMessage[]; estimatedModelMemoryLimit: string; + cloneJob?: DataFrameAnalyticsConfig; } export const getInitialState = (): State => ({ @@ -174,8 +176,8 @@ export const getJobConfigFromFormState = ( }; if ( - formState.jobType === JOB_TYPES.REGRESSION || - formState.jobType === JOB_TYPES.CLASSIFICATION + formState.jobType === ANALYSIS_CONFIG_TYPE.REGRESSION || + formState.jobType === ANALYSIS_CONFIG_TYPE.CLASSIFICATION ) { jobConfig.analysis = { [formState.jobType]: { @@ -187,3 +189,35 @@ export const getJobConfigFromFormState = ( return jobConfig; }; + +/** + * Extracts form state for a job clone from the analytics job configuration. + * For cloning we keep job id and destination index empty. + */ +export function getCloneFormStateFromJobConfig( + analyticsJobConfig: CloneDataFrameAnalyticsConfig +): Partial { + const jobType = Object.keys(analyticsJobConfig.analysis)[0] as ANALYSIS_CONFIG_TYPE; + + const resultState: Partial = { + jobType, + description: analyticsJobConfig.description ?? '', + sourceIndex: Array.isArray(analyticsJobConfig.source.index) + ? analyticsJobConfig.source.index.join(',') + : analyticsJobConfig.source.index, + modelMemoryLimit: analyticsJobConfig.model_memory_limit, + excludes: analyticsJobConfig.analyzed_fields.excludes, + }; + + if ( + isRegressionAnalysis(analyticsJobConfig.analysis) || + isClassificationAnalysis(analyticsJobConfig.analysis) + ) { + const analysisConfig = analyticsJobConfig.analysis[jobType]; + + resultState.dependentVariable = analysisConfig.dependent_variable; + resultState.trainingPercent = analysisConfig.training_percent; + } + + return resultState; +} diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts index 9a243e1b0316d..74161d7c48c24 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts @@ -17,6 +17,10 @@ import { DataFrameAnalyticsId, DataFrameAnalyticsConfig, } from '../../../../common'; +import { + extractCloningConfig, + isAdvancedConfig, +} from '../../components/analytics_list/action_clone'; import { ActionDispatchers, ACTION } from './actions'; import { reducer } from './reducer'; @@ -27,6 +31,7 @@ import { FormMessage, State, SourceIndexMap, + getCloneFormStateFromJobConfig, } from './state'; export interface CreateAnalyticsFormProps { @@ -187,9 +192,7 @@ export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => { } }; - const openModal = async () => { - resetForm(); - + const prepareFormValidation = async () => { // re-fetch existing analytics job IDs and indices for form validation try { setJobIds( @@ -248,7 +251,11 @@ export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => { ), }); } + }; + const openModal = async () => { + resetForm(); + await prepareFormValidation(); dispatch({ type: ACTION.OPEN_MODAL }); }; @@ -301,6 +308,23 @@ export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => { dispatch({ type: ACTION.SET_ESTIMATED_MODEL_MEMORY_LIMIT, value }); }; + const setJobClone = async (cloneJob: DataFrameAnalyticsConfig) => { + resetForm(); + await prepareFormValidation(); + + const config = extractCloningConfig(cloneJob); + if (isAdvancedConfig(config)) { + setJobConfig(config); + switchToAdvancedEditor(); + } else { + setFormState(getCloneFormStateFromJobConfig(config)); + setEstimatedModelMemoryLimit(config.model_memory_limit); + } + + dispatch({ type: ACTION.SET_JOB_CLONE, cloneJob }); + dispatch({ type: ACTION.OPEN_MODAL }); + }; + const actions: ActionDispatchers = { closeModal, createAnalyticsJob, @@ -313,6 +337,7 @@ export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => { startAnalyticsJob, switchToAdvancedEditor, setEstimatedModelMemoryLimit, + setJobClone, }; return { state, actions }; diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/datafeed_preview_tab.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/datafeed_preview_tab.js index 7a98ec5e5ce4a..216c416f30a6b 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/datafeed_preview_tab.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/datafeed_preview_tab.js @@ -57,7 +57,8 @@ export class DatafeedPreviewPane extends Component { } componentDidMount() { - const canPreviewDatafeed = checkPermission('canPreviewDatafeed'); + const canPreviewDatafeed = + checkPermission('canPreviewDatafeed') && this.props.job.datafeed_config !== undefined; this.setState({ canPreviewDatafeed }); updateDatafeedPreview(this.props.job, canPreviewDatafeed) @@ -87,7 +88,7 @@ function updateDatafeedPreview(job, canPreviewDatafeed) { return new Promise((resolve, reject) => { if (canPreviewDatafeed) { mlJobService - .getDatafeedPreview(job.job_id) + .getDatafeedPreview(job.datafeed_config.datafeed_id) .then(resp => { if (Array.isArray(resp)) { resolve(JSON.stringify(resp.slice(0, ML_DATA_PREVIEW_COUNT), null, 2)); diff --git a/x-pack/plugins/ml/public/application/services/job_service.js b/x-pack/plugins/ml/public/application/services/job_service.js index fe3663d6a3ddb..f092e85bef5ce 100644 --- a/x-pack/plugins/ml/public/application/services/job_service.js +++ b/x-pack/plugins/ml/public/application/services/job_service.js @@ -747,8 +747,7 @@ class JobService { return datafeedId; } - getDatafeedPreview(jobId) { - const datafeedId = this.getDatafeedId(jobId); + getDatafeedPreview(datafeedId) { return ml.datafeedPreview({ datafeedId }); } diff --git a/x-pack/plugins/searchprofiler/public/application/components/profile_tree/profile_tree.tsx b/x-pack/plugins/searchprofiler/public/application/components/profile_tree/profile_tree.tsx index 87a73cdefba31..1dec8f0161c52 100644 --- a/x-pack/plugins/searchprofiler/public/application/components/profile_tree/profile_tree.tsx +++ b/x-pack/plugins/searchprofiler/public/application/components/profile_tree/profile_tree.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { memo } from 'react'; +import React, { memo, Fragment } from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import { IndexDetails } from './index_details'; @@ -53,13 +53,11 @@ export const ProfileTree = memo(({ data, target, onHighlight }: Props) => { - {index.shards.map(shard => ( - + {index.shards.map((shard, idx) => ( + + + {idx < index.shards.length - 1 ? : undefined} + ))} diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index fb0eb6e4bf804..3763020a0b692 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -833,7 +833,6 @@ "kbn.advancedSettings.defaultIndexTitle": "デフォルトのインデックス", "kbn.advancedSettings.defaultRoute.defaultRouteText": "この設定は、Kibana 起動時のデフォルトのルートを設定します。この設定で、Kibana 起動時のランディングページを変更できます。ルートはスラッシュ (\"/\") で始まる必要があります。", "kbn.advancedSettings.defaultRoute.defaultRouteTitle": "デフォルトのルート", - "kbn.advancedSettings.defaultRoute.defaultRouteValidationMessage": "ルートはスラッシュ (\"/\") で始まる必要があります。", "kbn.advancedSettings.disableAnimationsText": "Kibana UI の不要なアニメーションをオフにします。変更を適用するにはページを更新してください。", "kbn.advancedSettings.disableAnimationsTitle": "アニメーションを無効にする", "kbn.advancedSettings.discover.aggsTermsSizeText": "「可視化」ボタンをクリックした際に、フィールドドロップダウンやディスカバリサイドバーに可視化される用語の数を設定します。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 0a9c82afaec1d..0477470a4b8a1 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -833,7 +833,6 @@ "kbn.advancedSettings.defaultIndexTitle": "默认索引", "kbn.advancedSettings.defaultRoute.defaultRouteText": "此设置指定打开 Kibana 时的默认路由。您可以使用此设置修改打开 Kibana 时的登陆页面。路由必须以正斜杠(“/”)开头。", "kbn.advancedSettings.defaultRoute.defaultRouteTitle": "默认路由", - "kbn.advancedSettings.defaultRoute.defaultRouteValidationMessage": "路由必须以正斜杠(“/”)开头", "kbn.advancedSettings.disableAnimationsText": "在 Kibana UI 中关闭所有没有必要的动画。刷新页面以应用更改。", "kbn.advancedSettings.disableAnimationsTitle": "禁用动画", "kbn.advancedSettings.discover.aggsTermsSizeText": "确定在单击“可视化”按钮时将在发现侧边栏的字段下拉列表中可视化多少个词。", diff --git a/x-pack/plugins/uptime/server/graphql/monitor_states/resolvers.ts b/x-pack/plugins/uptime/server/graphql/monitor_states/resolvers.ts index 6ac42f7717259..1560b78b3c050 100644 --- a/x-pack/plugins/uptime/server/graphql/monitor_states/resolvers.ts +++ b/x-pack/plugins/uptime/server/graphql/monitor_states/resolvers.ts @@ -10,9 +10,8 @@ import { UMResolver } from '../../../../../legacy/plugins/uptime/common/graphql/ import { GetMonitorStatesQueryArgs, MonitorSummaryResult, - StatesIndexStatus, } from '../../../../../legacy/plugins/uptime/common/graphql/types'; -import { CONTEXT_DEFAULTS } from '../../../../../legacy/plugins/uptime/common/constants/context_defaults'; +import { CONTEXT_DEFAULTS } from '../../../../../legacy/plugins/uptime/common/constants'; export type UMGetMonitorStatesResolver = UMResolver< MonitorSummaryResult | Promise, @@ -21,19 +20,11 @@ export type UMGetMonitorStatesResolver = UMResolver< UMContext >; -export type UMStatesIndexExistsResolver = UMResolver< - StatesIndexStatus | Promise, - any, - {}, - UMContext ->; - export const createMonitorStatesResolvers: CreateUMGraphQLResolvers = ( libs: UMServerLibs ): { Query: { getMonitorStates: UMGetMonitorStatesResolver; - getStatesIndexStatus: UMStatesIndexExistsResolver; }; } => { return { @@ -64,7 +55,7 @@ export const createMonitorStatesResolvers: CreateUMGraphQLResolvers = ( }), ]); - const totalSummaryCount = indexStatus?.docCount ?? { count: undefined }; + const totalSummaryCount = indexStatus?.docCount ?? 0; return { summaries, @@ -73,9 +64,6 @@ export const createMonitorStatesResolvers: CreateUMGraphQLResolvers = ( totalSummaryCount, }; }, - async getStatesIndexStatus(_resolver, {}, { APICaller }): Promise { - return await libs.requests.getIndexStatus({ callES: APICaller }); - }, }, }; }; diff --git a/x-pack/plugins/uptime/server/graphql/monitor_states/schema.gql.ts b/x-pack/plugins/uptime/server/graphql/monitor_states/schema.gql.ts index 198f97eab9652..d088aed951204 100644 --- a/x-pack/plugins/uptime/server/graphql/monitor_states/schema.gql.ts +++ b/x-pack/plugins/uptime/server/graphql/monitor_states/schema.gql.ts @@ -156,15 +156,7 @@ export const monitorStatesSchema = gql` "The objects representing the state of a series of heartbeat monitors." summaries: [MonitorSummary!] "The number of summaries." - totalSummaryCount: DocCount! - } - - "Represents the current status of the uptime index." - type StatesIndexStatus { - "Flag denoting whether the index exists." - indexExists: Boolean! - "The number of documents in the index." - docCount: DocCount + totalSummaryCount: Int! } enum CursorDirection { @@ -186,8 +178,5 @@ export const monitorStatesSchema = gql` filters: String statusFilter: String ): MonitorSummaryResult - - "Fetches details about the uptime index." - getStatesIndexStatus: StatesIndexStatus! } `; diff --git a/x-pack/plugins/uptime/server/lib/requests/get_index_status.ts b/x-pack/plugins/uptime/server/lib/requests/get_index_status.ts index 95aa7eeef88e1..d8a05c08b1417 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_index_status.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_index_status.ts @@ -5,8 +5,8 @@ */ import { UMElasticsearchQueryFn } from '../adapters'; -import { StatesIndexStatus } from '../../../../../legacy/plugins/uptime/common/graphql/types'; import { INDEX_NAMES } from '../../../../../legacy/plugins/uptime/common/constants'; +import { StatesIndexStatus } from '../../../../../legacy/plugins/uptime/common/runtime_types'; export const getIndexStatus: UMElasticsearchQueryFn<{}, StatesIndexStatus> = async ({ callES }) => { const { @@ -15,8 +15,6 @@ export const getIndexStatus: UMElasticsearchQueryFn<{}, StatesIndexStatus> = asy } = await callES('count', { index: INDEX_NAMES.HEARTBEAT }); return { indexExists: total > 0, - docCount: { - count, - }, + docCount: count, }; }; diff --git a/x-pack/plugins/uptime/server/lib/requests/uptime_requests.ts b/x-pack/plugins/uptime/server/lib/requests/uptime_requests.ts index 6fd77afe711d4..7f192994bd075 100644 --- a/x-pack/plugins/uptime/server/lib/requests/uptime_requests.ts +++ b/x-pack/plugins/uptime/server/lib/requests/uptime_requests.ts @@ -5,11 +5,7 @@ */ import { UMElasticsearchQueryFn } from '../adapters'; -import { - Ping, - PingResults, - StatesIndexStatus, -} from '../../../../../legacy/plugins/uptime/common/graphql/types'; +import { Ping, PingResults } from '../../../../../legacy/plugins/uptime/common/graphql/types'; import { GetFilterBarParams, GetLatestMonitorParams, @@ -26,6 +22,7 @@ import { MonitorDetails, MonitorLocations, Snapshot, + StatesIndexStatus, } from '../../../../../legacy/plugins/uptime/common/runtime_types'; import { GetMonitorStatesResult } from './get_monitor_states'; import { GetSnapshotCountParams } from './get_snapshot_counts'; diff --git a/x-pack/plugins/uptime/server/rest_api/index.ts b/x-pack/plugins/uptime/server/rest_api/index.ts index 69981b7860d59..b0cc38ebfb4b6 100644 --- a/x-pack/plugins/uptime/server/rest_api/index.ts +++ b/x-pack/plugins/uptime/server/rest_api/index.ts @@ -6,7 +6,6 @@ import { createGetOverviewFilters } from './overview_filters'; import { createGetPingsRoute } from './pings'; -import { createGetIndexPatternRoute } from './index_pattern'; import { createLogMonitorPageRoute, createLogOverviewPageRoute } from './telemetry'; import { createGetSnapshotCount } from './snapshot'; import { UMRestApiRouteFactory } from './types'; @@ -18,6 +17,7 @@ import { } from './monitors'; import { createGetPingHistogramRoute } from './pings/get_ping_histogram'; import { createGetMonitorDurationRoute } from './monitors/monitors_durations'; +import { createGetIndexPatternRoute, createGetIndexStatusRoute } from './index_state'; export * from './types'; export { createRouteWithAuth } from './create_route_with_auth'; @@ -27,6 +27,7 @@ export const restApiRoutes: UMRestApiRouteFactory[] = [ createGetOverviewFilters, createGetPingsRoute, createGetIndexPatternRoute, + createGetIndexStatusRoute, createGetMonitorRoute, createGetMonitorDetailsRoute, createGetMonitorLocationsRoute, diff --git a/x-pack/plugins/uptime/server/rest_api/index_pattern/get_index_pattern.ts b/x-pack/plugins/uptime/server/rest_api/index_state/get_index_pattern.ts similarity index 100% rename from x-pack/plugins/uptime/server/rest_api/index_pattern/get_index_pattern.ts rename to x-pack/plugins/uptime/server/rest_api/index_state/get_index_pattern.ts diff --git a/x-pack/plugins/uptime/server/rest_api/index_state/get_index_status.ts b/x-pack/plugins/uptime/server/rest_api/index_state/get_index_status.ts new file mode 100644 index 0000000000000..44799aa19c140 --- /dev/null +++ b/x-pack/plugins/uptime/server/rest_api/index_state/get_index_status.ts @@ -0,0 +1,29 @@ +/* + * 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 { UMServerLibs } from '../../lib/lib'; +import { UMRestApiRouteFactory } from '../types'; +import { REST_API_URLS } from '../../../../../legacy/plugins/uptime/common/constants/rest_api'; + +export const createGetIndexStatusRoute: UMRestApiRouteFactory = (libs: UMServerLibs) => ({ + method: 'GET', + path: REST_API_URLS.INDEX_STATUS, + validate: false, + options: { + tags: ['access:uptime'], + }, + handler: async ({ callES }, _context, _request, response): Promise => { + try { + return response.ok({ + body: { + ...(await libs.requests.getIndexStatus({ callES })), + }, + }); + } catch (e) { + return response.internalError({ body: { message: e.message } }); + } + }, +}); diff --git a/x-pack/plugins/uptime/server/rest_api/index_pattern/index.ts b/x-pack/plugins/uptime/server/rest_api/index_state/index.ts similarity index 82% rename from x-pack/plugins/uptime/server/rest_api/index_pattern/index.ts rename to x-pack/plugins/uptime/server/rest_api/index_state/index.ts index b35e2e7b65a29..ff44794bfe7d1 100644 --- a/x-pack/plugins/uptime/server/rest_api/index_pattern/index.ts +++ b/x-pack/plugins/uptime/server/rest_api/index_state/index.ts @@ -5,3 +5,4 @@ */ export { createGetIndexPatternRoute } from './get_index_pattern'; +export { createGetIndexStatusRoute } from './get_index_status'; diff --git a/x-pack/plugins/watcher/public/application/models/action/logging_action.js b/x-pack/plugins/watcher/public/application/models/action/logging_action.js index bef094b57cc8e..1590ee62e68b4 100644 --- a/x-pack/plugins/watcher/public/application/models/action/logging_action.js +++ b/x-pack/plugins/watcher/public/application/models/action/logging_action.js @@ -37,7 +37,18 @@ export class LoggingAction extends BaseAction { get upstreamJson() { const result = super.upstreamJson; - const text = !!this.text.trim() ? this.text : undefined; + let text; + + if (typeof this.text === 'string') { + // If this.text is a non-empty string, we can send it to the API. + if (!!this.text.trim()) { + text = this.text; + } + } else { + // If the user incorrectly defined this.text, e.g. as an object in a JSON watch, let the API + // deal with it. + text = this.text; + } Object.assign(result, { text, diff --git a/x-pack/test/api_integration/apis/uptime/feature_controls.ts b/x-pack/test/api_integration/apis/uptime/feature_controls.ts index adbfacb014e9f..15666acab2335 100644 --- a/x-pack/test/api_integration/apis/uptime/feature_controls.ts +++ b/x-pack/test/api_integration/apis/uptime/feature_controls.ts @@ -5,9 +5,9 @@ */ import expect from '@kbn/expect'; -import { docCountQueryString } from '../../../../legacy/plugins/uptime/public/queries'; import { FtrProviderContext } from '../../ftr_provider_context'; import { PINGS_DATE_RANGE_END, PINGS_DATE_RANGE_START } from './constants'; +import { REST_API_URLS } from '../../../../legacy/plugins/uptime/common/constants'; export default function featureControlsTests({ getService }: FtrProviderContext) { const supertest = getService('supertestWithoutAuth'); @@ -26,22 +26,13 @@ export default function featureControlsTests({ getService }: FtrProviderContext) expect(result.response).to.have.property('statusCode', 200); }; - const executeGraphQLQuery = async (username: string, password: string, spaceId?: string) => { + const executeRESTAPIQuery = async (username: string, password: string, spaceId?: string) => { const basePath = spaceId ? `/s/${spaceId}` : ''; - const getDocCountQuery = { - operationName: null, - query: docCountQueryString, - variables: { - dateRangeStart: '2019-01-28T17:40:08.078Z', - dateRangeEnd: '2019-01-28T19:00:16.078Z', - }, - }; return await supertest - .post(`${basePath}/api/uptime/graphql`) + .get(basePath + REST_API_URLS.INDEX_STATUS) .auth(username, password) .set('kbn-xsrf', 'foo') - .send({ ...getDocCountQuery }) .then((response: any) => ({ error: undefined, response })) .catch((error: any) => ({ error, response: undefined })); }; @@ -82,7 +73,7 @@ export default function featureControlsTests({ getService }: FtrProviderContext) full_name: 'a kibana user', }); - const graphQLResult = await executeGraphQLQuery(username, password); + const graphQLResult = await executeRESTAPIQuery(username, password); expect404(graphQLResult); const pingsResult = await executePingsRequest(username, password); @@ -121,7 +112,7 @@ export default function featureControlsTests({ getService }: FtrProviderContext) full_name: 'a kibana user', }); - const graphQLResult = await executeGraphQLQuery(username, password); + const graphQLResult = await executeRESTAPIQuery(username, password); expectResponse(graphQLResult); const pingsResult = await executePingsRequest(username, password); @@ -163,7 +154,7 @@ export default function featureControlsTests({ getService }: FtrProviderContext) full_name: 'a kibana user', }); - const graphQLResult = await executeGraphQLQuery(username, password); + const graphQLResult = await executeRESTAPIQuery(username, password); expect404(graphQLResult); const pingsResult = await executePingsRequest(username, password); @@ -232,7 +223,7 @@ export default function featureControlsTests({ getService }: FtrProviderContext) }); it('user_1 can access APIs in space_1', async () => { - const graphQLResult = await executeGraphQLQuery(username, password, space1Id); + const graphQLResult = await executeRESTAPIQuery(username, password, space1Id); expectResponse(graphQLResult); const pingsResult = await executePingsRequest(username, password, space1Id); @@ -240,7 +231,7 @@ export default function featureControlsTests({ getService }: FtrProviderContext) }); it(`user_1 can't access APIs in space_2`, async () => { - const graphQLResult = await executeGraphQLQuery(username, password); + const graphQLResult = await executeRESTAPIQuery(username, password); expect404(graphQLResult); const pingsResult = await executePingsRequest(username, password); diff --git a/x-pack/test/api_integration/apis/uptime/graphql/doc_count.js b/x-pack/test/api_integration/apis/uptime/graphql/doc_count.js deleted file mode 100644 index 1aa69faaab736..0000000000000 --- a/x-pack/test/api_integration/apis/uptime/graphql/doc_count.js +++ /dev/null @@ -1,36 +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 { docCountQueryString } from '../../../../../legacy/plugins/uptime/public/queries'; -import { expectFixtureEql } from './helpers/expect_fixture_eql'; - -export default function({ getService }) { - describe('docCount query', () => { - before('load heartbeat data', () => getService('esArchiver').load('uptime/full_heartbeat')); - after('unload heartbeat index', () => getService('esArchiver').unload('uptime/full_heartbeat')); - - const supertest = getService('supertest'); - - it(`will fetch the index's count`, async () => { - const getDocCountQuery = { - operationName: null, - query: docCountQueryString, - variables: { - dateRangeStart: '2019-01-28T17:40:08.078Z', - dateRangeEnd: '2025-01-28T19:00:16.078Z', - }, - }; - const { - body: { data }, - } = await supertest - .post('/api/uptime/graphql') - .set('kbn-xsrf', 'foo') - .send({ ...getDocCountQuery }); - - expectFixtureEql(data, 'doc_count'); - }); - }); -} diff --git a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/doc_count.json b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/doc_count.json deleted file mode 100644 index 4daf223a79a69..0000000000000 --- a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/doc_count.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "statesIndexStatus": { - "docCount": { - "count": 2000 - }, - "indexExists": true - } -} \ No newline at end of file diff --git a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states.json b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states.json index 59f5f95e7d840..05724f0716e8d 100644 --- a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states.json +++ b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states.json @@ -2,9 +2,7 @@ "monitorStates": { "prevPagePagination": null, "nextPagePagination": "{\"cursorDirection\":\"AFTER\",\"sortOrder\":\"ASC\",\"cursorKey\":{\"monitor_id\":\"0009-up\"}}", - "totalSummaryCount": { - "count": 2000 - }, + "totalSummaryCount": 2000, "summaries": [ { "monitor_id": "0000-intermittent", @@ -172,9 +170,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -362,9 +358,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -552,9 +546,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -742,9 +734,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -932,9 +922,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1122,9 +1110,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1312,9 +1298,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1502,9 +1486,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1692,9 +1674,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1882,9 +1862,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1908,4 +1886,4 @@ } ] } -} \ No newline at end of file +} diff --git a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_id_filtered.json b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_id_filtered.json index 9a1363f00578a..6e62787069f40 100644 --- a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_id_filtered.json +++ b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_id_filtered.json @@ -2,9 +2,7 @@ "monitorStates": { "prevPagePagination": null, "nextPagePagination": null, - "totalSummaryCount": { - "count": 2000 - }, + "totalSummaryCount": 2000, "summaries": [ { "monitor_id": "0002-up", @@ -172,9 +170,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -198,4 +194,4 @@ } ] } -} \ No newline at end of file +} diff --git a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_1.json b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_1.json index 59f5f95e7d840..05724f0716e8d 100644 --- a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_1.json +++ b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_1.json @@ -2,9 +2,7 @@ "monitorStates": { "prevPagePagination": null, "nextPagePagination": "{\"cursorDirection\":\"AFTER\",\"sortOrder\":\"ASC\",\"cursorKey\":{\"monitor_id\":\"0009-up\"}}", - "totalSummaryCount": { - "count": 2000 - }, + "totalSummaryCount": 2000, "summaries": [ { "monitor_id": "0000-intermittent", @@ -172,9 +170,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -362,9 +358,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -552,9 +546,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -742,9 +734,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -932,9 +922,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1122,9 +1110,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1312,9 +1298,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1502,9 +1486,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1692,9 +1674,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1882,9 +1862,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1908,4 +1886,4 @@ } ] } -} \ No newline at end of file +} diff --git a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_10.json b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_10.json index 5c07b4daaf543..6cbe4ee3659a8 100644 --- a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_10.json +++ b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_10.json @@ -2,9 +2,7 @@ "monitorStates": { "prevPagePagination": "{\"cursorKey\":{\"monitor_id\":\"0090-intermittent\"},\"sortOrder\":\"ASC\",\"cursorDirection\":\"BEFORE\"}", "nextPagePagination": null, - "totalSummaryCount": { - "count": 2000 - }, + "totalSummaryCount": 2000, "summaries": [ { "monitor_id": "0090-intermittent", @@ -172,9 +170,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -362,9 +358,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -552,9 +546,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -742,9 +734,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -932,9 +922,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1122,9 +1110,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1312,9 +1298,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1502,9 +1486,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1692,9 +1674,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1882,9 +1862,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1908,4 +1886,4 @@ } ] } -} \ No newline at end of file +} diff --git a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_10_previous.json b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_10_previous.json index 71184093f4318..9a3f781735cb7 100644 --- a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_10_previous.json +++ b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_10_previous.json @@ -2,9 +2,7 @@ "monitorStates": { "prevPagePagination": "{\"cursorKey\":{\"monitor_id\":\"0080-down\"},\"sortOrder\":\"ASC\",\"cursorDirection\":\"BEFORE\"}", "nextPagePagination": "{\"cursorKey\":{\"monitor_id\":\"0089-up\"},\"sortOrder\":\"ASC\",\"cursorDirection\":\"AFTER\"}", - "totalSummaryCount": { - "count": 2000 - }, + "totalSummaryCount": 2000, "summaries": [ { "monitor_id": "0080-down", @@ -172,9 +170,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -362,9 +358,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -552,9 +546,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -742,9 +734,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -932,9 +922,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1122,9 +1110,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1312,9 +1298,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1502,9 +1486,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1692,9 +1674,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1882,9 +1862,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1908,4 +1886,4 @@ } ] } -} \ No newline at end of file +} diff --git a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_2.json b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_2.json index 3b15ea3c24eeb..4f4af9c2c6012 100644 --- a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_2.json +++ b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_2.json @@ -2,9 +2,7 @@ "monitorStates": { "prevPagePagination": "{\"cursorKey\":{\"monitor_id\":\"0010-down\"},\"sortOrder\":\"ASC\",\"cursorDirection\":\"BEFORE\"}", "nextPagePagination": "{\"cursorDirection\":\"AFTER\",\"sortOrder\":\"ASC\",\"cursorKey\":{\"monitor_id\":\"0019-up\"}}", - "totalSummaryCount": { - "count": 2000 - }, + "totalSummaryCount": 2000, "summaries": [ { "monitor_id": "0010-down", @@ -172,9 +170,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -362,9 +358,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -552,9 +546,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -742,9 +734,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -932,9 +922,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1122,9 +1110,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1312,9 +1298,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1502,9 +1486,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1692,9 +1674,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1882,9 +1862,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1908,4 +1886,4 @@ } ] } -} \ No newline at end of file +} diff --git a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_2_previous.json b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_2_previous.json index eb6512d2f75b3..fe48ad49d13ba 100644 --- a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_2_previous.json +++ b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_2_previous.json @@ -2,9 +2,7 @@ "monitorStates": { "prevPagePagination": null, "nextPagePagination": "{\"cursorKey\":{\"monitor_id\":\"0009-up\"},\"sortOrder\":\"ASC\",\"cursorDirection\":\"AFTER\"}", - "totalSummaryCount": { - "count": 2000 - }, + "totalSummaryCount": 2000, "summaries": [ { "monitor_id": "0000-intermittent", @@ -172,9 +170,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -362,9 +358,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -552,9 +546,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -742,9 +734,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -932,9 +922,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1122,9 +1110,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1312,9 +1298,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1502,9 +1486,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1692,9 +1674,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1882,9 +1862,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1908,4 +1886,4 @@ } ] } -} \ No newline at end of file +} diff --git a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_3.json b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_3.json index aee4fa6946fc0..70ca665704a79 100644 --- a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_3.json +++ b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_3.json @@ -2,9 +2,7 @@ "monitorStates": { "prevPagePagination": "{\"cursorKey\":{\"monitor_id\":\"0020-down\"},\"sortOrder\":\"ASC\",\"cursorDirection\":\"BEFORE\"}", "nextPagePagination": "{\"cursorDirection\":\"AFTER\",\"sortOrder\":\"ASC\",\"cursorKey\":{\"monitor_id\":\"0029-up\"}}", - "totalSummaryCount": { - "count": 2000 - }, + "totalSummaryCount": 2000, "summaries": [ { "monitor_id": "0020-down", @@ -172,9 +170,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -362,9 +358,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -552,9 +546,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -742,9 +734,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -932,9 +922,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1122,9 +1110,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1312,9 +1298,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1502,9 +1486,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1692,9 +1674,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1882,9 +1862,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1908,4 +1886,4 @@ } ] } -} \ No newline at end of file +} diff --git a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_3_previous.json b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_3_previous.json index 03164f794a4d5..3f09c951ec2fa 100644 --- a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_3_previous.json +++ b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_3_previous.json @@ -2,9 +2,7 @@ "monitorStates": { "prevPagePagination": "{\"cursorKey\":{\"monitor_id\":\"0010-down\"},\"sortOrder\":\"ASC\",\"cursorDirection\":\"BEFORE\"}", "nextPagePagination": "{\"cursorKey\":{\"monitor_id\":\"0019-up\"},\"sortOrder\":\"ASC\",\"cursorDirection\":\"AFTER\"}", - "totalSummaryCount": { - "count": 2000 - }, + "totalSummaryCount": 2000, "summaries": [ { "monitor_id": "0010-down", @@ -172,9 +170,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -362,9 +358,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -552,9 +546,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -742,9 +734,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -932,9 +922,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1122,9 +1110,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1312,9 +1298,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1502,9 +1486,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1692,9 +1674,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1882,9 +1862,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1908,4 +1886,4 @@ } ] } -} \ No newline at end of file +} diff --git a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_4.json b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_4.json index 488fdab14f1e2..cdc0f32c9765e 100644 --- a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_4.json +++ b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_4.json @@ -2,9 +2,7 @@ "monitorStates": { "prevPagePagination": "{\"cursorKey\":{\"monitor_id\":\"0030-intermittent\"},\"sortOrder\":\"ASC\",\"cursorDirection\":\"BEFORE\"}", "nextPagePagination": "{\"cursorDirection\":\"AFTER\",\"sortOrder\":\"ASC\",\"cursorKey\":{\"monitor_id\":\"0039-up\"}}", - "totalSummaryCount": { - "count": 2000 - }, + "totalSummaryCount": 2000, "summaries": [ { "monitor_id": "0030-intermittent", @@ -172,9 +170,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -362,9 +358,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -552,9 +546,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -742,9 +734,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -932,9 +922,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1122,9 +1110,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1312,9 +1298,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1502,9 +1486,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1692,9 +1674,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1882,9 +1862,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1908,4 +1886,4 @@ } ] } -} \ No newline at end of file +} diff --git a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_4_previous.json b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_4_previous.json index 79ce05d86f533..9f6d004380c16 100644 --- a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_4_previous.json +++ b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_4_previous.json @@ -2,9 +2,7 @@ "monitorStates": { "prevPagePagination": "{\"cursorKey\":{\"monitor_id\":\"0020-down\"},\"sortOrder\":\"ASC\",\"cursorDirection\":\"BEFORE\"}", "nextPagePagination": "{\"cursorKey\":{\"monitor_id\":\"0029-up\"},\"sortOrder\":\"ASC\",\"cursorDirection\":\"AFTER\"}", - "totalSummaryCount": { - "count": 2000 - }, + "totalSummaryCount": 2000, "summaries": [ { "monitor_id": "0020-down", @@ -172,9 +170,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -362,9 +358,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -552,9 +546,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -742,9 +734,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -932,9 +922,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1122,9 +1110,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1312,9 +1298,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1502,9 +1486,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1692,9 +1674,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1882,9 +1862,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1908,4 +1886,4 @@ } ] } -} \ No newline at end of file +} diff --git a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_5.json b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_5.json index ef62cdd86c2a9..dedddb2a78ade 100644 --- a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_5.json +++ b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_5.json @@ -2,9 +2,7 @@ "monitorStates": { "prevPagePagination": "{\"cursorKey\":{\"monitor_id\":\"0040-down\"},\"sortOrder\":\"ASC\",\"cursorDirection\":\"BEFORE\"}", "nextPagePagination": "{\"cursorDirection\":\"AFTER\",\"sortOrder\":\"ASC\",\"cursorKey\":{\"monitor_id\":\"0049-up\"}}", - "totalSummaryCount": { - "count": 2000 - }, + "totalSummaryCount": 2000, "summaries": [ { "monitor_id": "0040-down", @@ -172,9 +170,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -362,9 +358,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -552,9 +546,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -742,9 +734,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -932,9 +922,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1122,9 +1110,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1312,9 +1298,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1502,9 +1486,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1692,9 +1674,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1882,9 +1862,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1908,4 +1886,4 @@ } ] } -} \ No newline at end of file +} diff --git a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_5_previous.json b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_5_previous.json index 34e8269cb95d9..fabcf70404952 100644 --- a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_5_previous.json +++ b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_5_previous.json @@ -2,9 +2,7 @@ "monitorStates": { "prevPagePagination": "{\"cursorKey\":{\"monitor_id\":\"0030-intermittent\"},\"sortOrder\":\"ASC\",\"cursorDirection\":\"BEFORE\"}", "nextPagePagination": "{\"cursorKey\":{\"monitor_id\":\"0039-up\"},\"sortOrder\":\"ASC\",\"cursorDirection\":\"AFTER\"}", - "totalSummaryCount": { - "count": 2000 - }, + "totalSummaryCount": 2000, "summaries": [ { "monitor_id": "0030-intermittent", @@ -172,9 +170,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -362,9 +358,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -552,9 +546,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -742,9 +734,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -932,9 +922,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1122,9 +1110,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1312,9 +1298,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1502,9 +1486,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1692,9 +1674,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1882,9 +1862,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1908,4 +1886,4 @@ } ] } -} \ No newline at end of file +} diff --git a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_6.json b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_6.json index d733467c3f9b6..943cc68249dc1 100644 --- a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_6.json +++ b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_6.json @@ -2,9 +2,7 @@ "monitorStates": { "prevPagePagination": "{\"cursorKey\":{\"monitor_id\":\"0050-down\"},\"sortOrder\":\"ASC\",\"cursorDirection\":\"BEFORE\"}", "nextPagePagination": "{\"cursorDirection\":\"AFTER\",\"sortOrder\":\"ASC\",\"cursorKey\":{\"monitor_id\":\"0059-up\"}}", - "totalSummaryCount": { - "count": 2000 - }, + "totalSummaryCount": 2000, "summaries": [ { "monitor_id": "0050-down", @@ -172,9 +170,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -362,9 +358,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -552,9 +546,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -742,9 +734,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -932,9 +922,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1122,9 +1110,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1312,9 +1298,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1502,9 +1486,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1692,9 +1674,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1882,9 +1862,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1908,4 +1886,4 @@ } ] } -} \ No newline at end of file +} diff --git a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_6_previous.json b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_6_previous.json index 12e27106bd533..564f58f59f373 100644 --- a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_6_previous.json +++ b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_6_previous.json @@ -2,9 +2,7 @@ "monitorStates": { "prevPagePagination": "{\"cursorKey\":{\"monitor_id\":\"0040-down\"},\"sortOrder\":\"ASC\",\"cursorDirection\":\"BEFORE\"}", "nextPagePagination": "{\"cursorKey\":{\"monitor_id\":\"0049-up\"},\"sortOrder\":\"ASC\",\"cursorDirection\":\"AFTER\"}", - "totalSummaryCount": { - "count": 2000 - }, + "totalSummaryCount": 2000, "summaries": [ { "monitor_id": "0040-down", @@ -172,9 +170,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -362,9 +358,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -552,9 +546,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -742,9 +734,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -932,9 +922,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1122,9 +1110,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1312,9 +1298,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1502,9 +1486,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1692,9 +1674,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1882,9 +1862,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1908,4 +1886,4 @@ } ] } -} \ No newline at end of file +} diff --git a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_7.json b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_7.json index d0f2b820f8327..cb94273e91fd8 100644 --- a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_7.json +++ b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_7.json @@ -2,9 +2,7 @@ "monitorStates": { "prevPagePagination": "{\"cursorKey\":{\"monitor_id\":\"0060-intermittent\"},\"sortOrder\":\"ASC\",\"cursorDirection\":\"BEFORE\"}", "nextPagePagination": "{\"cursorDirection\":\"AFTER\",\"sortOrder\":\"ASC\",\"cursorKey\":{\"monitor_id\":\"0069-up\"}}", - "totalSummaryCount": { - "count": 2000 - }, + "totalSummaryCount": 2000, "summaries": [ { "monitor_id": "0060-intermittent", @@ -172,9 +170,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -362,9 +358,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -552,9 +546,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -742,9 +734,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -932,9 +922,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1122,9 +1110,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1312,9 +1298,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1502,9 +1486,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1692,9 +1674,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1882,9 +1862,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1908,4 +1886,4 @@ } ] } -} \ No newline at end of file +} diff --git a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_7_previous.json b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_7_previous.json index cf0c8641cc87b..7aac62bba84f7 100644 --- a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_7_previous.json +++ b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_7_previous.json @@ -2,9 +2,7 @@ "monitorStates": { "prevPagePagination": "{\"cursorKey\":{\"monitor_id\":\"0050-down\"},\"sortOrder\":\"ASC\",\"cursorDirection\":\"BEFORE\"}", "nextPagePagination": "{\"cursorKey\":{\"monitor_id\":\"0059-up\"},\"sortOrder\":\"ASC\",\"cursorDirection\":\"AFTER\"}", - "totalSummaryCount": { - "count": 2000 - }, + "totalSummaryCount": 2000, "summaries": [ { "monitor_id": "0050-down", @@ -172,9 +170,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -362,9 +358,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -552,9 +546,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -742,9 +734,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -932,9 +922,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1122,9 +1110,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1312,9 +1298,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1502,9 +1486,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1692,9 +1674,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1882,9 +1862,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1908,4 +1886,4 @@ } ] } -} \ No newline at end of file +} diff --git a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_8.json b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_8.json index 2801e94e034c7..08cbd0d878b44 100644 --- a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_8.json +++ b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_8.json @@ -2,9 +2,7 @@ "monitorStates": { "prevPagePagination": "{\"cursorKey\":{\"monitor_id\":\"0070-down\"},\"sortOrder\":\"ASC\",\"cursorDirection\":\"BEFORE\"}", "nextPagePagination": "{\"cursorDirection\":\"AFTER\",\"sortOrder\":\"ASC\",\"cursorKey\":{\"monitor_id\":\"0079-up\"}}", - "totalSummaryCount": { - "count": 2000 - }, + "totalSummaryCount": 2000, "summaries": [ { "monitor_id": "0070-down", @@ -172,9 +170,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -362,9 +358,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -552,9 +546,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -742,9 +734,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -932,9 +922,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1122,9 +1110,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1312,9 +1298,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1502,9 +1486,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1692,9 +1674,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1882,9 +1862,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1908,4 +1886,4 @@ } ] } -} \ No newline at end of file +} diff --git a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_8_previous.json b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_8_previous.json index 7fcbd4dd59659..8de639b705ee9 100644 --- a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_8_previous.json +++ b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_8_previous.json @@ -2,9 +2,7 @@ "monitorStates": { "prevPagePagination": "{\"cursorKey\":{\"monitor_id\":\"0060-intermittent\"},\"sortOrder\":\"ASC\",\"cursorDirection\":\"BEFORE\"}", "nextPagePagination": "{\"cursorKey\":{\"monitor_id\":\"0069-up\"},\"sortOrder\":\"ASC\",\"cursorDirection\":\"AFTER\"}", - "totalSummaryCount": { - "count": 2000 - }, + "totalSummaryCount": 2000, "summaries": [ { "monitor_id": "0060-intermittent", @@ -172,9 +170,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -362,9 +358,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -552,9 +546,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -742,9 +734,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -932,9 +922,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1122,9 +1110,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1312,9 +1298,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1502,9 +1486,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1692,9 +1674,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1882,9 +1862,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1908,4 +1886,4 @@ } ] } -} \ No newline at end of file +} diff --git a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_9.json b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_9.json index 0adb7ad0b0dba..c38f5c801a267 100644 --- a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_9.json +++ b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_9.json @@ -2,9 +2,7 @@ "monitorStates": { "prevPagePagination": "{\"cursorKey\":{\"monitor_id\":\"0080-down\"},\"sortOrder\":\"ASC\",\"cursorDirection\":\"BEFORE\"}", "nextPagePagination": "{\"cursorDirection\":\"AFTER\",\"sortOrder\":\"ASC\",\"cursorKey\":{\"monitor_id\":\"0089-up\"}}", - "totalSummaryCount": { - "count": 2000 - }, + "totalSummaryCount": 2000, "summaries": [ { "monitor_id": "0080-down", @@ -172,9 +170,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -362,9 +358,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -552,9 +546,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -742,9 +734,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -932,9 +922,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1122,9 +1110,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1312,9 +1298,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1502,9 +1486,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1692,9 +1674,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1882,9 +1862,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1908,4 +1886,4 @@ } ] } -} \ No newline at end of file +} diff --git a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_9_previous.json b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_9_previous.json index a796be38bd0d9..5c2ec8512e320 100644 --- a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_9_previous.json +++ b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_states_page_9_previous.json @@ -2,9 +2,7 @@ "monitorStates": { "prevPagePagination": "{\"cursorKey\":{\"monitor_id\":\"0070-down\"},\"sortOrder\":\"ASC\",\"cursorDirection\":\"BEFORE\"}", "nextPagePagination": "{\"cursorKey\":{\"monitor_id\":\"0079-up\"},\"sortOrder\":\"ASC\",\"cursorDirection\":\"AFTER\"}", - "totalSummaryCount": { - "count": 2000 - }, + "totalSummaryCount": 2000, "summaries": [ { "monitor_id": "0070-down", @@ -172,9 +170,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -362,9 +358,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -552,9 +546,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -742,9 +734,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -932,9 +922,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1122,9 +1110,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1312,9 +1298,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1502,9 +1486,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1692,9 +1674,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1882,9 +1862,7 @@ "geo": null, "observer": { "geo": { - "name": [ - "mpls" - ], + "name": ["mpls"], "location": null } }, @@ -1908,4 +1886,4 @@ } ] } -} \ No newline at end of file +} diff --git a/x-pack/test/api_integration/apis/uptime/graphql/index.js b/x-pack/test/api_integration/apis/uptime/graphql/index.js index c2fdc57edede3..ee22974d47170 100644 --- a/x-pack/test/api_integration/apis/uptime/graphql/index.js +++ b/x-pack/test/api_integration/apis/uptime/graphql/index.js @@ -10,7 +10,6 @@ export default function({ loadTestFile }) { // the uptime app and runs it against the live HTTP server, // verifying the pre-loaded documents are returned in a way that // matches the snapshots contained in './fixtures' - loadTestFile(require.resolve('./doc_count')); loadTestFile(require.resolve('./monitor_states')); loadTestFile(require.resolve('./ping_list')); }); diff --git a/x-pack/test/api_integration/apis/uptime/rest/doc_count.ts b/x-pack/test/api_integration/apis/uptime/rest/doc_count.ts new file mode 100644 index 0000000000000..1f5322f581b39 --- /dev/null +++ b/x-pack/test/api_integration/apis/uptime/rest/doc_count.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { FtrProviderContext } from '../../../ftr_provider_context'; +import { expectFixtureEql } from '../graphql/helpers/expect_fixture_eql'; +import { REST_API_URLS } from '../../../../../legacy/plugins/uptime/common/constants'; + +export default function({ getService }: FtrProviderContext) { + describe('docCount query', () => { + const supertest = getService('supertest'); + + it(`will fetch the index's count`, async () => { + const apiResponse = await supertest.get(REST_API_URLS.INDEX_STATUS); + const data = apiResponse.body; + expectFixtureEql(data, 'doc_count'); + }); + }); +} diff --git a/x-pack/test/api_integration/apis/uptime/rest/fixtures/doc_count.json b/x-pack/test/api_integration/apis/uptime/rest/fixtures/doc_count.json new file mode 100644 index 0000000000000..41b9af392dded --- /dev/null +++ b/x-pack/test/api_integration/apis/uptime/rest/fixtures/doc_count.json @@ -0,0 +1,4 @@ +{ + "docCount": 2000, + "indexExists": true +} diff --git a/x-pack/test/api_integration/apis/uptime/rest/index.ts b/x-pack/test/api_integration/apis/uptime/rest/index.ts index 5e26cb9216f45..67b94f19c638f 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/index.ts +++ b/x-pack/test/api_integration/apis/uptime/rest/index.ts @@ -21,6 +21,7 @@ export default function({ getService, loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./selected_monitor')); loadTestFile(require.resolve('./ping_histogram')); loadTestFile(require.resolve('./monitor_duration')); + loadTestFile(require.resolve('./doc_count')); }); }); } diff --git a/x-pack/test/functional/apps/machine_learning/data_frame_analytics/cloning.ts b/x-pack/test/functional/apps/machine_learning/data_frame_analytics/cloning.ts new file mode 100644 index 0000000000000..512de861e673a --- /dev/null +++ b/x-pack/test/functional/apps/machine_learning/data_frame_analytics/cloning.ts @@ -0,0 +1,200 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import expect from '@kbn/expect'; +import { DeepPartial } from '../../../../../plugins/ml/common/types/common'; +import { DataFrameAnalyticsConfig } from '../../../../../plugins/ml/public/application/data_frame_analytics/common'; + +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function({ getService }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const ml = getService('ml'); + describe('jobs cloning supported by UI form', function() { + this.tags(['smoke']); + + const testDataList: Array<{ + suiteTitle: string; + archive: string; + job: DeepPartial; + }> = (() => { + const timestamp = Date.now(); + + return [ + { + suiteTitle: 'classification job supported by the form', + archive: 'ml/bm_classification', + job: { + id: `bm_1_${timestamp}`, + description: + "Classification job based on 'bank-marketing' dataset with dependentVariable 'y' and trainingPercent '20'", + source: { + index: ['bank-marketing*'], + query: { + match_all: {}, + }, + }, + dest: { + get index(): string { + return `user-bm_1_${timestamp}`; + }, + results_field: 'ml', + }, + analysis: { + classification: { + dependent_variable: 'y', + training_percent: 20, + }, + }, + analyzed_fields: { + includes: [], + excludes: [], + }, + model_memory_limit: '350mb', + allow_lazy_start: false, + }, + }, + { + suiteTitle: 'outlier detection job supported by the form', + archive: 'ml/ihp_outlier', + job: { + id: `ihp_1_${timestamp}`, + description: 'This is the job description', + source: { + index: ['ihp_outlier'], + query: { + match_all: {}, + }, + }, + dest: { + get index(): string { + return `user-ihp_1_${timestamp}`; + }, + results_field: 'ml', + }, + analysis: { + outlier_detection: {}, + }, + analyzed_fields: { + includes: [], + excludes: [], + }, + model_memory_limit: '55mb', + }, + }, + { + suiteTitle: 'regression job supported by the form', + archive: 'ml/egs_regression', + job: { + id: `egs_1_${timestamp}`, + description: 'This is the job description', + source: { + index: ['egs_regression'], + query: { + match_all: {}, + }, + }, + dest: { + get index(): string { + return `user-egs_1_${timestamp}`; + }, + results_field: 'ml', + }, + analysis: { + regression: { + dependent_variable: 'stab', + training_percent: 20, + }, + }, + analyzed_fields: { + includes: [], + excludes: [], + }, + model_memory_limit: '105mb', + }, + }, + ]; + })(); + + before(async () => { + await ml.securityUI.loginAsMlPowerUser(); + }); + + after(async () => { + await ml.api.cleanMlIndices(); + }); + + for (const testData of testDataList) { + describe(`${testData.suiteTitle}`, function() { + const cloneJobId = `${testData.job.id}_clone`; + const cloneDestIndex = `${testData.job!.dest!.index}_clone`; + + before(async () => { + await esArchiver.load(testData.archive); + await ml.api.createDataFrameAnalyticsJob(testData.job as DataFrameAnalyticsConfig); + + await ml.navigation.navigateToMl(); + await ml.navigation.navigateToDataFrameAnalytics(); + await ml.dataFrameAnalyticsTable.waitForAnalyticsToLoad(); + await ml.dataFrameAnalyticsTable.filterWithSearchString(testData.job.id as string); + await ml.dataFrameAnalyticsTable.cloneJob(testData.job.id as string); + }); + + after(async () => { + await ml.api.deleteIndices(cloneDestIndex); + await ml.api.deleteIndices(testData.job.dest!.index as string); + await esArchiver.unload(testData.archive); + }); + + it('should open the flyout with a proper header', async () => { + expect(await ml.dataFrameAnalyticsCreation.getHeaderText()).to.be( + `Clone job from ${testData.job.id}` + ); + }); + + it('should have correct init form values', async () => { + await ml.dataFrameAnalyticsCreation.assertInitialCloneJobForm( + testData.job as DataFrameAnalyticsConfig + ); + }); + + it('should have disabled Create button on open', async () => { + expect(await ml.dataFrameAnalyticsCreation.isCreateButtonDisabled()).to.be(true); + }); + + it('should enable Create button on a valid form input', async () => { + await ml.dataFrameAnalyticsCreation.setJobId(cloneJobId); + await ml.dataFrameAnalyticsCreation.setDestIndex(cloneDestIndex); + expect(await ml.dataFrameAnalyticsCreation.isCreateButtonDisabled()).to.be(false); + }); + + it('should create a clone job', async () => { + await ml.dataFrameAnalyticsCreation.createAnalyticsJob(); + }); + + it('should start the clone analytics job', async () => { + await ml.dataFrameAnalyticsCreation.assertStartButtonExists(); + await ml.dataFrameAnalyticsCreation.startAnalyticsJob(); + }); + + it('should close the create job flyout', async () => { + await ml.dataFrameAnalyticsCreation.assertCloseButtonExists(); + await ml.dataFrameAnalyticsCreation.closeCreateAnalyticsJobFlyout(); + }); + + it('displays the created job in the analytics table', async () => { + await ml.dataFrameAnalyticsTable.refreshAnalyticsTable(); + await ml.dataFrameAnalyticsTable.filterWithSearchString(cloneJobId); + const rows = await ml.dataFrameAnalyticsTable.parseAnalyticsTable(); + const filteredRows = rows.filter(row => row.id === cloneJobId); + expect(filteredRows).to.have.length( + 1, + `Filtered analytics table should have 1 row for job id '${cloneJobId}' (got matching items '${filteredRows}')` + ); + }); + }); + } + }); +} diff --git a/x-pack/test/functional/apps/machine_learning/data_frame_analytics/index.ts b/x-pack/test/functional/apps/machine_learning/data_frame_analytics/index.ts index fda0c5d203f2e..fe94f4aea9220 100644 --- a/x-pack/test/functional/apps/machine_learning/data_frame_analytics/index.ts +++ b/x-pack/test/functional/apps/machine_learning/data_frame_analytics/index.ts @@ -12,5 +12,6 @@ export default function({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./outlier_detection_creation')); loadTestFile(require.resolve('./regression_creation')); loadTestFile(require.resolve('./classification_creation')); + loadTestFile(require.resolve('./cloning')); }); } diff --git a/x-pack/test/functional/apps/machine_learning/feature_controls/ml_security.ts b/x-pack/test/functional/apps/machine_learning/feature_controls/ml_security.ts index 405e9575f4222..f3731f46a5bce 100644 --- a/x-pack/test/functional/apps/machine_learning/feature_controls/ml_security.ts +++ b/x-pack/test/functional/apps/machine_learning/feature_controls/ml_security.ts @@ -13,7 +13,6 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { const PageObjects = getPageObjects(['common', 'security']); describe('security', function() { - this.tags(['james']); before(async () => { await esArchiver.load('empty_kibana'); diff --git a/x-pack/test/functional/apps/spaces/enter_space.ts b/x-pack/test/functional/apps/spaces/enter_space.ts index e0b1ec544d460..38220c15cb266 100644 --- a/x-pack/test/functional/apps/spaces/enter_space.ts +++ b/x-pack/test/functional/apps/spaces/enter_space.ts @@ -10,7 +10,6 @@ export default function enterSpaceFunctonalTests({ getPageObjects, }: FtrProviderContext) { const esArchiver = getService('esArchiver'); - const kibanaServer = getService('kibanaServer'); const PageObjects = getPageObjects(['security', 'spaceSelector']); describe('Enter Space', function() { @@ -25,8 +24,8 @@ export default function enterSpaceFunctonalTests({ await PageObjects.security.forceLogout(); }); - it('allows user to navigate to different spaces, respecting the configured default route', async () => { - const spaceId = 'another-space'; + it('falls back to the default home page when the configured default route is malformed', async () => { + const spaceId = 'default'; await PageObjects.security.login(null, null, { expectSpaceSelector: true, @@ -34,22 +33,11 @@ export default function enterSpaceFunctonalTests({ await PageObjects.spaceSelector.clickSpaceCard(spaceId); - await PageObjects.spaceSelector.expectRoute(spaceId, '/app/kibana/#/dashboard'); - - await PageObjects.spaceSelector.openSpacesNav(); - - // change spaces - - await PageObjects.spaceSelector.clickSpaceAvatar('default'); - - await PageObjects.spaceSelector.expectRoute('default', '/app/canvas'); + await PageObjects.spaceSelector.expectHomePage(spaceId); }); - it('falls back to the default home page when the configured default route is malformed', async () => { - await kibanaServer.uiSettings.replace({ defaultRoute: 'http://example.com/evil' }); - - // This test only works with the default space, as other spaces have an enforced relative url of `${serverBasePath}/s/space-id/${defaultRoute}` - const spaceId = 'default'; + it('allows user to navigate to different spaces, respecting the configured default route', async () => { + const spaceId = 'another-space'; await PageObjects.security.login(null, null, { expectSpaceSelector: true, @@ -57,7 +45,15 @@ export default function enterSpaceFunctonalTests({ await PageObjects.spaceSelector.clickSpaceCard(spaceId); - await PageObjects.spaceSelector.expectHomePage(spaceId); + await PageObjects.spaceSelector.expectRoute(spaceId, '/app/canvas'); + + await PageObjects.spaceSelector.openSpacesNav(); + + // change spaces + const newSpaceId = 'default'; + await PageObjects.spaceSelector.clickSpaceAvatar(newSpaceId); + + await PageObjects.spaceSelector.expectHomePage(newSpaceId); }); }); } diff --git a/x-pack/test/functional/es_archives/spaces/enter_space/data.json b/x-pack/test/functional/es_archives/spaces/enter_space/data.json index 462a2a1ee38fe..475fc14e96e6a 100644 --- a/x-pack/test/functional/es_archives/spaces/enter_space/data.json +++ b/x-pack/test/functional/es_archives/spaces/enter_space/data.json @@ -7,7 +7,7 @@ "config": { "buildNum": 8467, "dateFormat:tz": "UTC", - "defaultRoute": "/app/canvas" + "defaultRoute": "http://example.com/evil" }, "type": "config" } @@ -24,7 +24,7 @@ "config": { "buildNum": 8467, "dateFormat:tz": "UTC", - "defaultRoute": "/app/kibana/#dashboard" + "defaultRoute": "/app/canvas" }, "type": "config" } diff --git a/x-pack/test/functional/services/machine_learning/api.ts b/x-pack/test/functional/services/machine_learning/api.ts index 89c81a800e471..e305d23c1a124 100644 --- a/x-pack/test/functional/services/machine_learning/api.ts +++ b/x-pack/test/functional/services/machine_learning/api.ts @@ -5,6 +5,7 @@ */ import expect from '@kbn/expect'; import { ProvidedType } from '@kbn/test/types/ftr'; +import { DataFrameAnalyticsConfig } from '../../../../plugins/ml/public/application/data_frame_analytics/common'; import { FtrProviderContext } from '../../ftr_provider_context'; @@ -355,5 +356,26 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { await this.waitForDatafeedState(datafeedConfig.datafeed_id, DATAFEED_STATE.STOPPED); await this.waitForJobState(jobConfig.job_id, JOB_STATE.CLOSED); }, + + async getDataFrameAnalyticsJob(analyticsId: string) { + return await esSupertest.get(`/_ml/data_frame/analytics/${analyticsId}`).expect(200); + }, + + async createDataFrameAnalyticsJob(jobConfig: DataFrameAnalyticsConfig) { + const { id: analyticsId, ...analyticsConfig } = jobConfig; + log.debug(`Creating data frame analytic job with id '${analyticsId}'...`); + await esSupertest + .put(`/_ml/data_frame/analytics/${analyticsId}`) + .send(analyticsConfig) + .expect(200); + + await retry.waitForWithTimeout(`'${analyticsId}' to be created`, 5 * 1000, async () => { + if (await this.getDataFrameAnalyticsJob(analyticsId)) { + return true; + } else { + throw new Error(`expected data frame analytics job '${analyticsId}' to be created`); + } + }); + }, }; } diff --git a/x-pack/test/functional/services/machine_learning/data_frame_analytics_creation.ts b/x-pack/test/functional/services/machine_learning/data_frame_analytics_creation.ts index 96dc8993c3d35..9d5f5753e8b04 100644 --- a/x-pack/test/functional/services/machine_learning/data_frame_analytics_creation.ts +++ b/x-pack/test/functional/services/machine_learning/data_frame_analytics_creation.ts @@ -4,10 +4,31 @@ * you may not use this file except in compliance with the Elastic License. */ import expect from '@kbn/expect'; +import { DataFrameAnalyticsConfig } from '../../../../plugins/ml/public/application/data_frame_analytics/common'; +import { + ClassificationAnalysis, + RegressionAnalysis, +} from '../../../../plugins/ml/public/application/data_frame_analytics/common/analytics'; import { FtrProviderContext } from '../../ftr_provider_context'; import { MlCommon } from './common'; +enum ANALYSIS_CONFIG_TYPE { + OUTLIER_DETECTION = 'outlier_detection', + REGRESSION = 'regression', + CLASSIFICATION = 'classification', +} + +const isRegressionAnalysis = (arg: any): arg is RegressionAnalysis => { + const keys = Object.keys(arg); + return keys.length === 1 && keys[0] === ANALYSIS_CONFIG_TYPE.REGRESSION; +}; + +const isClassificationAnalysis = (arg: any): arg is ClassificationAnalysis => { + const keys = Object.keys(arg); + return keys.length === 1 && keys[0] === ANALYSIS_CONFIG_TYPE.CLASSIFICATION; +}; + export function MachineLearningDataFrameAnalyticsCreationProvider( { getService }: FtrProviderContext, mlCommon: MlCommon @@ -114,6 +135,16 @@ export function MachineLearningDataFrameAnalyticsCreationProvider( ); }, + async assertExcludedFieldsSelection(expectedSelection: string[]) { + const actualSelection = await comboBox.getComboBoxSelectedOptions( + 'mlAnalyticsCreateJobFlyoutExcludesSelect > comboBoxInput' + ); + expect(actualSelection).to.eql( + expectedSelection, + `Excluded fields should be '${expectedSelection}' (got '${actualSelection}')` + ); + }, + async selectSourceIndex(sourceIndex: string) { await comboBox.set( 'mlAnalyticsCreateJobFlyoutSourceIndexSelect > comboBoxInput', @@ -297,6 +328,11 @@ export function MachineLearningDataFrameAnalyticsCreationProvider( await testSubjects.missingOrFail('mlAnalyticsCreateJobFlyoutCreateButton'); }, + async isCreateButtonDisabled() { + const isEnabled = await testSubjects.isEnabled('mlAnalyticsCreateJobFlyoutCreateButton'); + return !isEnabled; + }, + async createAnalyticsJob() { await testSubjects.click('mlAnalyticsCreateJobFlyoutCreateButton'); await retry.tryForTime(5000, async () => { @@ -331,5 +367,24 @@ export function MachineLearningDataFrameAnalyticsCreationProvider( await testSubjects.missingOrFail('mlAnalyticsCreateJobFlyout'); }); }, + + async getHeaderText() { + return await testSubjects.getVisibleText('mlDataFrameAnalyticsFlyoutHeaderTitle'); + }, + + async assertInitialCloneJobForm(job: DataFrameAnalyticsConfig) { + const jobType = Object.keys(job.analysis)[0]; + await this.assertJobTypeSelection(jobType); + await this.assertJobIdValue(''); // id should be empty + await this.assertJobDescriptionValue(String(job.description)); + await this.assertSourceIndexSelection(job.source.index as string[]); + await this.assertDestIndexValue(''); // destination index should be empty + if (isClassificationAnalysis(job.analysis) || isRegressionAnalysis(job.analysis)) { + await this.assertDependentVariableSelection([job.analysis[jobType].dependent_variable]); + await this.assertTrainingPercentValue(String(job.analysis[jobType].training_percent)); + } + await this.assertExcludedFieldsSelection(job.analyzed_fields.excludes); + await this.assertModelMemoryValue(job.model_memory_limit); + }, }; } diff --git a/x-pack/test/functional/services/machine_learning/data_frame_analytics_table.ts b/x-pack/test/functional/services/machine_learning/data_frame_analytics_table.ts index 1d710a1c4cec7..921981768daba 100644 --- a/x-pack/test/functional/services/machine_learning/data_frame_analytics_table.ts +++ b/x-pack/test/functional/services/machine_learning/data_frame_analytics_table.ts @@ -10,6 +10,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export function MachineLearningDataFrameAnalyticsTableProvider({ getService }: FtrProviderContext) { const testSubjects = getService('testSubjects'); + const find = getService('find'); return new (class AnalyticsTable { public async parseAnalyticsTable() { @@ -108,5 +109,18 @@ export function MachineLearningDataFrameAnalyticsTableProvider({ getService }: F const analyticsRow = rows.filter(row => row.id === analyticsId)[0]; expect(analyticsRow).to.eql(expectedRow); } + + public async openRowActions(analyticsId: string) { + await find.clickByCssSelector( + `[data-test-subj="mlAnalyticsTableRow row-${analyticsId}"] [data-test-subj=euiCollapsedItemActionsButton]` + ); + await find.existsByCssSelector('.euiPanel', 20 * 1000); + } + + public async cloneJob(analyticsId: string) { + await this.openRowActions(analyticsId); + await testSubjects.click(`mlAnalyticsJobCloneButton`); + await testSubjects.existOrFail('mlAnalyticsCreateJobFlyout'); + } })(); } diff --git a/x-pack/test/siem_cypress/es_archives/signals/data.json.gz b/x-pack/test/siem_cypress/es_archives/signals/data.json.gz new file mode 100644 index 0000000000000..c0d7fb18bbdb2 Binary files /dev/null and b/x-pack/test/siem_cypress/es_archives/signals/data.json.gz differ diff --git a/x-pack/test/siem_cypress/es_archives/signals/mappings.json b/x-pack/test/siem_cypress/es_archives/signals/mappings.json new file mode 100644 index 0000000000000..114faa0dae336 --- /dev/null +++ b/x-pack/test/siem_cypress/es_archives/signals/mappings.json @@ -0,0 +1,7602 @@ +{ + "type": "index", + "value": { + "aliases": { + ".siem-signals-default": { + "is_write_index": true + } + }, + "index": ".siem-signals-default-000001", + "mappings": { + "dynamic": "false", + "properties": { + "@timestamp": { + "type": "date" + }, + "agent": { + "properties": { + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "client": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "cloud": { + "properties": { + "account": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "availability_zone": { + "ignore_above": 1024, + "type": "keyword" + }, + "instance": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "machine": { + "properties": { + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "region": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "container": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "image": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "tag": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "labels": { + "type": "object" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "runtime": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "destination": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "dns": { + "properties": { + "answers": { + "properties": { + "class": { + "ignore_above": 1024, + "type": "keyword" + }, + "data": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "ttl": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "header_flags": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "op_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "question": { + "properties": { + "class": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "subdomain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "resolved_ip": { + "type": "ip" + }, + "response_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "error": { + "properties": { + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "message": { + "norms": false, + "type": "text" + }, + "stack_trace": { + "doc_values": false, + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "index": false, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "event": { + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "created": { + "type": "date" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "end": { + "type": "date" + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ingested": { + "type": "date" + }, + "kind": { + "ignore_above": 1024, + "type": "keyword" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "doc_values": false, + "ignore_above": 1024, + "index": false, + "type": "keyword" + }, + "outcome": { + "ignore_above": 1024, + "type": "keyword" + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "risk_score": { + "type": "float" + }, + "risk_score_norm": { + "type": "float" + }, + "sequence": { + "type": "long" + }, + "severity": { + "type": "long" + }, + "start": { + "type": "date" + }, + "timezone": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "file": { + "properties": { + "accessed": { + "type": "date" + }, + "attributes": { + "ignore_above": 1024, + "type": "keyword" + }, + "created": { + "type": "date" + }, + "ctime": { + "type": "date" + }, + "device": { + "ignore_above": 1024, + "type": "keyword" + }, + "directory": { + "ignore_above": 1024, + "type": "keyword" + }, + "drive_letter": { + "ignore_above": 1, + "type": "keyword" + }, + "extension": { + "ignore_above": 1024, + "type": "keyword" + }, + "gid": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "inode": { + "ignore_above": 1024, + "type": "keyword" + }, + "mode": { + "ignore_above": 1024, + "type": "keyword" + }, + "mtime": { + "type": "date" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "owner": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "size": { + "type": "long" + }, + "target_path": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uid": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "http": { + "properties": { + "request": { + "properties": { + "body": { + "properties": { + "bytes": { + "type": "long" + }, + "content": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "bytes": { + "type": "long" + }, + "method": { + "ignore_above": 1024, + "type": "keyword" + }, + "referrer": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "response": { + "properties": { + "body": { + "properties": { + "bytes": { + "type": "long" + }, + "content": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "bytes": { + "type": "long" + }, + "status_code": { + "type": "long" + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "labels": { + "type": "object" + }, + "log": { + "properties": { + "level": { + "ignore_above": 1024, + "type": "keyword" + }, + "logger": { + "ignore_above": 1024, + "type": "keyword" + }, + "origin": { + "properties": { + "file": { + "properties": { + "line": { + "type": "integer" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "function": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "original": { + "doc_values": false, + "ignore_above": 1024, + "index": false, + "type": "keyword" + }, + "syslog": { + "properties": { + "facility": { + "properties": { + "code": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "priority": { + "type": "long" + }, + "severity": { + "properties": { + "code": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "message": { + "norms": false, + "type": "text" + }, + "network": { + "properties": { + "application": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes": { + "type": "long" + }, + "community_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "direction": { + "ignore_above": 1024, + "type": "keyword" + }, + "forwarded_ip": { + "type": "ip" + }, + "iana_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "packets": { + "type": "long" + }, + "protocol": { + "ignore_above": 1024, + "type": "keyword" + }, + "transport": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "observer": { + "properties": { + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + }, + "serial_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "vendor": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "organization": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "package": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "build_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "checksum": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "install_scope": { + "ignore_above": 1024, + "type": "keyword" + }, + "installed": { + "type": "date" + }, + "license": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "size": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "process": { + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "args_count": { + "type": "long" + }, + "command_line": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "exit_code": { + "type": "long" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "parent": { + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "args_count": { + "type": "long" + }, + "command_line": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "exit_code": { + "type": "long" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "pgid": { + "type": "long" + }, + "pid": { + "type": "long" + }, + "ppid": { + "type": "long" + }, + "start": { + "type": "date" + }, + "thread": { + "properties": { + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "title": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + }, + "working_directory": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pgid": { + "type": "long" + }, + "pid": { + "type": "long" + }, + "ppid": { + "type": "long" + }, + "start": { + "type": "date" + }, + "thread": { + "properties": { + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "title": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + }, + "working_directory": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "registry": { + "properties": { + "data": { + "properties": { + "bytes": { + "ignore_above": 1024, + "type": "keyword" + }, + "strings": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hive": { + "ignore_above": 1024, + "type": "keyword" + }, + "key": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "value": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "related": { + "properties": { + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "user": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "rule": { + "properties": { + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "ruleset": { + "ignore_above": 1024, + "type": "keyword" + }, + "uuid": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "server": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "service": { + "properties": { + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "node": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "state": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "signal": { + "properties": { + "ancestors": { + "properties": { + "depth": { + "type": "long" + }, + "id": { + "type": "keyword" + }, + "rule": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "original_event": { + "properties": { + "action": { + "type": "keyword" + }, + "category": { + "type": "keyword" + }, + "code": { + "type": "keyword" + }, + "created": { + "type": "date" + }, + "dataset": { + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "end": { + "type": "date" + }, + "hash": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "kind": { + "type": "keyword" + }, + "module": { + "type": "keyword" + }, + "original": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "outcome": { + "type": "keyword" + }, + "provider": { + "type": "keyword" + }, + "risk_score": { + "type": "float" + }, + "risk_score_norm": { + "type": "float" + }, + "sequence": { + "type": "long" + }, + "severity": { + "type": "long" + }, + "start": { + "type": "date" + }, + "timezone": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "original_time": { + "type": "date" + }, + "parent": { + "properties": { + "depth": { + "type": "long" + }, + "id": { + "type": "keyword" + }, + "index": { + "type": "keyword" + }, + "rule": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "rule": { + "properties": { + "created_at": { + "type": "date" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "keyword" + }, + "enabled": { + "type": "keyword" + }, + "false_positives": { + "type": "keyword" + }, + "filters": { + "type": "object" + }, + "from": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "immutable": { + "type": "keyword" + }, + "index": { + "type": "keyword" + }, + "interval": { + "type": "keyword" + }, + "language": { + "type": "keyword" + }, + "max_signals": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "output_index": { + "type": "keyword" + }, + "query": { + "type": "keyword" + }, + "references": { + "type": "keyword" + }, + "risk_score": { + "type": "keyword" + }, + "rule_id": { + "type": "keyword" + }, + "saved_id": { + "type": "keyword" + }, + "severity": { + "type": "keyword" + }, + "size": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "threat": { + "properties": { + "framework": { + "type": "keyword" + }, + "tactic": { + "properties": { + "id": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "reference": { + "type": "keyword" + } + } + }, + "technique": { + "properties": { + "id": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "reference": { + "type": "keyword" + } + } + } + } + }, + "timeline_id": { + "type": "keyword" + }, + "timeline_title": { + "type": "keyword" + }, + "to": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "status": { + "type": "keyword" + } + } + }, + "source": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "tags": { + "ignore_above": 1024, + "type": "keyword" + }, + "threat": { + "properties": { + "framework": { + "ignore_above": 1024, + "type": "keyword" + }, + "tactic": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "technique": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "tls": { + "properties": { + "cipher": { + "ignore_above": 1024, + "type": "keyword" + }, + "client": { + "properties": { + "certificate": { + "ignore_above": 1024, + "type": "keyword" + }, + "certificate_chain": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "issuer": { + "ignore_above": 1024, + "type": "keyword" + }, + "ja3": { + "ignore_above": 1024, + "type": "keyword" + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "server_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject": { + "ignore_above": 1024, + "type": "keyword" + }, + "supported_ciphers": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "curve": { + "ignore_above": 1024, + "type": "keyword" + }, + "established": { + "type": "boolean" + }, + "next_protocol": { + "ignore_above": 1024, + "type": "keyword" + }, + "resumed": { + "type": "boolean" + }, + "server": { + "properties": { + "certificate": { + "ignore_above": 1024, + "type": "keyword" + }, + "certificate_chain": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "issuer": { + "ignore_above": 1024, + "type": "keyword" + }, + "ja3s": { + "ignore_above": 1024, + "type": "keyword" + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "subject": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + }, + "version_protocol": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "trace": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "transaction": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "url": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "extension": { + "ignore_above": 1024, + "type": "keyword" + }, + "fragment": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "password": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "port": { + "type": "long" + }, + "query": { + "ignore_above": 1024, + "type": "keyword" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "scheme": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "username": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "user_agent": { + "properties": { + "device": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "vulnerability": { + "properties": { + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "classification": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "enumeration": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "report_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "scanner": { + "properties": { + "vendor": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "score": { + "properties": { + "base": { + "type": "float" + }, + "environmental": { + "type": "float" + }, + "temporal": { + "type": "float" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "severity": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "settings": { + "index": { + "lifecycle": { + "name": ".siem-signals-default", + "rollover_alias": ".siem-signals-default" + }, + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} + +{ + "type": "index", + "value": { + "aliases": { + "auditbeat-7.6.0": { + "is_write_index": true + } + }, + "index": "auditbeat-7.6.0-2020.03.11-000001", + "mappings": { + "_meta": { + "beat": "auditbeat", + "version": "7.6.0" + }, + "date_detection": false, + "dynamic_templates": [ + { + "labels": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "labels.*" + } + }, + { + "container.labels": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "container.labels.*" + } + }, + { + "dns.answers": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "dns.answers.*" + } + }, + { + "log.syslog": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "log.syslog.*" + } + }, + { + "fields": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "fields.*" + } + }, + { + "docker.container.labels": { + "mapping": { + "type": "keyword" + }, + "match_mapping_type": "string", + "path_match": "docker.container.labels.*" + } + }, + { + "kubernetes.labels.*": { + "mapping": { + "type": "keyword" + }, + "path_match": "kubernetes.labels.*" + } + }, + { + "kubernetes.annotations.*": { + "mapping": { + "type": "keyword" + }, + "path_match": "kubernetes.annotations.*" + } + }, + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "agent": { + "properties": { + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "auditd": { + "properties": { + "data": { + "properties": { + "a0": { + "ignore_above": 1024, + "type": "keyword" + }, + "a1": { + "ignore_above": 1024, + "type": "keyword" + }, + "a2": { + "ignore_above": 1024, + "type": "keyword" + }, + "a3": { + "ignore_above": 1024, + "type": "keyword" + }, + "a[0-3]": { + "ignore_above": 1024, + "type": "keyword" + }, + "acct": { + "ignore_above": 1024, + "type": "keyword" + }, + "acl": { + "ignore_above": 1024, + "type": "keyword" + }, + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "added": { + "ignore_above": 1024, + "type": "keyword" + }, + "addr": { + "ignore_above": 1024, + "type": "keyword" + }, + "apparmor": { + "ignore_above": 1024, + "type": "keyword" + }, + "arch": { + "ignore_above": 1024, + "type": "keyword" + }, + "argc": { + "ignore_above": 1024, + "type": "keyword" + }, + "audit_backlog_limit": { + "ignore_above": 1024, + "type": "keyword" + }, + "audit_backlog_wait_time": { + "ignore_above": 1024, + "type": "keyword" + }, + "audit_enabled": { + "ignore_above": 1024, + "type": "keyword" + }, + "audit_failure": { + "ignore_above": 1024, + "type": "keyword" + }, + "banners": { + "ignore_above": 1024, + "type": "keyword" + }, + "bool": { + "ignore_above": 1024, + "type": "keyword" + }, + "bus": { + "ignore_above": 1024, + "type": "keyword" + }, + "cap_fe": { + "ignore_above": 1024, + "type": "keyword" + }, + "cap_fi": { + "ignore_above": 1024, + "type": "keyword" + }, + "cap_fp": { + "ignore_above": 1024, + "type": "keyword" + }, + "cap_fver": { + "ignore_above": 1024, + "type": "keyword" + }, + "cap_pe": { + "ignore_above": 1024, + "type": "keyword" + }, + "cap_pi": { + "ignore_above": 1024, + "type": "keyword" + }, + "cap_pp": { + "ignore_above": 1024, + "type": "keyword" + }, + "capability": { + "ignore_above": 1024, + "type": "keyword" + }, + "cgroup": { + "ignore_above": 1024, + "type": "keyword" + }, + "changed": { + "ignore_above": 1024, + "type": "keyword" + }, + "cipher": { + "ignore_above": 1024, + "type": "keyword" + }, + "class": { + "ignore_above": 1024, + "type": "keyword" + }, + "cmd": { + "ignore_above": 1024, + "type": "keyword" + }, + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "compat": { + "ignore_above": 1024, + "type": "keyword" + }, + "daddr": { + "ignore_above": 1024, + "type": "keyword" + }, + "data": { + "ignore_above": 1024, + "type": "keyword" + }, + "default-context": { + "ignore_above": 1024, + "type": "keyword" + }, + "device": { + "ignore_above": 1024, + "type": "keyword" + }, + "dir": { + "ignore_above": 1024, + "type": "keyword" + }, + "direction": { + "ignore_above": 1024, + "type": "keyword" + }, + "dmac": { + "ignore_above": 1024, + "type": "keyword" + }, + "dport": { + "ignore_above": 1024, + "type": "keyword" + }, + "enforcing": { + "ignore_above": 1024, + "type": "keyword" + }, + "entries": { + "ignore_above": 1024, + "type": "keyword" + }, + "exit": { + "ignore_above": 1024, + "type": "keyword" + }, + "fam": { + "ignore_above": 1024, + "type": "keyword" + }, + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "fd": { + "ignore_above": 1024, + "type": "keyword" + }, + "fe": { + "ignore_above": 1024, + "type": "keyword" + }, + "feature": { + "ignore_above": 1024, + "type": "keyword" + }, + "fi": { + "ignore_above": 1024, + "type": "keyword" + }, + "file": { + "ignore_above": 1024, + "type": "keyword" + }, + "flags": { + "ignore_above": 1024, + "type": "keyword" + }, + "format": { + "ignore_above": 1024, + "type": "keyword" + }, + "fp": { + "ignore_above": 1024, + "type": "keyword" + }, + "fver": { + "ignore_above": 1024, + "type": "keyword" + }, + "grantors": { + "ignore_above": 1024, + "type": "keyword" + }, + "grp": { + "ignore_above": 1024, + "type": "keyword" + }, + "hook": { + "ignore_above": 1024, + "type": "keyword" + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "icmp_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "igid": { + "ignore_above": 1024, + "type": "keyword" + }, + "img-ctx": { + "ignore_above": 1024, + "type": "keyword" + }, + "inif": { + "ignore_above": 1024, + "type": "keyword" + }, + "ino": { + "ignore_above": 1024, + "type": "keyword" + }, + "inode_gid": { + "ignore_above": 1024, + "type": "keyword" + }, + "inode_uid": { + "ignore_above": 1024, + "type": "keyword" + }, + "invalid_context": { + "ignore_above": 1024, + "type": "keyword" + }, + "ioctlcmd": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "ignore_above": 1024, + "type": "keyword" + }, + "ipid": { + "ignore_above": 1024, + "type": "keyword" + }, + "ipx-net": { + "ignore_above": 1024, + "type": "keyword" + }, + "items": { + "ignore_above": 1024, + "type": "keyword" + }, + "iuid": { + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "kind": { + "ignore_above": 1024, + "type": "keyword" + }, + "ksize": { + "ignore_above": 1024, + "type": "keyword" + }, + "laddr": { + "ignore_above": 1024, + "type": "keyword" + }, + "len": { + "ignore_above": 1024, + "type": "keyword" + }, + "list": { + "ignore_above": 1024, + "type": "keyword" + }, + "lport": { + "ignore_above": 1024, + "type": "keyword" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "macproto": { + "ignore_above": 1024, + "type": "keyword" + }, + "maj": { + "ignore_above": 1024, + "type": "keyword" + }, + "major": { + "ignore_above": 1024, + "type": "keyword" + }, + "minor": { + "ignore_above": 1024, + "type": "keyword" + }, + "model": { + "ignore_above": 1024, + "type": "keyword" + }, + "msg": { + "ignore_above": 1024, + "type": "keyword" + }, + "nargs": { + "ignore_above": 1024, + "type": "keyword" + }, + "net": { + "ignore_above": 1024, + "type": "keyword" + }, + "new": { + "ignore_above": 1024, + "type": "keyword" + }, + "new-chardev": { + "ignore_above": 1024, + "type": "keyword" + }, + "new-disk": { + "ignore_above": 1024, + "type": "keyword" + }, + "new-enabled": { + "ignore_above": 1024, + "type": "keyword" + }, + "new-fs": { + "ignore_above": 1024, + "type": "keyword" + }, + "new-level": { + "ignore_above": 1024, + "type": "keyword" + }, + "new-log_passwd": { + "ignore_above": 1024, + "type": "keyword" + }, + "new-mem": { + "ignore_above": 1024, + "type": "keyword" + }, + "new-net": { + "ignore_above": 1024, + "type": "keyword" + }, + "new-range": { + "ignore_above": 1024, + "type": "keyword" + }, + "new-rng": { + "ignore_above": 1024, + "type": "keyword" + }, + "new-role": { + "ignore_above": 1024, + "type": "keyword" + }, + "new-seuser": { + "ignore_above": 1024, + "type": "keyword" + }, + "new-vcpu": { + "ignore_above": 1024, + "type": "keyword" + }, + "new_gid": { + "ignore_above": 1024, + "type": "keyword" + }, + "new_lock": { + "ignore_above": 1024, + "type": "keyword" + }, + "new_pe": { + "ignore_above": 1024, + "type": "keyword" + }, + "new_pi": { + "ignore_above": 1024, + "type": "keyword" + }, + "new_pp": { + "ignore_above": 1024, + "type": "keyword" + }, + "nlnk-fam": { + "ignore_above": 1024, + "type": "keyword" + }, + "nlnk-grp": { + "ignore_above": 1024, + "type": "keyword" + }, + "nlnk-pid": { + "ignore_above": 1024, + "type": "keyword" + }, + "oauid": { + "ignore_above": 1024, + "type": "keyword" + }, + "obj": { + "ignore_above": 1024, + "type": "keyword" + }, + "obj_gid": { + "ignore_above": 1024, + "type": "keyword" + }, + "obj_uid": { + "ignore_above": 1024, + "type": "keyword" + }, + "ocomm": { + "ignore_above": 1024, + "type": "keyword" + }, + "oflag": { + "ignore_above": 1024, + "type": "keyword" + }, + "old": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-auid": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-chardev": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-disk": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-enabled": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-fs": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-level": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-log_passwd": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-mem": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-net": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-range": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-rng": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-role": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-ses": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-seuser": { + "ignore_above": 1024, + "type": "keyword" + }, + "old-vcpu": { + "ignore_above": 1024, + "type": "keyword" + }, + "old_enforcing": { + "ignore_above": 1024, + "type": "keyword" + }, + "old_lock": { + "ignore_above": 1024, + "type": "keyword" + }, + "old_pe": { + "ignore_above": 1024, + "type": "keyword" + }, + "old_pi": { + "ignore_above": 1024, + "type": "keyword" + }, + "old_pp": { + "ignore_above": 1024, + "type": "keyword" + }, + "old_prom": { + "ignore_above": 1024, + "type": "keyword" + }, + "old_val": { + "ignore_above": 1024, + "type": "keyword" + }, + "op": { + "ignore_above": 1024, + "type": "keyword" + }, + "opid": { + "ignore_above": 1024, + "type": "keyword" + }, + "oses": { + "ignore_above": 1024, + "type": "keyword" + }, + "outif": { + "ignore_above": 1024, + "type": "keyword" + }, + "parent": { + "ignore_above": 1024, + "type": "keyword" + }, + "per": { + "ignore_above": 1024, + "type": "keyword" + }, + "perm": { + "ignore_above": 1024, + "type": "keyword" + }, + "perm_mask": { + "ignore_above": 1024, + "type": "keyword" + }, + "permissive": { + "ignore_above": 1024, + "type": "keyword" + }, + "pfs": { + "ignore_above": 1024, + "type": "keyword" + }, + "printer": { + "ignore_above": 1024, + "type": "keyword" + }, + "prom": { + "ignore_above": 1024, + "type": "keyword" + }, + "proto": { + "ignore_above": 1024, + "type": "keyword" + }, + "qbytes": { + "ignore_above": 1024, + "type": "keyword" + }, + "range": { + "ignore_above": 1024, + "type": "keyword" + }, + "reason": { + "ignore_above": 1024, + "type": "keyword" + }, + "removed": { + "ignore_above": 1024, + "type": "keyword" + }, + "res": { + "ignore_above": 1024, + "type": "keyword" + }, + "resrc": { + "ignore_above": 1024, + "type": "keyword" + }, + "rport": { + "ignore_above": 1024, + "type": "keyword" + }, + "sauid": { + "ignore_above": 1024, + "type": "keyword" + }, + "scontext": { + "ignore_above": 1024, + "type": "keyword" + }, + "selected-context": { + "ignore_above": 1024, + "type": "keyword" + }, + "seperm": { + "ignore_above": 1024, + "type": "keyword" + }, + "seperms": { + "ignore_above": 1024, + "type": "keyword" + }, + "seqno": { + "ignore_above": 1024, + "type": "keyword" + }, + "seresult": { + "ignore_above": 1024, + "type": "keyword" + }, + "ses": { + "ignore_above": 1024, + "type": "keyword" + }, + "seuser": { + "ignore_above": 1024, + "type": "keyword" + }, + "sig": { + "ignore_above": 1024, + "type": "keyword" + }, + "sigev_signo": { + "ignore_above": 1024, + "type": "keyword" + }, + "smac": { + "ignore_above": 1024, + "type": "keyword" + }, + "socket": { + "properties": { + "addr": { + "ignore_above": 1024, + "type": "keyword" + }, + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "port": { + "ignore_above": 1024, + "type": "keyword" + }, + "saddr": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "spid": { + "ignore_above": 1024, + "type": "keyword" + }, + "sport": { + "ignore_above": 1024, + "type": "keyword" + }, + "state": { + "ignore_above": 1024, + "type": "keyword" + }, + "subj": { + "ignore_above": 1024, + "type": "keyword" + }, + "success": { + "ignore_above": 1024, + "type": "keyword" + }, + "syscall": { + "ignore_above": 1024, + "type": "keyword" + }, + "table": { + "ignore_above": 1024, + "type": "keyword" + }, + "tclass": { + "ignore_above": 1024, + "type": "keyword" + }, + "tcontext": { + "ignore_above": 1024, + "type": "keyword" + }, + "terminal": { + "ignore_above": 1024, + "type": "keyword" + }, + "tty": { + "ignore_above": 1024, + "type": "keyword" + }, + "unit": { + "ignore_above": 1024, + "type": "keyword" + }, + "uri": { + "ignore_above": 1024, + "type": "keyword" + }, + "uuid": { + "ignore_above": 1024, + "type": "keyword" + }, + "val": { + "ignore_above": 1024, + "type": "keyword" + }, + "ver": { + "ignore_above": 1024, + "type": "keyword" + }, + "virt": { + "ignore_above": 1024, + "type": "keyword" + }, + "vm": { + "ignore_above": 1024, + "type": "keyword" + }, + "vm-ctx": { + "ignore_above": 1024, + "type": "keyword" + }, + "vm-pid": { + "ignore_above": 1024, + "type": "keyword" + }, + "watch": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "message_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "paths": { + "properties": { + "dev": { + "ignore_above": 1024, + "type": "keyword" + }, + "inode": { + "ignore_above": 1024, + "type": "keyword" + }, + "item": { + "ignore_above": 1024, + "type": "keyword" + }, + "mode": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "nametype": { + "ignore_above": 1024, + "type": "keyword" + }, + "obj_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "obj_level": { + "ignore_above": 1024, + "type": "keyword" + }, + "obj_role": { + "ignore_above": 1024, + "type": "keyword" + }, + "obj_user": { + "ignore_above": 1024, + "type": "keyword" + }, + "objtype": { + "ignore_above": 1024, + "type": "keyword" + }, + "ogid": { + "ignore_above": 1024, + "type": "keyword" + }, + "ouid": { + "ignore_above": 1024, + "type": "keyword" + }, + "rdev": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "result": { + "ignore_above": 1024, + "type": "keyword" + }, + "sequence": { + "type": "long" + }, + "session": { + "ignore_above": 1024, + "type": "keyword" + }, + "summary": { + "properties": { + "actor": { + "properties": { + "primary": { + "ignore_above": 1024, + "type": "keyword" + }, + "secondary": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "how": { + "ignore_above": 1024, + "type": "keyword" + }, + "object": { + "properties": { + "primary": { + "ignore_above": 1024, + "type": "keyword" + }, + "secondary": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "client": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "cloud": { + "properties": { + "account": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "availability_zone": { + "ignore_above": 1024, + "type": "keyword" + }, + "image": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "instance": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "machine": { + "properties": { + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "project": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "region": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "container": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "image": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "tag": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "labels": { + "type": "object" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "runtime": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "destination": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "dns": { + "properties": { + "answers": { + "properties": { + "class": { + "ignore_above": 1024, + "type": "keyword" + }, + "data": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "ttl": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "header_flags": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "op_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "question": { + "properties": { + "class": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "subdomain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "resolved_ip": { + "type": "ip" + }, + "response_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "docker": { + "properties": { + "container": { + "properties": { + "labels": { + "type": "object" + } + } + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "error": { + "properties": { + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "message": { + "norms": false, + "type": "text" + }, + "stack_trace": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "event": { + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "created": { + "type": "date" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "end": { + "type": "date" + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ingested": { + "type": "date" + }, + "kind": { + "ignore_above": 1024, + "type": "keyword" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + }, + "origin": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "ignore_above": 1024, + "type": "keyword" + }, + "outcome": { + "ignore_above": 1024, + "type": "keyword" + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "risk_score": { + "type": "float" + }, + "risk_score_norm": { + "type": "float" + }, + "sequence": { + "type": "long" + }, + "severity": { + "type": "long" + }, + "start": { + "type": "date" + }, + "timezone": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "fields": { + "type": "object" + }, + "file": { + "properties": { + "accessed": { + "type": "date" + }, + "attributes": { + "ignore_above": 1024, + "type": "keyword" + }, + "created": { + "type": "date" + }, + "ctime": { + "type": "date" + }, + "device": { + "ignore_above": 1024, + "type": "keyword" + }, + "directory": { + "ignore_above": 1024, + "type": "keyword" + }, + "drive_letter": { + "ignore_above": 1, + "type": "keyword" + }, + "extension": { + "ignore_above": 1024, + "type": "keyword" + }, + "gid": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "inode": { + "ignore_above": 1024, + "type": "keyword" + }, + "mode": { + "ignore_above": 1024, + "type": "keyword" + }, + "mtime": { + "type": "date" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "origin": { + "fields": { + "raw": { + "ignore_above": 1024, + "type": "keyword" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "owner": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "selinux": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "level": { + "ignore_above": 1024, + "type": "keyword" + }, + "role": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "setgid": { + "type": "boolean" + }, + "setuid": { + "type": "boolean" + }, + "size": { + "type": "long" + }, + "target_path": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uid": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "geoip": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "properties": { + "blake2b_256": { + "ignore_above": 1024, + "type": "keyword" + }, + "blake2b_384": { + "ignore_above": 1024, + "type": "keyword" + }, + "blake2b_512": { + "ignore_above": 1024, + "type": "keyword" + }, + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha224": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha384": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha3_224": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha3_256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha3_384": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha3_512": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512_224": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512_256": { + "ignore_above": 1024, + "type": "keyword" + }, + "xxh64": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "containerized": { + "type": "boolean" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "build": { + "ignore_above": 1024, + "type": "keyword" + }, + "codename": { + "ignore_above": 1024, + "type": "keyword" + }, + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "http": { + "properties": { + "request": { + "properties": { + "body": { + "properties": { + "bytes": { + "type": "long" + }, + "content": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "bytes": { + "type": "long" + }, + "method": { + "ignore_above": 1024, + "type": "keyword" + }, + "referrer": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "response": { + "properties": { + "body": { + "properties": { + "bytes": { + "type": "long" + }, + "content": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "bytes": { + "type": "long" + }, + "status_code": { + "type": "long" + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "jolokia": { + "properties": { + "agent": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "secured": { + "type": "boolean" + }, + "server": { + "properties": { + "product": { + "ignore_above": 1024, + "type": "keyword" + }, + "vendor": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "url": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "kubernetes": { + "properties": { + "annotations": { + "properties": { + "*": { + "type": "object" + } + } + }, + "container": { + "properties": { + "image": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "deployment": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "labels": { + "properties": { + "*": { + "type": "object" + } + } + }, + "namespace": { + "ignore_above": 1024, + "type": "keyword" + }, + "node": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pod": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "uid": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "replicaset": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "statefulset": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "labels": { + "type": "object" + }, + "log": { + "properties": { + "level": { + "ignore_above": 1024, + "type": "keyword" + }, + "logger": { + "ignore_above": 1024, + "type": "keyword" + }, + "origin": { + "properties": { + "file": { + "properties": { + "line": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "function": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "original": { + "ignore_above": 1024, + "type": "keyword" + }, + "syslog": { + "properties": { + "facility": { + "properties": { + "code": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "priority": { + "type": "long" + }, + "severity": { + "properties": { + "code": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "message": { + "norms": false, + "type": "text" + }, + "network": { + "properties": { + "application": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes": { + "type": "long" + }, + "community_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "direction": { + "ignore_above": 1024, + "type": "keyword" + }, + "forwarded_ip": { + "type": "ip" + }, + "iana_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "packets": { + "type": "long" + }, + "protocol": { + "ignore_above": 1024, + "type": "keyword" + }, + "transport": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "observer": { + "properties": { + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + }, + "serial_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "vendor": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "organization": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "package": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "build_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "checksum": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "install_scope": { + "ignore_above": 1024, + "type": "keyword" + }, + "installed": { + "type": "date" + }, + "license": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "size": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "process": { + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "args_count": { + "type": "long" + }, + "command_line": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "exit_code": { + "type": "long" + }, + "hash": { + "properties": { + "blake2b_256": { + "ignore_above": 1024, + "type": "keyword" + }, + "blake2b_384": { + "ignore_above": 1024, + "type": "keyword" + }, + "blake2b_512": { + "ignore_above": 1024, + "type": "keyword" + }, + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha224": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha384": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha3_224": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha3_256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha3_384": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha3_512": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512_224": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512_256": { + "ignore_above": 1024, + "type": "keyword" + }, + "xxh64": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "parent": { + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "args_count": { + "type": "long" + }, + "command_line": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "exit_code": { + "type": "long" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "pgid": { + "type": "long" + }, + "pid": { + "type": "long" + }, + "ppid": { + "type": "long" + }, + "start": { + "type": "date" + }, + "thread": { + "properties": { + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "title": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + }, + "working_directory": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pgid": { + "type": "long" + }, + "pid": { + "type": "long" + }, + "ppid": { + "type": "long" + }, + "start": { + "type": "date" + }, + "thread": { + "properties": { + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "title": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + }, + "working_directory": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "registry": { + "properties": { + "data": { + "properties": { + "bytes": { + "ignore_above": 1024, + "type": "keyword" + }, + "strings": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hive": { + "ignore_above": 1024, + "type": "keyword" + }, + "key": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "value": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "related": { + "properties": { + "ip": { + "type": "ip" + }, + "user": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "rule": { + "properties": { + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "ruleset": { + "ignore_above": 1024, + "type": "keyword" + }, + "uuid": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "server": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "service": { + "properties": { + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "node": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "state": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "socket": { + "properties": { + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "source": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "system": { + "properties": { + "audit": { + "properties": { + "host": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "boottime": { + "type": "date" + }, + "containerized": { + "type": "boolean" + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "codename": { + "ignore_above": 1024, + "type": "keyword" + }, + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "timezone": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "offset": { + "properties": { + "sec": { + "type": "long" + } + } + } + } + }, + "uptime": { + "type": "long" + } + } + }, + "package": { + "properties": { + "arch": { + "ignore_above": 1024, + "type": "keyword" + }, + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "installtime": { + "type": "date" + }, + "license": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "release": { + "ignore_above": 1024, + "type": "keyword" + }, + "size": { + "type": "long" + }, + "summary": { + "ignore_above": 1024, + "type": "keyword" + }, + "url": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "user": { + "properties": { + "dir": { + "ignore_above": 1024, + "type": "keyword" + }, + "gid": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "type": "object" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "password": { + "properties": { + "last_changed": { + "type": "date" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "shell": { + "ignore_above": 1024, + "type": "keyword" + }, + "uid": { + "ignore_above": 1024, + "type": "keyword" + }, + "user_information": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "tags": { + "ignore_above": 1024, + "type": "keyword" + }, + "threat": { + "properties": { + "framework": { + "ignore_above": 1024, + "type": "keyword" + }, + "tactic": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "technique": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "timeseries": { + "properties": { + "instance": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "tls": { + "properties": { + "cipher": { + "ignore_above": 1024, + "type": "keyword" + }, + "client": { + "properties": { + "certificate": { + "ignore_above": 1024, + "type": "keyword" + }, + "certificate_chain": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "issuer": { + "ignore_above": 1024, + "type": "keyword" + }, + "ja3": { + "ignore_above": 1024, + "type": "keyword" + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "server_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject": { + "ignore_above": 1024, + "type": "keyword" + }, + "supported_ciphers": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "curve": { + "ignore_above": 1024, + "type": "keyword" + }, + "established": { + "type": "boolean" + }, + "next_protocol": { + "ignore_above": 1024, + "type": "keyword" + }, + "resumed": { + "type": "boolean" + }, + "server": { + "properties": { + "certificate": { + "ignore_above": 1024, + "type": "keyword" + }, + "certificate_chain": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "issuer": { + "ignore_above": 1024, + "type": "keyword" + }, + "ja3s": { + "ignore_above": 1024, + "type": "keyword" + }, + "not_after": { + "type": "date" + }, + "not_before": { + "type": "date" + }, + "subject": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + }, + "version_protocol": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "tracing": { + "properties": { + "trace": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "transaction": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "url": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "extension": { + "ignore_above": 1024, + "type": "keyword" + }, + "fragment": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "password": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "port": { + "type": "long" + }, + "query": { + "ignore_above": 1024, + "type": "keyword" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "scheme": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "username": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "user": { + "properties": { + "audit": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "effective": { + "properties": { + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "filesystem": { + "properties": { + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "full_name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "name_map": { + "type": "object" + }, + "saved": { + "properties": { + "group": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "selinux": { + "properties": { + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "level": { + "ignore_above": 1024, + "type": "keyword" + }, + "role": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "terminal": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "user_agent": { + "properties": { + "device": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "vulnerability": { + "properties": { + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "classification": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "enumeration": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + }, + "report_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "scanner": { + "properties": { + "vendor": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "score": { + "properties": { + "base": { + "type": "float" + }, + "environmental": { + "type": "float" + }, + "temporal": { + "type": "float" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "severity": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "settings": { + "index": { + "lifecycle": { + "name": "auditbeat", + "rollover_alias": "auditbeat-7.6.0" + }, + "mapping": { + "total_fields": { + "limit": "10000" + } + }, + "number_of_replicas": "1", + "number_of_shards": "1", + "query": { + "default_field": [ + "message", + "tags", + "agent.ephemeral_id", + "agent.id", + "agent.name", + "agent.type", + "agent.version", + "as.organization.name", + "client.address", + "client.as.organization.name", + "client.domain", + "client.geo.city_name", + "client.geo.continent_name", + "client.geo.country_iso_code", + "client.geo.country_name", + "client.geo.name", + "client.geo.region_iso_code", + "client.geo.region_name", + "client.mac", + "client.registered_domain", + "client.top_level_domain", + "client.user.domain", + "client.user.email", + "client.user.full_name", + "client.user.group.domain", + "client.user.group.id", + "client.user.group.name", + "client.user.hash", + "client.user.id", + "client.user.name", + "cloud.account.id", + "cloud.availability_zone", + "cloud.instance.id", + "cloud.instance.name", + "cloud.machine.type", + "cloud.provider", + "cloud.region", + "container.id", + "container.image.name", + "container.image.tag", + "container.name", + "container.runtime", + "destination.address", + "destination.as.organization.name", + "destination.domain", + "destination.geo.city_name", + "destination.geo.continent_name", + "destination.geo.country_iso_code", + "destination.geo.country_name", + "destination.geo.name", + "destination.geo.region_iso_code", + "destination.geo.region_name", + "destination.mac", + "destination.registered_domain", + "destination.top_level_domain", + "destination.user.domain", + "destination.user.email", + "destination.user.full_name", + "destination.user.group.domain", + "destination.user.group.id", + "destination.user.group.name", + "destination.user.hash", + "destination.user.id", + "destination.user.name", + "dns.answers.class", + "dns.answers.data", + "dns.answers.name", + "dns.answers.type", + "dns.header_flags", + "dns.id", + "dns.op_code", + "dns.question.class", + "dns.question.name", + "dns.question.registered_domain", + "dns.question.subdomain", + "dns.question.top_level_domain", + "dns.question.type", + "dns.response_code", + "dns.type", + "ecs.version", + "error.code", + "error.id", + "error.message", + "error.stack_trace", + "error.type", + "event.action", + "event.category", + "event.code", + "event.dataset", + "event.hash", + "event.id", + "event.kind", + "event.module", + "event.original", + "event.outcome", + "event.provider", + "event.timezone", + "event.type", + "file.device", + "file.directory", + "file.extension", + "file.gid", + "file.group", + "file.hash.md5", + "file.hash.sha1", + "file.hash.sha256", + "file.hash.sha512", + "file.inode", + "file.mode", + "file.name", + "file.owner", + "file.path", + "file.target_path", + "file.type", + "file.uid", + "geo.city_name", + "geo.continent_name", + "geo.country_iso_code", + "geo.country_name", + "geo.name", + "geo.region_iso_code", + "geo.region_name", + "group.domain", + "group.id", + "group.name", + "hash.md5", + "hash.sha1", + "hash.sha256", + "hash.sha512", + "host.architecture", + "host.geo.city_name", + "host.geo.continent_name", + "host.geo.country_iso_code", + "host.geo.country_name", + "host.geo.name", + "host.geo.region_iso_code", + "host.geo.region_name", + "host.hostname", + "host.id", + "host.mac", + "host.name", + "host.os.family", + "host.os.full", + "host.os.kernel", + "host.os.name", + "host.os.platform", + "host.os.version", + "host.type", + "host.user.domain", + "host.user.email", + "host.user.full_name", + "host.user.group.domain", + "host.user.group.id", + "host.user.group.name", + "host.user.hash", + "host.user.id", + "host.user.name", + "http.request.body.content", + "http.request.method", + "http.request.referrer", + "http.response.body.content", + "http.version", + "log.level", + "log.logger", + "log.origin.file.name", + "log.origin.function", + "log.original", + "log.syslog.facility.name", + "log.syslog.severity.name", + "network.application", + "network.community_id", + "network.direction", + "network.iana_number", + "network.name", + "network.protocol", + "network.transport", + "network.type", + "observer.geo.city_name", + "observer.geo.continent_name", + "observer.geo.country_iso_code", + "observer.geo.country_name", + "observer.geo.name", + "observer.geo.region_iso_code", + "observer.geo.region_name", + "observer.hostname", + "observer.mac", + "observer.name", + "observer.os.family", + "observer.os.full", + "observer.os.kernel", + "observer.os.name", + "observer.os.platform", + "observer.os.version", + "observer.product", + "observer.serial_number", + "observer.type", + "observer.vendor", + "observer.version", + "organization.id", + "organization.name", + "os.family", + "os.full", + "os.kernel", + "os.name", + "os.platform", + "os.version", + "package.architecture", + "package.checksum", + "package.description", + "package.install_scope", + "package.license", + "package.name", + "package.path", + "package.version", + "process.args", + "text", + "process.executable", + "process.hash.md5", + "process.hash.sha1", + "process.hash.sha256", + "process.hash.sha512", + "process.name", + "text", + "text", + "text", + "text", + "text", + "process.thread.name", + "process.title", + "process.working_directory", + "server.address", + "server.as.organization.name", + "server.domain", + "server.geo.city_name", + "server.geo.continent_name", + "server.geo.country_iso_code", + "server.geo.country_name", + "server.geo.name", + "server.geo.region_iso_code", + "server.geo.region_name", + "server.mac", + "server.registered_domain", + "server.top_level_domain", + "server.user.domain", + "server.user.email", + "server.user.full_name", + "server.user.group.domain", + "server.user.group.id", + "server.user.group.name", + "server.user.hash", + "server.user.id", + "server.user.name", + "service.ephemeral_id", + "service.id", + "service.name", + "service.node.name", + "service.state", + "service.type", + "service.version", + "source.address", + "source.as.organization.name", + "source.domain", + "source.geo.city_name", + "source.geo.continent_name", + "source.geo.country_iso_code", + "source.geo.country_name", + "source.geo.name", + "source.geo.region_iso_code", + "source.geo.region_name", + "source.mac", + "source.registered_domain", + "source.top_level_domain", + "source.user.domain", + "source.user.email", + "source.user.full_name", + "source.user.group.domain", + "source.user.group.id", + "source.user.group.name", + "source.user.hash", + "source.user.id", + "source.user.name", + "threat.framework", + "threat.tactic.id", + "threat.tactic.name", + "threat.tactic.reference", + "threat.technique.id", + "threat.technique.name", + "threat.technique.reference", + "tracing.trace.id", + "tracing.transaction.id", + "url.domain", + "url.extension", + "url.fragment", + "url.full", + "url.original", + "url.password", + "url.path", + "url.query", + "url.registered_domain", + "url.scheme", + "url.top_level_domain", + "url.username", + "user.domain", + "user.email", + "user.full_name", + "user.group.domain", + "user.group.id", + "user.group.name", + "user.hash", + "user.id", + "user.name", + "user_agent.device.name", + "user_agent.name", + "text", + "user_agent.original", + "user_agent.os.family", + "user_agent.os.full", + "user_agent.os.kernel", + "user_agent.os.name", + "user_agent.os.platform", + "user_agent.os.version", + "user_agent.version", + "text", + "agent.hostname", + "timeseries.instance", + "cloud.project.id", + "cloud.image.id", + "host.os.build", + "host.os.codename", + "kubernetes.pod.name", + "kubernetes.pod.uid", + "kubernetes.namespace", + "kubernetes.node.name", + "kubernetes.replicaset.name", + "kubernetes.deployment.name", + "kubernetes.statefulset.name", + "kubernetes.container.name", + "kubernetes.container.image", + "jolokia.agent.version", + "jolokia.agent.id", + "jolokia.server.product", + "jolokia.server.version", + "jolokia.server.vendor", + "jolokia.url", + "raw", + "file.origin", + "file.selinux.user", + "file.selinux.role", + "file.selinux.domain", + "file.selinux.level", + "user.audit.id", + "user.audit.name", + "user.effective.id", + "user.effective.name", + "user.effective.group.id", + "user.effective.group.name", + "user.filesystem.id", + "user.filesystem.name", + "user.filesystem.group.id", + "user.filesystem.group.name", + "user.saved.id", + "user.saved.name", + "user.saved.group.id", + "user.saved.group.name", + "user.selinux.user", + "user.selinux.role", + "user.selinux.domain", + "user.selinux.level", + "user.selinux.category", + "source.path", + "destination.path", + "auditd.message_type", + "auditd.session", + "auditd.result", + "auditd.summary.actor.primary", + "auditd.summary.actor.secondary", + "auditd.summary.object.type", + "auditd.summary.object.primary", + "auditd.summary.object.secondary", + "auditd.summary.how", + "auditd.paths.inode", + "auditd.paths.dev", + "auditd.paths.obj_user", + "auditd.paths.obj_role", + "auditd.paths.obj_domain", + "auditd.paths.obj_level", + "auditd.paths.objtype", + "auditd.paths.ouid", + "auditd.paths.rdev", + "auditd.paths.nametype", + "auditd.paths.ogid", + "auditd.paths.item", + "auditd.paths.mode", + "auditd.paths.name", + "auditd.data.action", + "auditd.data.minor", + "auditd.data.acct", + "auditd.data.addr", + "auditd.data.cipher", + "auditd.data.id", + "auditd.data.entries", + "auditd.data.kind", + "auditd.data.ksize", + "auditd.data.spid", + "auditd.data.arch", + "auditd.data.argc", + "auditd.data.major", + "auditd.data.unit", + "auditd.data.table", + "auditd.data.terminal", + "auditd.data.grantors", + "auditd.data.direction", + "auditd.data.op", + "auditd.data.tty", + "auditd.data.syscall", + "auditd.data.data", + "auditd.data.family", + "auditd.data.mac", + "auditd.data.pfs", + "auditd.data.items", + "auditd.data.a0", + "auditd.data.a1", + "auditd.data.a2", + "auditd.data.a3", + "auditd.data.hostname", + "auditd.data.lport", + "auditd.data.rport", + "auditd.data.exit", + "auditd.data.fp", + "auditd.data.laddr", + "auditd.data.sport", + "auditd.data.capability", + "auditd.data.nargs", + "auditd.data.new-enabled", + "auditd.data.audit_backlog_limit", + "auditd.data.dir", + "auditd.data.cap_pe", + "auditd.data.model", + "auditd.data.new_pp", + "auditd.data.old-enabled", + "auditd.data.oauid", + "auditd.data.old", + "auditd.data.banners", + "auditd.data.feature", + "auditd.data.vm-ctx", + "auditd.data.opid", + "auditd.data.seperms", + "auditd.data.seresult", + "auditd.data.new-rng", + "auditd.data.old-net", + "auditd.data.sigev_signo", + "auditd.data.ino", + "auditd.data.old_enforcing", + "auditd.data.old-vcpu", + "auditd.data.range", + "auditd.data.res", + "auditd.data.added", + "auditd.data.fam", + "auditd.data.nlnk-pid", + "auditd.data.subj", + "auditd.data.a[0-3]", + "auditd.data.cgroup", + "auditd.data.kernel", + "auditd.data.ocomm", + "auditd.data.new-net", + "auditd.data.permissive", + "auditd.data.class", + "auditd.data.compat", + "auditd.data.fi", + "auditd.data.changed", + "auditd.data.msg", + "auditd.data.dport", + "auditd.data.new-seuser", + "auditd.data.invalid_context", + "auditd.data.dmac", + "auditd.data.ipx-net", + "auditd.data.iuid", + "auditd.data.macproto", + "auditd.data.obj", + "auditd.data.ipid", + "auditd.data.new-fs", + "auditd.data.vm-pid", + "auditd.data.cap_pi", + "auditd.data.old-auid", + "auditd.data.oses", + "auditd.data.fd", + "auditd.data.igid", + "auditd.data.new-disk", + "auditd.data.parent", + "auditd.data.len", + "auditd.data.oflag", + "auditd.data.uuid", + "auditd.data.code", + "auditd.data.nlnk-grp", + "auditd.data.cap_fp", + "auditd.data.new-mem", + "auditd.data.seperm", + "auditd.data.enforcing", + "auditd.data.new-chardev", + "auditd.data.old-rng", + "auditd.data.outif", + "auditd.data.cmd", + "auditd.data.hook", + "auditd.data.new-level", + "auditd.data.sauid", + "auditd.data.sig", + "auditd.data.audit_backlog_wait_time", + "auditd.data.printer", + "auditd.data.old-mem", + "auditd.data.perm", + "auditd.data.old_pi", + "auditd.data.state", + "auditd.data.format", + "auditd.data.new_gid", + "auditd.data.tcontext", + "auditd.data.maj", + "auditd.data.watch", + "auditd.data.device", + "auditd.data.grp", + "auditd.data.bool", + "auditd.data.icmp_type", + "auditd.data.new_lock", + "auditd.data.old_prom", + "auditd.data.acl", + "auditd.data.ip", + "auditd.data.new_pi", + "auditd.data.default-context", + "auditd.data.inode_gid", + "auditd.data.new-log_passwd", + "auditd.data.new_pe", + "auditd.data.selected-context", + "auditd.data.cap_fver", + "auditd.data.file", + "auditd.data.net", + "auditd.data.virt", + "auditd.data.cap_pp", + "auditd.data.old-range", + "auditd.data.resrc", + "auditd.data.new-range", + "auditd.data.obj_gid", + "auditd.data.proto", + "auditd.data.old-disk", + "auditd.data.audit_failure", + "auditd.data.inif", + "auditd.data.vm", + "auditd.data.flags", + "auditd.data.nlnk-fam", + "auditd.data.old-fs", + "auditd.data.old-ses", + "auditd.data.seqno", + "auditd.data.fver", + "auditd.data.qbytes", + "auditd.data.seuser", + "auditd.data.cap_fe", + "auditd.data.new-vcpu", + "auditd.data.old-level", + "auditd.data.old_pp", + "auditd.data.daddr", + "auditd.data.old-role", + "auditd.data.ioctlcmd", + "auditd.data.smac", + "auditd.data.apparmor", + "auditd.data.fe", + "auditd.data.perm_mask", + "auditd.data.ses", + "auditd.data.cap_fi", + "auditd.data.obj_uid", + "auditd.data.reason", + "auditd.data.list", + "auditd.data.old_lock", + "auditd.data.bus", + "auditd.data.old_pe", + "auditd.data.new-role", + "auditd.data.prom", + "auditd.data.uri", + "auditd.data.audit_enabled", + "auditd.data.old-log_passwd", + "auditd.data.old-seuser", + "auditd.data.per", + "auditd.data.scontext", + "auditd.data.tclass", + "auditd.data.ver", + "auditd.data.new", + "auditd.data.val", + "auditd.data.img-ctx", + "auditd.data.old-chardev", + "auditd.data.old_val", + "auditd.data.success", + "auditd.data.inode_uid", + "auditd.data.removed", + "auditd.data.socket.port", + "auditd.data.socket.saddr", + "auditd.data.socket.addr", + "auditd.data.socket.family", + "auditd.data.socket.path", + "geoip.continent_name", + "geoip.city_name", + "geoip.region_name", + "geoip.country_iso_code", + "hash.blake2b_256", + "hash.blake2b_384", + "hash.blake2b_512", + "hash.md5", + "hash.sha1", + "hash.sha224", + "hash.sha256", + "hash.sha384", + "hash.sha3_224", + "hash.sha3_256", + "hash.sha3_384", + "hash.sha3_512", + "hash.sha512", + "hash.sha512_224", + "hash.sha512_256", + "hash.xxh64", + "event.origin", + "user.entity_id", + "user.terminal", + "process.entity_id", + "process.hash.blake2b_256", + "process.hash.blake2b_384", + "process.hash.blake2b_512", + "process.hash.sha224", + "process.hash.sha384", + "process.hash.sha3_224", + "process.hash.sha3_256", + "process.hash.sha3_384", + "process.hash.sha3_512", + "process.hash.sha512_224", + "process.hash.sha512_256", + "process.hash.xxh64", + "socket.entity_id", + "system.audit.host.timezone.name", + "system.audit.host.hostname", + "system.audit.host.id", + "system.audit.host.architecture", + "system.audit.host.mac", + "system.audit.host.os.codename", + "system.audit.host.os.platform", + "system.audit.host.os.name", + "system.audit.host.os.family", + "system.audit.host.os.version", + "system.audit.host.os.kernel", + "system.audit.package.entity_id", + "system.audit.package.name", + "system.audit.package.version", + "system.audit.package.release", + "system.audit.package.arch", + "system.audit.package.license", + "system.audit.package.summary", + "system.audit.package.url", + "system.audit.user.name", + "system.audit.user.uid", + "system.audit.user.gid", + "system.audit.user.dir", + "system.audit.user.shell", + "system.audit.user.user_information", + "system.audit.user.password.type", + "fields.*" + ] + }, + "refresh_interval": "5s" + } + } + } +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 5b13c8bd37aed..ae55508cee886 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5921,9 +5921,9 @@ acorn@^6.2.1: integrity sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA== acorn@^7.0.0, acorn@^7.1.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.1.0.tgz#949d36f2c292535da602283586c2477c57eb2d6c" - integrity sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ== + version "7.1.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.1.1.tgz#e35668de0b402f359de515c5482a1ab9f89a69bf" + integrity sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg== address@1.1.0: version "1.1.0"