Merge remote-tracking branch 'upstream/master' into fix/flaky-tsvb-chart-test
17
.ci/jobs.yml
|
@ -1,20 +1,33 @@
|
|||
JOB:
|
||||
- intake
|
||||
- firefoxSmoke
|
||||
- kibana-intake
|
||||
- x-pack-intake
|
||||
- kibana-firefoxSmoke
|
||||
- kibana-ciGroup1
|
||||
- kibana-ciGroup2
|
||||
- kibana-ciGroup3
|
||||
- kibana-ciGroup4
|
||||
- kibana-ciGroup5
|
||||
- kibana-ciGroup6
|
||||
- kibana-ciGroup7
|
||||
- kibana-ciGroup8
|
||||
- kibana-ciGroup9
|
||||
- kibana-ciGroup10
|
||||
- kibana-ciGroup11
|
||||
- kibana-ciGroup12
|
||||
# - kibana-visualRegression
|
||||
|
||||
# make sure all x-pack-ciGroups are listed in test/scripts/jenkins_xpack_ci_group.sh
|
||||
- x-pack-firefoxSmoke
|
||||
- x-pack-ciGroup1
|
||||
- x-pack-ciGroup2
|
||||
- x-pack-ciGroup3
|
||||
- x-pack-ciGroup4
|
||||
- x-pack-ciGroup5
|
||||
- x-pack-ciGroup6
|
||||
- x-pack-ciGroup7
|
||||
- x-pack-ciGroup8
|
||||
- x-pack-ciGroup9
|
||||
- x-pack-ciGroup10
|
||||
# - x-pack-visualRegression
|
||||
|
||||
# `~` is yaml for `null`
|
||||
|
|
10
.ci/run.sh
|
@ -11,7 +11,7 @@ source src/dev/ci_setup/setup.sh
|
|||
source src/dev/ci_setup/checkout_sibling_es.sh
|
||||
|
||||
case "$JOB" in
|
||||
intake)
|
||||
kibana-intake)
|
||||
./test/scripts/jenkins_unit.sh
|
||||
;;
|
||||
kibana-ciGroup*)
|
||||
|
@ -21,9 +21,12 @@ kibana-ciGroup*)
|
|||
kibana-visualRegression*)
|
||||
./test/scripts/jenkins_visual_regression.sh
|
||||
;;
|
||||
firefoxSmoke*)
|
||||
kibana-firefoxSmoke*)
|
||||
./test/scripts/jenkins_firefox_smoke.sh
|
||||
;;
|
||||
x-pack-intake)
|
||||
./test/scripts/jenkins_xpack.sh
|
||||
;;
|
||||
x-pack-ciGroup*)
|
||||
export CI_GROUP="${JOB##x-pack-ciGroup}"
|
||||
./test/scripts/jenkins_xpack_ci_group.sh
|
||||
|
@ -31,6 +34,9 @@ x-pack-ciGroup*)
|
|||
x-pack-visualRegression*)
|
||||
./test/scripts/jenkins_xpack_visual_regression.sh
|
||||
;;
|
||||
x-pack-firefoxSmoke*)
|
||||
./test/scripts/jenkins_xpack_firefox_smoke.sh
|
||||
;;
|
||||
*)
|
||||
echo "JOB '$JOB' is not implemented."
|
||||
exit 1
|
||||
|
|
32
Jenkinsfile
vendored
|
@ -6,8 +6,8 @@ stage("Kibana Pipeline") { // This stage is just here to help the BlueOcean UI a
|
|||
ansiColor('xterm') {
|
||||
catchError {
|
||||
parallel([
|
||||
'intake-agent': legacyJobRunner('intake'),
|
||||
'firefox-smoke-agent': legacyJobRunner('firefoxSmoke'),
|
||||
'kibana-intake-agent': legacyJobRunner('kibana-intake'),
|
||||
'x-pack-intake-agent': legacyJobRunner('x-pack-intake'),
|
||||
'kibana-oss-agent': withWorkers('kibana-oss-tests', { buildOss() }, [
|
||||
'oss-ciGroup1': getOssCiGroupWorker(1),
|
||||
'oss-ciGroup2': getOssCiGroupWorker(2),
|
||||
|
@ -15,14 +15,14 @@ stage("Kibana Pipeline") { // This stage is just here to help the BlueOcean UI a
|
|||
'oss-ciGroup4': getOssCiGroupWorker(4),
|
||||
'oss-ciGroup5': getOssCiGroupWorker(5),
|
||||
'oss-ciGroup6': getOssCiGroupWorker(6),
|
||||
// 'oss-ciGroup7': getOssCiGroupWorker(7),
|
||||
// 'oss-ciGroup8': getOssCiGroupWorker(8),
|
||||
// 'oss-ciGroup9': getOssCiGroupWorker(9),
|
||||
// 'oss-ciGroup10': getOssCiGroupWorker(10),
|
||||
// 'oss-ciGroup11': getOssCiGroupWorker(11),
|
||||
// 'oss-ciGroup12': getOssCiGroupWorker(12),
|
||||
'oss-ciGroup7': getOssCiGroupWorker(7),
|
||||
'oss-ciGroup8': getOssCiGroupWorker(8),
|
||||
'oss-ciGroup9': getOssCiGroupWorker(9),
|
||||
'oss-ciGroup10': getOssCiGroupWorker(10),
|
||||
'oss-ciGroup11': getOssCiGroupWorker(11),
|
||||
'oss-ciGroup12': getOssCiGroupWorker(12),
|
||||
'oss-firefoxSmoke': getPostBuildWorker('firefoxSmoke', { runbld './test/scripts/jenkins_firefox_smoke.sh' }),
|
||||
// 'oss-visualRegression': getPostBuildWorker('visualRegression', { runbld './test/scripts/jenkins_visual_regression.sh' }),
|
||||
// 'oss-firefoxSmoke': getPostBuildWorker('firefoxSmoke', { runbld './test/scripts/jenkins_firefox_smoke.sh' }),
|
||||
]),
|
||||
'kibana-xpack-agent': withWorkers('kibana-xpack-tests', { buildXpack() }, [
|
||||
'xpack-ciGroup1': getXpackCiGroupWorker(1),
|
||||
|
@ -30,12 +30,12 @@ stage("Kibana Pipeline") { // This stage is just here to help the BlueOcean UI a
|
|||
'xpack-ciGroup3': getXpackCiGroupWorker(3),
|
||||
'xpack-ciGroup4': getXpackCiGroupWorker(4),
|
||||
'xpack-ciGroup5': getXpackCiGroupWorker(5),
|
||||
// 'xpack-ciGroup6': getXpackCiGroupWorker(6),
|
||||
// 'xpack-ciGroup7': getXpackCiGroupWorker(7),
|
||||
// 'xpack-ciGroup8': getXpackCiGroupWorker(8),
|
||||
// 'xpack-ciGroup9': getXpackCiGroupWorker(9),
|
||||
// 'xpack-ciGroup10': getXpackCiGroupWorker(10),
|
||||
// 'xpack-firefoxSmoke': getPostBuildWorker('xpack-firefoxSmoke', { runbld './test/scripts/jenkins_xpack_firefox_smoke.sh' }),
|
||||
'xpack-ciGroup6': getXpackCiGroupWorker(6),
|
||||
'xpack-ciGroup7': getXpackCiGroupWorker(7),
|
||||
'xpack-ciGroup8': getXpackCiGroupWorker(8),
|
||||
'xpack-ciGroup9': getXpackCiGroupWorker(9),
|
||||
'xpack-ciGroup10': getXpackCiGroupWorker(10),
|
||||
'xpack-firefoxSmoke': getPostBuildWorker('xpack-firefoxSmoke', { runbld './test/scripts/jenkins_xpack_firefox_smoke.sh' }),
|
||||
// 'xpack-visualRegression': getPostBuildWorker('xpack-visualRegression', { runbld './test/scripts/jenkins_xpack_visual_regression.sh' }),
|
||||
]),
|
||||
])
|
||||
|
@ -109,6 +109,7 @@ def getPostBuildWorker(name, closure) {
|
|||
"TEST_KIBANA_URL=http://elastic:changeme@localhost:${kibanaPort}",
|
||||
"TEST_ES_URL=http://elastic:changeme@localhost:${esPort}",
|
||||
"TEST_ES_TRANSPORT_PORT=${esTransportPort}",
|
||||
"IS_PIPELINE_JOB=1",
|
||||
]) {
|
||||
closure()
|
||||
}
|
||||
|
@ -171,7 +172,6 @@ def jobRunner(label, closure) {
|
|||
|
||||
withEnv([
|
||||
"CI=true",
|
||||
"IS_PIPELINE_JOB=1",
|
||||
"HOME=${env.JENKINS_HOME}",
|
||||
"PR_SOURCE_BRANCH=${env.ghprbSourceBranch ?: ''}",
|
||||
"PR_TARGET_BRANCH=${env.ghprbTargetBranch ?: ''}",
|
||||
|
|
539
STYLEGUIDE.md
|
@ -3,25 +3,536 @@
|
|||
This guide applies to all development within the Kibana project and is
|
||||
recommended for the development of all Kibana plugins.
|
||||
|
||||
- [JavaScript](style_guides/js_style_guide.md)
|
||||
- [Angular](style_guides/angular_style_guide.md)
|
||||
- [React](style_guides/react_style_guide.md)
|
||||
- [SASS](https://elastic.github.io/eui/#/guidelines/sass)
|
||||
- [HTML](style_guides/html_style_guide.md)
|
||||
- [API](style_guides/api_style_guide.md)
|
||||
- [Architecture](style_guides/architecture_style_guide.md)
|
||||
- [Accessibility](style_guides/accessibility_guide.md)
|
||||
Besides the content in this style guide, the following style guides may also apply
|
||||
to all development within the Kibana project. Please make sure to also read them:
|
||||
|
||||
## Filenames
|
||||
- [Accessibility style guide](style_guides/accessibility_guide.md)
|
||||
- [SASS style guide](https://elastic.github.io/eui/#/guidelines/sass)
|
||||
|
||||
## General
|
||||
|
||||
### Filenames
|
||||
|
||||
All filenames should use `snake_case`.
|
||||
|
||||
**Right:** `src/kibana/index_patterns/index_pattern.js`
|
||||
|
||||
**Wrong:** `src/kibana/IndexPatterns/IndexPattern.js`
|
||||
|
||||
### Do not comment out code
|
||||
|
||||
We use a version management system. If a line of code is no longer needed,
|
||||
remove it, don't simply comment it out.
|
||||
|
||||
### Prettier and Linting
|
||||
|
||||
We are gradually moving the Kibana code base over to Prettier. All TypeScript code
|
||||
and some JavaScript code (check `.eslintrc.js`) is using Prettier to format code. You
|
||||
can run `node script/eslint --fix` to fix linting issues and apply Prettier formatting.
|
||||
We recommend you to enable running ESLint via your IDE.
|
||||
|
||||
Whenever possible we are trying to use Prettier and linting over written style guide rules.
|
||||
Consider every linting rule and every Prettier rule to be also part of our style guide
|
||||
and disable them only in exceptional cases and ideally leave a comment why they are
|
||||
disabled at that specific place.
|
||||
|
||||
## HTML
|
||||
|
||||
This part contains style guide rules around general (framework agnostic) HTML usage.
|
||||
|
||||
### Camel case `id` and `data-test-subj`
|
||||
|
||||
Use camel case for the values of attributes such as `id` and `data-test-subj` selectors.
|
||||
|
||||
```html
|
||||
<button
|
||||
id="veryImportantButton"
|
||||
data-test-subj="clickMeButton"
|
||||
>
|
||||
Click me
|
||||
</button>
|
||||
```
|
||||
|
||||
The only exception is in cases where you're dynamically creating the value, and you need to use
|
||||
hyphens as delimiters:
|
||||
|
||||
```jsx
|
||||
buttons.map(btn => (
|
||||
<button
|
||||
id={`veryImportantButton-${btn.id}`}
|
||||
data-test-subj={`clickMeButton-${btn.id}`}
|
||||
>
|
||||
{btn.label}
|
||||
</button>
|
||||
)
|
||||
```
|
||||
|
||||
### Capitalization in HTML and CSS should always match
|
||||
|
||||
It's important that when you write CSS/SASS selectors using classes, IDs, and attributes
|
||||
(keeping in mind that we should _never_ use IDs and attributes in our selectors), that the
|
||||
capitalization in the CSS matches that used in the HTML. HTML and CSS follow different case sensitivity rules, and we can avoid subtle gotchas by ensuring we use the
|
||||
same capitalization in both of them.
|
||||
|
||||
## API endpoints
|
||||
|
||||
The following style guide rules are targeting development of server side API endpoints.
|
||||
|
||||
### Paths
|
||||
|
||||
API routes must start with the `/api/` path segment, and should be followed by the plugin id if applicable:
|
||||
|
||||
**Right:** `/api/marvel/nodes`
|
||||
|
||||
**Wrong:** `/marvel/api/nodes`
|
||||
|
||||
### snake_case
|
||||
|
||||
Kibana uses `snake_case` for the entire API, just like Elasticsearch. All urls, paths, query string parameters, values, and bodies should be `snake_case` formatted.
|
||||
|
||||
*Right:*
|
||||
- `src/kibana/index_patterns/index_pattern.js`
|
||||
```
|
||||
POST /api/kibana/index_patterns
|
||||
{
|
||||
"id": "...",
|
||||
"time_field_name": "...",
|
||||
"fields": [
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
*Wrong:*
|
||||
- `src/kibana/IndexPatterns/IndexPattern.js`
|
||||
## TypeScript/JavaScript
|
||||
|
||||
## TypeScript vs JavaScript
|
||||
The following style guide rules apply for working with TypeScript/JavaScript files.
|
||||
|
||||
Whenever possible, write code in TypeScript instead of javascript, especially if it's new code. Check out [TYPESCRIPT.md](TYPESCRIPT.md) for help with this process.
|
||||
### TypeScript vs. JavaScript
|
||||
|
||||
Whenever possible, write code in TypeScript instead of JavaScript, especially if it's new code.
|
||||
Check out [TYPESCRIPT.md](TYPESCRIPT.md) for help with this process.
|
||||
|
||||
### Prefer modern JavaScript/TypeScript syntax
|
||||
|
||||
You should prefer modern language features in a lot of cases, e.g.:
|
||||
|
||||
* Prefer `class` over `prototype` inheritance
|
||||
* Prefer arrow function over function expressions
|
||||
* Prefer arrow function over storing `this` (no `const self = this;`)
|
||||
* Prefer template strings over string concatenation
|
||||
* Prefer the spread operator for copying arrays (`[...arr]`) over `arr.slice()`
|
||||
|
||||
### Avoid mutability and state
|
||||
|
||||
Wherever possible, do not rely on mutable state. This means you should not
|
||||
reassign variables, modify object properties, or push values to arrays.
|
||||
Instead, create new variables, and shallow copies of objects and arrays:
|
||||
|
||||
```js
|
||||
// good
|
||||
function addBar(foos, foo) {
|
||||
const newFoo = {...foo, name: 'bar'};
|
||||
return [...foos, newFoo];
|
||||
}
|
||||
|
||||
// bad
|
||||
function addBar(foos, foo) {
|
||||
foo.name = 'bar';
|
||||
foos.push(foo);
|
||||
}
|
||||
```
|
||||
|
||||
### Return/throw early from functions
|
||||
|
||||
To avoid deep nesting of if-statements, always return a function's value as early
|
||||
as possible. And where possible, do any assertions first:
|
||||
|
||||
```js
|
||||
// good
|
||||
function doStuff(val) {
|
||||
if (val > 100) {
|
||||
throw new Error('Too big');
|
||||
}
|
||||
|
||||
if (val < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ... stuff
|
||||
}
|
||||
|
||||
// bad
|
||||
function doStuff(val) {
|
||||
if (val >= 0) {
|
||||
if (val < 100) {
|
||||
// ... stuff
|
||||
} else {
|
||||
throw new Error('Too big');
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Use object destructuring
|
||||
|
||||
This helps avoid temporary references and helps prevent typo-related bugs.
|
||||
|
||||
```js
|
||||
// best
|
||||
function fullName({ first, last }) {
|
||||
return `${first} ${last}`;
|
||||
}
|
||||
|
||||
// good
|
||||
function fullName(user) {
|
||||
const { first, last } = user;
|
||||
return `${first} ${last}`;
|
||||
}
|
||||
|
||||
// bad
|
||||
function fullName(user) {
|
||||
const first = user.first;
|
||||
const last = user.last;
|
||||
return `${first} ${last}`;
|
||||
}
|
||||
```
|
||||
|
||||
### Use array destructuring
|
||||
|
||||
Directly accessing array values via index should be avoided, but if it is
|
||||
necessary, use array destructuring:
|
||||
|
||||
```js
|
||||
const arr = [1, 2, 3];
|
||||
|
||||
// good
|
||||
const [first, second] = arr;
|
||||
|
||||
// bad
|
||||
const first = arr[0];
|
||||
const second = arr[1];
|
||||
```
|
||||
|
||||
### Magic numbers/strings
|
||||
|
||||
These are numbers (or other values) simply used in line in your code. *Do not
|
||||
use these*, give them a variable name so they can be understood and changed
|
||||
easily.
|
||||
|
||||
```js
|
||||
// good
|
||||
const minWidth = 300;
|
||||
|
||||
if (width < minWidth) {
|
||||
...
|
||||
}
|
||||
|
||||
// bad
|
||||
if (width < 300) {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
### Modules
|
||||
|
||||
Module dependencies should be written using native ES2015 syntax wherever
|
||||
possible (which is almost everywhere):
|
||||
|
||||
```js
|
||||
// good
|
||||
import { mapValues } from 'lodash';
|
||||
export mapValues;
|
||||
|
||||
// bad
|
||||
const _ = require('lodash');
|
||||
module.exports = _.mapValues;
|
||||
|
||||
// worse
|
||||
define(['lodash'], function (_) {
|
||||
...
|
||||
});
|
||||
```
|
||||
|
||||
In those extremely rare cases where you're writing server-side JavaScript in a
|
||||
file that does not pass run through webpack, then use CommonJS modules.
|
||||
|
||||
In those even rarer cases where you're writing client-side code that does not
|
||||
run through webpack, then do not use a module loader at all.
|
||||
|
||||
#### Import only top-level modules
|
||||
|
||||
The files inside a module are implementation details of that module. They
|
||||
should never be imported directly. Instead, you must only import the top-level
|
||||
API that's exported by the module itself.
|
||||
|
||||
Without a clear mechanism in place in JS to encapsulate protected code, we make
|
||||
a broad assumption that anything beyond the root of a module is an
|
||||
implementation detail of that module.
|
||||
|
||||
On the other hand, a module should be able to import parent and sibling
|
||||
modules.
|
||||
|
||||
```js
|
||||
// good
|
||||
import foo from 'foo';
|
||||
import child from './child';
|
||||
import parent from '../';
|
||||
import ancestor from '../../../';
|
||||
import sibling from '../foo';
|
||||
|
||||
// bad
|
||||
import inFoo from 'foo/child';
|
||||
import inSibling from '../foo/child';
|
||||
```
|
||||
|
||||
### Global definitions
|
||||
|
||||
Don't do this. Everything should be wrapped in a module that can be depended on
|
||||
by other modules. Even things as simple as a single value should be a module.
|
||||
|
||||
|
||||
### Only use ternary operators for small, simple code
|
||||
|
||||
And *never* use multiple ternaries together, because they make it more
|
||||
difficult to reason about how different values flow through the conditions
|
||||
involved. Instead, structure the logic for maximum readability.
|
||||
|
||||
```js
|
||||
// good, a situation where only 1 ternary is needed
|
||||
const foo = (a === b) ? 1 : 2;
|
||||
|
||||
// bad
|
||||
const foo = (a === b) ? 1 : (a === c) ? 2 : 3;
|
||||
```
|
||||
|
||||
### Use descriptive conditions
|
||||
|
||||
Any non-trivial conditions should be converted to functions or assigned to
|
||||
descriptively named variables. By breaking up logic into smaller,
|
||||
self-contained blocks, it becomes easier to reason about the higher-level
|
||||
logic. Additionally, these blocks become good candidates for extraction into
|
||||
their own modules, with unit-tests.
|
||||
|
||||
```js
|
||||
// best
|
||||
function isShape(thing) {
|
||||
return thing instanceof Shape;
|
||||
}
|
||||
function notSquare(thing) {
|
||||
return !(thing instanceof Square);
|
||||
}
|
||||
if (isShape(thing) && notSquare(thing)) {
|
||||
...
|
||||
}
|
||||
|
||||
// good
|
||||
const isShape = thing instanceof Shape;
|
||||
const notSquare = !(thing instanceof Square);
|
||||
if (isShape && notSquare) {
|
||||
...
|
||||
}
|
||||
|
||||
// bad
|
||||
if (thing instanceof Shape && !(thing instanceof Square)) {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
### Name regular expressions
|
||||
|
||||
```js
|
||||
// good
|
||||
const validPassword = /^(?=.*\d).{4,}$/;
|
||||
|
||||
if (password.length >= 4 && validPassword.test(password)) {
|
||||
console.log('password is valid');
|
||||
}
|
||||
|
||||
// bad
|
||||
if (password.length >= 4 && /^(?=.*\d).{4,}$/.test(password)) {
|
||||
console.log('losing');
|
||||
}
|
||||
```
|
||||
|
||||
### Write small functions
|
||||
|
||||
Keep your functions short. A good function fits on a slide that the people in
|
||||
the last row of a big room can comfortably read. So don't count on them having
|
||||
perfect vision and limit yourself to ~15 lines of code per function.
|
||||
|
||||
### Use "rest" syntax rather than built-in `arguments`
|
||||
|
||||
For expressiveness sake, and so you can be mix dynamic and explicit arguments.
|
||||
|
||||
```js
|
||||
// good
|
||||
function something(foo, ...args) {
|
||||
...
|
||||
}
|
||||
|
||||
// bad
|
||||
function something(foo) {
|
||||
const args = Array.from(arguments).slice(1);
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
### Default argument syntax
|
||||
|
||||
Always use the default argument syntax for optional arguments.
|
||||
|
||||
```js
|
||||
// good
|
||||
function foo(options = {}) {
|
||||
...
|
||||
}
|
||||
|
||||
// bad
|
||||
function foo(options) {
|
||||
if (typeof options === 'undefined') {
|
||||
options = {};
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
And put your optional arguments at the end.
|
||||
|
||||
```js
|
||||
// good
|
||||
function foo(bar, options = {}) {
|
||||
...
|
||||
}
|
||||
|
||||
// bad
|
||||
function foo(options = {}, bar) {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
### Use thunks to create closures, where possible
|
||||
|
||||
For trivial examples (like the one that follows), thunks will seem like
|
||||
overkill, but they encourage isolating the implementation details of a closure
|
||||
from the business logic of the calling code.
|
||||
|
||||
```js
|
||||
// good
|
||||
function connectHandler(client, callback) {
|
||||
return () => client.connect(callback);
|
||||
}
|
||||
setTimeout(connectHandler(client, afterConnect), 1000);
|
||||
|
||||
// not as good
|
||||
setTimeout(() => {
|
||||
client.connect(afterConnect);
|
||||
}, 1000);
|
||||
|
||||
// bad
|
||||
setTimeout(() => {
|
||||
client.connect(() => {
|
||||
...
|
||||
});
|
||||
}, 1000);
|
||||
```
|
||||
|
||||
### Use slashes for comments
|
||||
|
||||
Use slashes for both single line and multi line comments. Try to write
|
||||
comments that explain higher level mechanisms or clarify difficult
|
||||
segments of your code. *Don't use comments to restate trivial things*.
|
||||
|
||||
*Exception:* Comment blocks describing a function and its arguments
|
||||
(docblock) should start with `/**`, contain a single `*` at the beginning of
|
||||
each line, and end with `*/`.
|
||||
|
||||
|
||||
```js
|
||||
// good
|
||||
|
||||
// 'ID_SOMETHING=VALUE' -> ['ID_SOMETHING=VALUE', 'SOMETHING', 'VALUE']
|
||||
const matches = item.match(/ID_([^\n]+)=([^\n]+)/));
|
||||
|
||||
/**
|
||||
* Fetches a user from...
|
||||
* @param {string} id - id of the user
|
||||
* @return {Promise}
|
||||
*/
|
||||
function loadUser(id) {
|
||||
// This function has a nasty side effect where a failure to increment a
|
||||
// redis counter used for statistics will cause an exception. This needs
|
||||
// to be fixed in a later iteration.
|
||||
|
||||
...
|
||||
}
|
||||
|
||||
const isSessionValid = (session.expires < Date.now());
|
||||
if (isSessionValid) {
|
||||
...
|
||||
}
|
||||
|
||||
// bad
|
||||
|
||||
// Execute a regex
|
||||
const matches = item.match(/ID_([^\n]+)=([^\n]+)/));
|
||||
|
||||
// Usage: loadUser(5, function() { ... })
|
||||
function loadUser(id, cb) {
|
||||
// ...
|
||||
}
|
||||
|
||||
// Check if the session is valid
|
||||
const isSessionValid = (session.expires < Date.now());
|
||||
// If the session is valid
|
||||
if (isSessionValid) {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
### Getters and Setters
|
||||
|
||||
Feel free to use getters that are free from [side effects][sideeffect], like
|
||||
providing a length property for a collection class.
|
||||
|
||||
Do not use setters, they cause more problems than they can solve.
|
||||
|
||||
[sideeffect]: http://en.wikipedia.org/wiki/Side_effect_(computer_science)
|
||||
|
||||
## React
|
||||
|
||||
The following style guide rules are specific for working with the React framework.
|
||||
|
||||
### Prefer reactDirective over react-component
|
||||
|
||||
When using `ngReact` to embed your react components inside Angular HTML, prefer the
|
||||
`reactDirective` service over the `react-component` directive.
|
||||
You can read more about these two ngReact methods [here](https://github.com/ngReact/ngReact#features).
|
||||
|
||||
Using `react-component` means adding a bunch of components into angular, while `reactDirective` keeps them isolated, and is also a more succinct syntax.
|
||||
|
||||
**Good:**
|
||||
```html
|
||||
<hello-component fname="person.fname" lname="person.lname" watch-depth="reference"></hello-component>
|
||||
```
|
||||
|
||||
**Bad:**
|
||||
```html
|
||||
<react-component name="HelloComponent" props="person" watch-depth="reference" />
|
||||
```
|
||||
|
||||
### Action function names and prop function names
|
||||
|
||||
Name action functions in the form of a strong verb and passed properties in the form of on<Subject><Change>. E.g:
|
||||
|
||||
```jsx
|
||||
<sort-button onClick={action.sort}/>
|
||||
<pagerButton onPageNext={action.turnToNextPage} />
|
||||
```
|
||||
|
||||
## Attribution
|
||||
|
||||
Parts of the JavaScript style guide were initially forked from the
|
||||
[node style guide](https://github.com/felixge/node-style-guide) created by [Felix Geisendörfer](http://felixge.de/) which is
|
||||
licensed under the [CC BY-SA 3.0](http://creativecommons.org/licenses/by-sa/3.0/)
|
||||
license.
|
||||
|
|
|
@ -12,7 +12,7 @@ You can think of transactions as the highest level of work you’re measuring wi
|
|||
As an example, a transaction could be a request to your server, a batch job, or a custom transaction type.
|
||||
* {apm-overview-ref-v}/errors.html[*Errors*] contain information about the original exception that occurred or about a log created when the exception occurred.
|
||||
|
||||
Each of these information types have a specific page associated with them in the APM UI.
|
||||
Each of these information types have a specific page associated with them in the APM app.
|
||||
These various pages display the captured data in curated charts and tables that allow you to easily compare and debug your applications.
|
||||
|
||||
For example, you can see information about response times, requests per minute, and status codes per endpoint.
|
||||
|
|
|
@ -1,21 +1,29 @@
|
|||
[[filters]]
|
||||
=== Filters
|
||||
|
||||
Global filters are ways you can filter your data within the APM UI.
|
||||
APM provides two different ways you can filter your data within the APM App:
|
||||
|
||||
* <<global-filters>>
|
||||
* <<contextual-filters>>
|
||||
|
||||
[[global-filters]]
|
||||
==== Global filters
|
||||
|
||||
Global filters are ways you can filter any and all data across the APM app.
|
||||
They are available in the Services, Transactions, Errors, Metrics, and Traces views,
|
||||
and any filter applied will persist as you move between pages.
|
||||
|
||||
[role="screenshot"]
|
||||
image::apm/images/global-filters.png[Global filters available in the APM UI in Kibana]
|
||||
image::apm/images/global-filters.png[Global filters available in the APM app in Kibana]
|
||||
|
||||
[float]
|
||||
==== Global time range
|
||||
===== Global time range
|
||||
|
||||
The <<set-time-filter,global time range filter>> in {kib} restricts APM data to a specific time period.
|
||||
|
||||
[float]
|
||||
[[query-bar]]
|
||||
==== Query bar
|
||||
===== Query bar
|
||||
|
||||
The query bar is a powerful data query feature.
|
||||
Similar to the query bar in {kibana-ref}/discover.html[Discover],
|
||||
|
@ -27,7 +35,7 @@ See <<query-bar,advanced queries>> for more information and sample queries.
|
|||
|
||||
[float]
|
||||
[[environment-selector]]
|
||||
==== Service environment filter
|
||||
===== Service environment filter
|
||||
|
||||
The environment selector is a global filter for `service.environment`.
|
||||
It allows you to view only relevant data, and is especially useful for separating development from production environments.
|
||||
|
@ -46,3 +54,19 @@ v|*Go:* {apm-go-ref}/configuration.html#config-environment[`ELASTIC_APM_ENVIRONM
|
|||
*Ruby:* {apm-ruby-ref}/configuration.html#config-environment[`environment`]
|
||||
*Real User Monitoring:* {apm-rum-ref}/configuration.html#environment[`environment`]
|
||||
|===
|
||||
|
||||
[[contextual-filters]]
|
||||
==== Contextual filters
|
||||
|
||||
Local filters are ways you can filter your specific APM data on each individual page.
|
||||
The filters shown are relevant to your data, and will persist between pages,
|
||||
but only where they are applicable -- they are typically most useful in their original context.
|
||||
As an example, if you select a host on the Services overview, then select a transaction group,
|
||||
the host filter will still be applied.
|
||||
|
||||
These filters are very useful for quickly and easily removing noise from your data.
|
||||
With just a click, you can filter your transactions by the transaction result,
|
||||
host, container ID, and more.
|
||||
|
||||
[role="screenshot"]
|
||||
image::apm/images/local-filter.png[Local filters available in the APM app in Kibana]
|
|
@ -15,7 +15,7 @@ To set up the correct index pattern,
|
|||
simply click *Load Kibana objects* at the bottom of the Setup Instructions.
|
||||
|
||||
After you install an Elastic APM agent library in your application,
|
||||
the application automatically appears in the APM UI in {kib}.
|
||||
the application automatically appears in the APM app in {kib}.
|
||||
No further configuration is required.
|
||||
|
||||
[role="screenshot"]
|
||||
|
|
Before Width: | Height: | Size: 311 KiB After Width: | Height: | Size: 172 KiB |
Before Width: | Height: | Size: 402 KiB After Width: | Height: | Size: 377 KiB |
Before Width: | Height: | Size: 360 KiB After Width: | Height: | Size: 421 KiB |
BIN
docs/apm/images/apm-geo-ui.jpg
Normal file
After Width: | Height: | Size: 176 KiB |
Before Width: | Height: | Size: 448 KiB After Width: | Height: | Size: 476 KiB |
Before Width: | Height: | Size: 305 KiB After Width: | Height: | Size: 309 KiB |
Before Width: | Height: | Size: 479 KiB After Width: | Height: | Size: 401 KiB |
Before Width: | Height: | Size: 458 KiB After Width: | Height: | Size: 471 KiB |
Before Width: | Height: | Size: 532 KiB After Width: | Height: | Size: 460 KiB |
Before Width: | Height: | Size: 338 KiB After Width: | Height: | Size: 346 KiB |
Before Width: | Height: | Size: 596 KiB After Width: | Height: | Size: 506 KiB |
BIN
docs/apm/images/local-filter.png
Normal file
After Width: | Height: | Size: 247 KiB |
|
@ -7,7 +7,7 @@
|
|||
Elastic Application Performance Monitoring (APM) automatically collects in-depth
|
||||
performance metrics and errors from inside your applications.
|
||||
|
||||
The **APM** page in {kib} is provided with the basic license. It
|
||||
The **APM** app in {kib} is provided with the basic license. It
|
||||
enables developers to drill down into the performance data for their applications
|
||||
and quickly locate the performance bottlenecks.
|
||||
|
||||
|
|
|
@ -52,6 +52,20 @@ For further details, including troubleshooting and custom implementation instruc
|
|||
refer to the documentation for each {apm-agents-ref}[APM Agent] you've implemented.
|
||||
====
|
||||
|
||||
[[rum-transaction-overview]]
|
||||
==== RUM Transaction overview
|
||||
|
||||
The transaction overview page is customized for the JavaScript RUM Agent.
|
||||
This page highlights things like *page load times*, *transactions per minute*, and even the *average page load duration distribution by country*.
|
||||
|
||||
[role="screenshot"]
|
||||
image::apm/images/apm-geo-ui.jpg[average page load duration distribution]
|
||||
|
||||
This data is available due to the geo-ip and user agent pipelines being enabled by default,
|
||||
which allows for the capture of geo-location and user agent data.
|
||||
These visualizations make it easy for you to visualize performance information about your
|
||||
end users' experience based on their location.
|
||||
|
||||
[[transaction-details]]
|
||||
==== Transaction details
|
||||
|
||||
|
|
|
@ -75,7 +75,7 @@ Reviewers are not simply evaluating the code itself, they are also evaluating th
|
|||
|
||||
Having a relatively consistent codebase is an important part of us building a sustainable project. With dozens of active contributors at any given time, we rely on automation to help ensure consistency - we enforce a comprehensive set of linting rules through CI. We're also rolling out prettier to make this even more automatic.
|
||||
|
||||
For things that can't be easily automated, we maintain various link:https://github.com/elastic/kibana/tree/master/style_guides[styleguides] that authors should adhere to and reviewers should keep in mind when they review a pull request.
|
||||
For things that can't be easily automated, we maintain a link:https://github.com/elastic/kibana/blob/master/STYLEGUIDE.md[style guide] that authors should adhere to and reviewers should keep in mind when they review a pull request.
|
||||
|
||||
Beyond that, we're into subjective territory. Statements like "this isn't very readable" are hardly helpful since they can't be qualified, but that doesn't mean a reviewer should outright ignore code that is hard to understand due to how it is written. There isn't one definitively "best" way to write any particular code, so pursuing such shouldn't be our goal. Instead, reviewers and authors alike must accept that there are likely many different appropriate ways to accomplish the same thing with code, and so long as the contribution is utilizing one of those ways, then we're in good shape.
|
||||
|
||||
|
|
|
@ -7,5 +7,5 @@
|
|||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
request?: Request;
|
||||
request: Request;
|
||||
```
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [HttpErrorResponse](./kibana-plugin-public.httperrorresponse.md) > [body](./kibana-plugin-public.httperrorresponse.body.md)
|
||||
|
||||
## HttpErrorResponse.body property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
body?: HttpBody;
|
||||
```
|
|
@ -8,12 +8,15 @@
|
|||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface HttpErrorResponse extends HttpResponse
|
||||
export interface HttpErrorResponse
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [body](./kibana-plugin-public.httperrorresponse.body.md) | <code>HttpBody</code> | |
|
||||
| [error](./kibana-plugin-public.httperrorresponse.error.md) | <code>Error | HttpFetchError</code> | |
|
||||
| [request](./kibana-plugin-public.httperrorresponse.request.md) | <code>Request</code> | |
|
||||
| [response](./kibana-plugin-public.httperrorresponse.response.md) | <code>Response</code> | |
|
||||
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [HttpErrorResponse](./kibana-plugin-public.httperrorresponse.md) > [request](./kibana-plugin-public.httperrorresponse.request.md)
|
||||
|
||||
## HttpErrorResponse.request property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
request?: Request;
|
||||
```
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [HttpErrorResponse](./kibana-plugin-public.httperrorresponse.md) > [response](./kibana-plugin-public.httperrorresponse.response.md)
|
||||
|
||||
## HttpErrorResponse.response property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
response?: Response;
|
||||
```
|
|
@ -0,0 +1,15 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [HttpInterceptController](./kibana-plugin-public.httpinterceptcontroller.md) > [halt](./kibana-plugin-public.httpinterceptcontroller.halt.md)
|
||||
|
||||
## HttpInterceptController.halt() method
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
halt(): void;
|
||||
```
|
||||
<b>Returns:</b>
|
||||
|
||||
`void`
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [HttpInterceptController](./kibana-plugin-public.httpinterceptcontroller.md) > [halted](./kibana-plugin-public.httpinterceptcontroller.halted.md)
|
||||
|
||||
## HttpInterceptController.halted property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
readonly halted: boolean;
|
||||
```
|
|
@ -0,0 +1,24 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [HttpInterceptController](./kibana-plugin-public.httpinterceptcontroller.md)
|
||||
|
||||
## HttpInterceptController class
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export declare class HttpInterceptController
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Modifiers | Type | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| [halted](./kibana-plugin-public.httpinterceptcontroller.halted.md) | | <code>boolean</code> | |
|
||||
|
||||
## Methods
|
||||
|
||||
| Method | Modifiers | Description |
|
||||
| --- | --- | --- |
|
||||
| [halt()](./kibana-plugin-public.httpinterceptcontroller.halt.md) | | |
|
||||
|
|
@ -7,5 +7,5 @@
|
|||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
request: Request;
|
||||
request?: Request;
|
||||
```
|
||||
|
|
|
@ -14,6 +14,7 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
|
|||
|
||||
| Class | Description |
|
||||
| --- | --- |
|
||||
| [HttpInterceptController](./kibana-plugin-public.httpinterceptcontroller.md) | |
|
||||
| [SavedObjectsClient](./kibana-plugin-public.savedobjectsclient.md) | Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing plugin state. The client-side SavedObjectsClient is a thin convenience library around the SavedObjects HTTP API for interacting with Saved Objects. |
|
||||
| [SimpleSavedObject](./kibana-plugin-public.simplesavedobject.md) | This class is a very simple wrapper for SavedObjects loaded from the server with the [SavedObjectsClient](./kibana-plugin-public.savedobjectsclient.md)<!-- -->.<!-- -->It provides basic functionality for creating/saving/deleting saved objects, but doesn't include any type-specific implementations. |
|
||||
| [ToastsApi](./kibana-plugin-public.toastsapi.md) | |
|
||||
|
|
|
@ -1,46 +1,47 @@
|
|||
[[set-time-filter]]
|
||||
== Setting the time filter
|
||||
The time filter restricts the search results to a specific time period. You can
|
||||
You can
|
||||
set a time filter if your index contains time-based events and a time-field is
|
||||
configured for the selected index pattern.
|
||||
By default, the time filter is set to the last 15 minutes. You can use the time
|
||||
picker to change the time filter, or select a specific time
|
||||
range in the histogram.
|
||||
|
||||
By default the time filter is set to the last 15 minutes. You can use the time
|
||||
picker to change the time filter, or select a specific time interval or time
|
||||
range in the histogram at the top of the page.
|
||||
[role="screenshot"]
|
||||
|
||||
[float]
|
||||
=== Filtering with the time picker
|
||||
=== Filter with the time picker
|
||||
|
||||
You can specify a time filter in one of four ways:
|
||||
|
||||
* *Quick*. Click your desired time window from the options listed.
|
||||
* *Relative*. Specify a time filter relative to the current time. You can
|
||||
specify the end time relative to the current time. Relative times can be in the past or future.
|
||||
* *Absolute*. Specify both the start and end times for the time filter. You can
|
||||
adjust the time by editing the *To* and *From* fields.
|
||||
* *Recent*. Click one of the times from your list of recently used time filters.
|
||||
To quickly select from popular time range options, click the calendar
|
||||
dropdown image:infrastructure/images/time-filter-calendar.png[].
|
||||
|
||||
[role="screenshot"]
|
||||
image::images/Timepicker-View.png[Time picker menu]
|
||||
|
||||
[float]
|
||||
=== Filtering from the histogram
|
||||
With the time picker, you can choose from:
|
||||
|
||||
You can set a time filter from the histogram in one of two ways:
|
||||
* *Quick select* to choose a recent time range, and use the back and forward arrows to move through the time ranges.
|
||||
* *Commonly used* to choose a time range from options such as *Last 15 minutes*, *Today*, and *Week to date*.
|
||||
* *Refresh every* to specify an auto-refresh rate.
|
||||
|
||||
For control over the start and end times,
|
||||
click the start time or end time in
|
||||
the bar next to the calendar dropdown. In this popup,
|
||||
you can select *Absolute*, *Relative* or *Now*,
|
||||
and then specify the required options.
|
||||
|
||||
|
||||
[float]
|
||||
=== Filter from the histogram
|
||||
|
||||
You can set a time filter from the histogram in two ways:
|
||||
|
||||
* Click the bar that represents the time interval you want to zoom in on.
|
||||
* Click and drag to view a specific timespan. You must start the selection with
|
||||
the cursor over the background of the chart--the cursor changes to a plus sign
|
||||
when you hover over a valid start point.
|
||||
|
||||
To use a different interval, click the dropdown and select an interval.
|
||||
|
||||
[role="screenshot"]
|
||||
image::images/Histogram-Time.png[Time range selector in Histogram]
|
||||
|
||||
More options:
|
||||
|
||||
* Use the browser Back button to undo your changes.
|
||||
* To move forward or backward in time, click the arrows to the left or right of the time rage.
|
||||
* To use a different interval, click the dropdown and select an interval.
|
||||
|
||||
|
||||
|
|
Before Width: | Height: | Size: 157 KiB After Width: | Height: | Size: 250 KiB |
|
@ -185,6 +185,9 @@ Refresh the page to apply the changes.
|
|||
=== Search settings
|
||||
|
||||
[horizontal]
|
||||
`courier:batchSearches`:: When disabled, dashboard panels will load individually, and search requests will terminate when
|
||||
users navigate away or update the query. When enabled, dashboard panels will load together when all of the data is loaded,
|
||||
and searches will not terminate.
|
||||
`courier:customRequestPreference`:: {ref}/search-request-body.html#request-body-search-preference[Request preference]
|
||||
to use when `courier:setRequestPreference` is set to "custom".
|
||||
`courier:ignoreFilterIfFieldNotInIndex`:: Skips filters that apply to fields that don't exist in the index for a visualization.
|
||||
|
|
|
@ -35,18 +35,3 @@ This page has moved. Please see <<infra-configure-source>>.
|
|||
|
||||
This page has moved. Please see <<xpack-logs-configuring>>.
|
||||
|
||||
[role="exclude",id="creating-df-kib"]
|
||||
== Creating {transforms}
|
||||
|
||||
This page is deleted. Please see
|
||||
{stack-ov}/ecommerce-dataframes.html[Transforming the eCommerce sample data].
|
||||
|
||||
[role="exclude",id="ml-jobs"]
|
||||
== Creating {anomaly-jobs}
|
||||
|
||||
This page has moved. Please see {stack-ov}/create-jobs.html[Creating {anomaly-jobs}].
|
||||
|
||||
[role="exclude",id="job-tips"]
|
||||
== Machine learning job tips
|
||||
|
||||
This page has moved. Please see {stack-ov}/job-tips.html[Machine learning job tips].
|
||||
|
|
|
@ -275,10 +275,9 @@ identifies this Kibana instance.
|
|||
`server.port:`:: *Default: 5601* Kibana is served by a back end server. This
|
||||
setting specifies the port to use.
|
||||
|
||||
`server.rewriteBasePath:`:: *Default: false* Specifies whether Kibana should
|
||||
`server.rewriteBasePath:`:: *Default: true* Specifies whether Kibana should
|
||||
rewrite requests that are prefixed with `server.basePath` or require that they
|
||||
are rewritten by your reverse proxy. This setting was effectively always `false`
|
||||
before Kibana 6.3 and will default to `true` starting in Kibana 7.0.
|
||||
are rewritten by your reverse proxy.
|
||||
|
||||
`server.socketTimeout:`:: *Default: "120000"* The number of milliseconds to wait before closing an
|
||||
inactive socket.
|
||||
|
|
Before Width: | Height: | Size: 520 KiB After Width: | Height: | Size: 554 KiB |
Before Width: | Height: | Size: 175 KiB After Width: | Height: | Size: 566 KiB |
Before Width: | Height: | Size: 146 KiB After Width: | Height: | Size: 341 KiB |
Before Width: | Height: | Size: 116 KiB After Width: | Height: | Size: 99 KiB |
Before Width: | Height: | Size: 216 KiB After Width: | Height: | Size: 485 KiB |
BIN
docs/user/ml/images/outliers.jpg
Normal file
After Width: | Height: | Size: 417 KiB |
|
@ -4,14 +4,9 @@
|
|||
|
||||
As datasets increase in size and complexity, the human effort required to
|
||||
inspect dashboards or maintain rules for spotting infrastructure problems,
|
||||
cyber attacks, or business issues becomes impractical. The Elastic {ml}
|
||||
{anomaly-detect} feature automatically models the normal behavior of your time
|
||||
series data — learning trends, periodicity, and more — in real time to identify
|
||||
anomalies, streamline root cause analysis, and reduce false positives.
|
||||
|
||||
{anomaly-detect-cap} runs in and scales with {es}, and includes an
|
||||
intuitive UI on the {kib} *Machine Learning* page for creating {anomaly-jobs}
|
||||
and understanding results.
|
||||
cyber attacks, or business issues becomes impractical. Elastic {ml-features}
|
||||
such as {anomaly-detect} and {oldetection} make it easier to notice suspicious
|
||||
activities with minimal human interference.
|
||||
|
||||
If you have a basic license, you can use the *Data Visualizer* to learn more
|
||||
about your data. In particular, if your data is stored in {es} and contains a
|
||||
|
@ -25,9 +20,20 @@ experimental[] You can also upload a CSV, NDJSON, or log file (up to 100 MB in
|
|||
size). The *Data Visualizer* identifies the file format and field mappings. You
|
||||
can then optionally import that data into an {es} index.
|
||||
|
||||
If you have a trial or platinum license, you can
|
||||
create {anomaly-jobs} and manage jobs and {dfeeds} from the *Job
|
||||
Management* pane:
|
||||
[float]
|
||||
[[xpack-ml-anomalies]]
|
||||
=== {anomaly-detect-cap}
|
||||
|
||||
The Elastic {ml} {anomaly-detect} feature automatically models the normal
|
||||
behavior of your time series data — learning trends, periodicity, and more — in
|
||||
real time to identify anomalies, streamline root cause analysis, and reduce
|
||||
false positives. {anomaly-detect-cap} runs in and scales with {es}, and
|
||||
includes an intuitive UI on the {kib} *Machine Learning* page for creating
|
||||
{anomaly-jobs} and understanding results.
|
||||
|
||||
If you have a license that includes the {ml-features}, you can
|
||||
create {anomaly-jobs} and manage jobs and {dfeeds} from the *Job Management*
|
||||
pane:
|
||||
|
||||
[role="screenshot"]
|
||||
image::user/ml/images/ml-job-management.jpg[Job Management]
|
||||
|
@ -64,6 +70,23 @@ browser so that it does not block pop-up windows or create an exception for your
|
|||
{kib} URL.
|
||||
|
||||
For more information about the {anomaly-detect} feature, see
|
||||
https://www.elastic.co/what-is/elastic-stack-machine-learning and
|
||||
{stack-ov}/xpack-ml.html[{ml-cap} {anomaly-detect}].
|
||||
https://www.elastic.co/what-is/elastic-stack-machine-learning[{ml-cap} in the {stack}]
|
||||
and {stack-ov}/xpack-ml.html[{ml-cap} {anomaly-detect}].
|
||||
|
||||
[float]
|
||||
[[xpack-ml-dfanalytics]]
|
||||
=== {dfanalytics-cap}
|
||||
|
||||
The Elastic {ml} {dfanalytics} feature enables you to analyze your data using
|
||||
{oldetection} and {regression} algorithms and generate new indices that contain
|
||||
the results alongside your source data.
|
||||
|
||||
If you have a license that includes the {ml-features}, you can create
|
||||
{oldetection} {dfanalytics-jobs} and view their results on the *Analytics* page
|
||||
in {kib}. For example:
|
||||
|
||||
[role="screenshot"]
|
||||
image::user/ml/images/outliers.jpg[{oldetection-cap} results in {kib}]
|
||||
|
||||
For more information about the {dfanalytics} feature, see
|
||||
{stack-ov}/ml-dfanalytics.html[{ml-cap} {dfanalytics}].
|
||||
|
|
52
package.json
|
@ -80,11 +80,11 @@
|
|||
"**/@types/node": "10.12.27",
|
||||
"**/@types/react": "16.8.3",
|
||||
"**/@types/hapi": "^17.0.18",
|
||||
"**/@types/angular": "^1.6.56",
|
||||
"**/typescript": "3.5.3",
|
||||
"**/cypress/lodash": "4.17.13",
|
||||
"**/graphql-toolkit/lodash": "^4.17.13",
|
||||
"**/isomorphic-git/**/base64-js": "^1.2.1",
|
||||
"**/babel-plugin-inline-react-svg/svgo/js-yaml": "^3.13.1",
|
||||
"**/load-grunt-config/js-yaml": "^3.13.1"
|
||||
"**/babel-plugin-inline-react-svg/svgo/js-yaml": "^3.13.1"
|
||||
},
|
||||
"workspaces": {
|
||||
"packages": [
|
||||
|
@ -129,13 +129,13 @@
|
|||
"@types/recompose": "^0.30.5",
|
||||
"JSONStream": "1.3.5",
|
||||
"abortcontroller-polyfill": "^1.3.0",
|
||||
"angular": "1.6.9",
|
||||
"angular-aria": "1.6.6",
|
||||
"angular-elastic": "2.5.1",
|
||||
"angular": "^1.7.8",
|
||||
"angular-aria": "^1.7.8",
|
||||
"angular-elastic": "^2.5.1",
|
||||
"angular-recursion": "^1.0.5",
|
||||
"angular-route": "1.4.7",
|
||||
"angular-sanitize": "1.6.5",
|
||||
"angular-sortable-view": "0.0.17",
|
||||
"angular-route": "^1.7.8",
|
||||
"angular-sanitize": "^1.7.8",
|
||||
"angular-sortable-view": "^0.0.17",
|
||||
"autoprefixer": "9.6.1",
|
||||
"babel-loader": "^8.0.6",
|
||||
"bluebird": "3.5.5",
|
||||
|
@ -241,7 +241,7 @@
|
|||
"style-it": "^2.1.3",
|
||||
"style-loader": "0.23.1",
|
||||
"symbol-observable": "^1.2.0",
|
||||
"tar": "4.4.10",
|
||||
"tar": "4.4.13",
|
||||
"terser-webpack-plugin": "^1.4.1",
|
||||
"thread-loader": "^2.1.3",
|
||||
"tinygradient": "0.4.3",
|
||||
|
@ -271,7 +271,7 @@
|
|||
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
|
||||
"@babel/types": "^7.5.5",
|
||||
"@elastic/eslint-config-kibana": "0.15.0",
|
||||
"@elastic/eslint-plugin-eui": "0.0.1",
|
||||
"@elastic/eslint-plugin-eui": "0.0.2",
|
||||
"@elastic/github-checks-reporter": "0.0.20b3",
|
||||
"@elastic/makelogs": "^4.5.0",
|
||||
"@kbn/dev-utils": "1.0.0",
|
||||
|
@ -286,7 +286,7 @@
|
|||
"@microsoft/api-extractor": "7.4.2",
|
||||
"@octokit/rest": "^15.10.0",
|
||||
"@percy/agent": "^0.11.0",
|
||||
"@types/angular": "1.6.50",
|
||||
"@types/angular": "^1.6.56",
|
||||
"@types/angular-mocks": "^1.7.0",
|
||||
"@types/babel__core": "^7.1.2",
|
||||
"@types/bluebird": "^3.1.1",
|
||||
|
@ -294,8 +294,8 @@
|
|||
"@types/chance": "^1.0.0",
|
||||
"@types/cheerio": "^0.22.10",
|
||||
"@types/chromedriver": "^2.38.0",
|
||||
"@types/classnames": "^2.2.3",
|
||||
"@types/d3": "^3.5.41",
|
||||
"@types/classnames": "^2.2.9",
|
||||
"@types/d3": "^3.5.43",
|
||||
"@types/dedent": "^0.7.0",
|
||||
"@types/delete-empty": "^2.0.0",
|
||||
"@types/elasticsearch": "^5.0.33",
|
||||
|
@ -313,9 +313,9 @@
|
|||
"@types/history": "^4.7.3",
|
||||
"@types/hoek": "^4.1.3",
|
||||
"@types/humps": "^1.1.2",
|
||||
"@types/jest": "^24.0.9",
|
||||
"@types/jest": "^24.0.18",
|
||||
"@types/joi": "^13.4.2",
|
||||
"@types/jquery": "^3.3.6",
|
||||
"@types/jquery": "^3.3.31",
|
||||
"@types/js-yaml": "^3.11.1",
|
||||
"@types/json5": "^0.0.30",
|
||||
"@types/license-checker": "15.0.0",
|
||||
|
@ -352,7 +352,7 @@
|
|||
"@types/zen-observable": "^0.8.0",
|
||||
"@typescript-eslint/eslint-plugin": "1.13.0",
|
||||
"@typescript-eslint/parser": "1.13.0",
|
||||
"angular-mocks": "1.4.7",
|
||||
"angular-mocks": "^1.7.8",
|
||||
"archiver": "^3.0.0",
|
||||
"babel-eslint": "^10.0.3",
|
||||
"babel-jest": "^24.9.0",
|
||||
|
@ -371,17 +371,17 @@
|
|||
"enzyme-adapter-utils": "^1.12.0",
|
||||
"enzyme-to-json": "^3.3.4",
|
||||
"eslint": "5.16.0",
|
||||
"eslint-config-prettier": "6.1.0",
|
||||
"eslint-config-prettier": "6.3.0",
|
||||
"eslint-plugin-babel": "^5.3.0",
|
||||
"eslint-plugin-ban": "1.2.0",
|
||||
"eslint-plugin-ban": "1.3.0",
|
||||
"eslint-plugin-import": "2.18.2",
|
||||
"eslint-plugin-jest": "22.7.1",
|
||||
"eslint-plugin-jest": "22.17.0",
|
||||
"eslint-plugin-jsx-a11y": "6.2.3",
|
||||
"eslint-plugin-mocha": "5.3.0",
|
||||
"eslint-plugin-no-unsanitized": "3.0.2",
|
||||
"eslint-plugin-node": "9.1.0",
|
||||
"eslint-plugin-node": "9.2.0",
|
||||
"eslint-plugin-prefer-object-spread": "1.2.1",
|
||||
"eslint-plugin-prettier": "3.1.0",
|
||||
"eslint-plugin-prettier": "3.1.1",
|
||||
"eslint-plugin-react": "7.13.0",
|
||||
"eslint-plugin-react-hooks": "1.6.0",
|
||||
"exit-hook": "^2.2.0",
|
||||
|
@ -403,9 +403,9 @@
|
|||
"intl-messageformat-parser": "^1.4.0",
|
||||
"is-path-inside": "^2.1.0",
|
||||
"istanbul-instrumenter-loader": "3.0.1",
|
||||
"jest": "^24.8.0",
|
||||
"jest-cli": "^24.8.0",
|
||||
"jest-dom": "^3.1.3",
|
||||
"jest": "^24.9.0",
|
||||
"jest-cli": "^24.9.0",
|
||||
"jest-dom": "^3.5.0",
|
||||
"jest-raw-loader": "^1.0.1",
|
||||
"jimp": "0.8.4",
|
||||
"json5": "^1.0.1",
|
||||
|
@ -446,7 +446,7 @@
|
|||
"tree-kill": "^1.1.0",
|
||||
"typescript": "3.5.3",
|
||||
"typings-tester": "^0.3.2",
|
||||
"vinyl-fs": "^3.0.2",
|
||||
"vinyl-fs": "^3.0.3",
|
||||
"xml2js": "^0.4.19",
|
||||
"xmlbuilder": "13.0.2",
|
||||
"zlib": "^1.0.5"
|
||||
|
|
|
@ -20,10 +20,10 @@
|
|||
"babel-eslint": "^10.0.3",
|
||||
"eslint": "5.16.0",
|
||||
"eslint-plugin-babel": "^5.3.0",
|
||||
"eslint-plugin-ban": "1.2.0",
|
||||
"eslint-plugin-ban": "1.3.0",
|
||||
"eslint-plugin-jsx-a11y": "6.2.3",
|
||||
"eslint-plugin-import": "2.18.2",
|
||||
"eslint-plugin-jest": "^22.4.1",
|
||||
"eslint-plugin-jest": "^22.17.0",
|
||||
"eslint-plugin-mocha": "^5.3.0",
|
||||
"eslint-plugin-no-unsanitized": "3.0.2",
|
||||
"eslint-plugin-prefer-object-spread": "1.2.1",
|
||||
|
|
|
@ -129,6 +129,7 @@ module.exports = {
|
|||
'no-console': 'error',
|
||||
'no-debugger': 'error',
|
||||
'no-empty': 'error',
|
||||
'no-extend-native': 'error',
|
||||
'no-eval': 'error',
|
||||
'no-multiple-empty-lines': 'error',
|
||||
'no-new-wrappers': 'error',
|
||||
|
@ -142,6 +143,7 @@ module.exports = {
|
|||
'no-var': 'error',
|
||||
'object-curly-spacing': 'error',
|
||||
'object-shorthand': 'error',
|
||||
'one-var': [ 'error', 'never' ],
|
||||
'prefer-const': 'error',
|
||||
'quotes': ['error', 'double', { 'avoidEscape': true }],
|
||||
'quote-props': ['error', 'consistent-as-needed'],
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`#hostname returns error when empty string 1`] = `"any.empty"`;
|
||||
|
||||
exports[`#hostname returns error when value is not a valid hostname 1`] = `"value is [host:name] but it must be a valid hostname (see RFC 1123)."`;
|
||||
|
||||
exports[`#hostname returns error when value is not a valid hostname 2`] = `"value is [localhost:5601] but it must be a valid hostname (see RFC 1123)."`;
|
||||
|
@ -10,10 +12,16 @@ exports[`#hostname returns error when value is not a valid hostname 4`] = `"valu
|
|||
|
||||
exports[`#hostname returns error when value is not a valid hostname 5`] = `"value is [aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa] but it must be a valid hostname (see RFC 1123)."`;
|
||||
|
||||
exports[`#hostname supports string validation rules 1`] = `"value is [www.example.com] but it must have a maximum length of [3]."`;
|
||||
|
||||
exports[`#maxLength returns error when longer string 1`] = `"value is [foo] but it must have a maximum length of [2]."`;
|
||||
|
||||
exports[`#minLength returns error when empty string 1`] = `"value is [] but it must have a minimum length of [2]."`;
|
||||
|
||||
exports[`#minLength returns error when shorter string 1`] = `"value is [foo] but it must have a minimum length of [4]."`;
|
||||
|
||||
exports[`#validate throw when empty string 1`] = `"validator failure"`;
|
||||
|
||||
exports[`#validate throws when returns string 1`] = `"validator failure"`;
|
||||
|
||||
exports[`includes namespace in failure 1`] = `"[foo-namespace]: expected value of type [string] but got [undefined]"`;
|
||||
|
|
|
@ -23,6 +23,10 @@ test('returns value is string and defined', () => {
|
|||
expect(schema.string().validate('test')).toBe('test');
|
||||
});
|
||||
|
||||
test('allows empty strings', () => {
|
||||
expect(schema.string().validate('')).toBe('');
|
||||
});
|
||||
|
||||
test('is required by default', () => {
|
||||
expect(() => schema.string().validate(undefined)).toThrowErrorMatchingSnapshot();
|
||||
});
|
||||
|
@ -41,6 +45,10 @@ describe('#minLength', () => {
|
|||
test('returns error when shorter string', () => {
|
||||
expect(() => schema.string({ minLength: 4 }).validate('foo')).toThrowErrorMatchingSnapshot();
|
||||
});
|
||||
|
||||
test('returns error when empty string', () => {
|
||||
expect(() => schema.string({ minLength: 2 }).validate('')).toThrowErrorMatchingSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('#maxLength', () => {
|
||||
|
@ -84,6 +92,16 @@ describe('#hostname', () => {
|
|||
const tooLongHostName = 'a'.repeat(256);
|
||||
expect(() => hostNameSchema.validate(tooLongHostName)).toThrowErrorMatchingSnapshot();
|
||||
});
|
||||
|
||||
test('returns error when empty string', () => {
|
||||
expect(() => schema.string({ hostname: true }).validate('')).toThrowErrorMatchingSnapshot();
|
||||
});
|
||||
|
||||
test('supports string validation rules', () => {
|
||||
expect(() =>
|
||||
schema.string({ hostname: true, maxLength: 3 }).validate('www.example.com')
|
||||
).toThrowErrorMatchingSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('#defaultValue', () => {
|
||||
|
@ -130,6 +148,12 @@ describe('#validate', () => {
|
|||
|
||||
expect(() => schema.string({ validate }).validate('foo')).toThrowErrorMatchingSnapshot();
|
||||
});
|
||||
|
||||
test('throw when empty string', () => {
|
||||
const validate = () => 'validator failure';
|
||||
|
||||
expect(() => schema.string({ validate }).validate('')).toThrowErrorMatchingSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
test('returns error when not string', () => {
|
||||
|
|
|
@ -29,18 +29,33 @@ export type StringOptions = TypeOptions<string> & {
|
|||
|
||||
export class StringType extends Type<string> {
|
||||
constructor(options: StringOptions = {}) {
|
||||
let schema = internals.string().allow('');
|
||||
// We want to allow empty strings, however calling `allow('')` casues
|
||||
// Joi to whitelist the value and skip any additional validation.
|
||||
// Instead, we reimplement the string validator manually except in the
|
||||
// hostname case where empty strings aren't allowed anyways.
|
||||
let schema =
|
||||
options.hostname === true
|
||||
? internals.string().hostname()
|
||||
: internals.any().custom(value => {
|
||||
if (typeof value !== 'string') {
|
||||
return `expected value of type [string] but got [${typeDetect(value)}]`;
|
||||
}
|
||||
});
|
||||
|
||||
if (options.minLength !== undefined) {
|
||||
schema = schema.min(options.minLength);
|
||||
schema = schema.custom(value => {
|
||||
if (value.length < options.minLength!) {
|
||||
return `value is [${value}] but it must have a minimum length of [${options.minLength}].`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (options.maxLength !== undefined) {
|
||||
schema = schema.max(options.maxLength);
|
||||
}
|
||||
|
||||
if (options.hostname === true) {
|
||||
schema = schema.hostname();
|
||||
schema = schema.custom(value => {
|
||||
if (value.length > options.maxLength!) {
|
||||
return `value is [${value}] but it must have a maximum length of [${options.maxLength}].`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
super(schema, options);
|
||||
|
@ -49,12 +64,7 @@ export class StringType extends Type<string> {
|
|||
protected handleError(type: string, { limit, value }: Record<string, any>) {
|
||||
switch (type) {
|
||||
case 'any.required':
|
||||
case 'string.base':
|
||||
return `expected value of type [string] but got [${typeDetect(value)}]`;
|
||||
case 'string.min':
|
||||
return `value is [${value}] but it must have a minimum length of [${limit}].`;
|
||||
case 'string.max':
|
||||
return `value is [${value}] but it must have a maximum length of [${limit}].`;
|
||||
case 'string.hostname':
|
||||
return `value is [${value}] but it must be a valid hostname (see RFC 1123).`;
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
"devDependencies": {
|
||||
"@babel/core": "^7.5.5",
|
||||
"@babel/plugin-transform-async-to-generator": "^7.5.0",
|
||||
"jest": "^24.8.0",
|
||||
"jest": "^24.9.0",
|
||||
"typescript": "3.5.3"
|
||||
},
|
||||
"jest": {
|
||||
|
|
|
@ -21,6 +21,13 @@ import { Filter, FilterMeta } from './meta_filter';
|
|||
|
||||
export type ExistsFilterMeta = FilterMeta;
|
||||
|
||||
export interface FilterExistsProperty {
|
||||
field: any;
|
||||
}
|
||||
|
||||
export type ExistsFilter = Filter & {
|
||||
meta: ExistsFilterMeta;
|
||||
exists?: FilterExistsProperty;
|
||||
};
|
||||
|
||||
export const isExistsFilter = (filter: any): filter is ExistsFilter => filter && filter.exists;
|
||||
|
|
|
@ -28,4 +28,8 @@ export type GeoBoundingBoxFilterMeta = FilterMeta & {
|
|||
|
||||
export type GeoBoundingBoxFilter = Filter & {
|
||||
meta: GeoBoundingBoxFilterMeta;
|
||||
geo_bounding_box: any;
|
||||
};
|
||||
|
||||
export const isGeoBoundingBoxFilter = (filter: any): filter is GeoBoundingBoxFilter =>
|
||||
filter && filter.geo_bounding_box;
|
||||
|
|
|
@ -27,4 +27,8 @@ export type GeoPolygonFilterMeta = FilterMeta & {
|
|||
|
||||
export type GeoPolygonFilter = Filter & {
|
||||
meta: GeoPolygonFilterMeta;
|
||||
geo_polygon: any;
|
||||
};
|
||||
|
||||
export const isGeoPolygonFilter = (filter: any): filter is GeoPolygonFilter =>
|
||||
filter && filter.geo_polygon;
|
||||
|
|
|
@ -22,22 +22,38 @@ export * from './meta_filter';
|
|||
|
||||
// The actual filter types
|
||||
import { CustomFilter } from './custom_filter';
|
||||
import { ExistsFilter } from './exists_filter';
|
||||
import { GeoBoundingBoxFilter } from './geo_bounding_box_filter';
|
||||
import { GeoPolygonFilter } from './geo_polygon_filter';
|
||||
import { PhraseFilter } from './phrase_filter';
|
||||
import { PhrasesFilter } from './phrases_filter';
|
||||
import { QueryStringFilter } from './query_string_filter';
|
||||
import { RangeFilter } from './range_filter';
|
||||
import { ExistsFilter, isExistsFilter } from './exists_filter';
|
||||
import { GeoBoundingBoxFilter, isGeoBoundingBoxFilter } from './geo_bounding_box_filter';
|
||||
import { GeoPolygonFilter, isGeoPolygonFilter } from './geo_polygon_filter';
|
||||
import { PhraseFilter, isPhraseFilter, isScriptedPhraseFilter } from './phrase_filter';
|
||||
import { PhrasesFilter, isPhrasesFilter } from './phrases_filter';
|
||||
import { QueryStringFilter, isQueryStringFilter } from './query_string_filter';
|
||||
import { RangeFilter, isRangeFilter, isScriptedRangeFilter } from './range_filter';
|
||||
import { MatchAllFilter, isMatchAllFilter } from './match_all_filter';
|
||||
import { MissingFilter, isMissingFilter } from './missing_filter';
|
||||
|
||||
export {
|
||||
CustomFilter,
|
||||
ExistsFilter,
|
||||
isExistsFilter,
|
||||
GeoBoundingBoxFilter,
|
||||
isGeoBoundingBoxFilter,
|
||||
GeoPolygonFilter,
|
||||
isGeoPolygonFilter,
|
||||
PhraseFilter,
|
||||
isPhraseFilter,
|
||||
isScriptedPhraseFilter,
|
||||
PhrasesFilter,
|
||||
isPhrasesFilter,
|
||||
QueryStringFilter,
|
||||
isQueryStringFilter,
|
||||
RangeFilter,
|
||||
isRangeFilter,
|
||||
isScriptedRangeFilter,
|
||||
MatchAllFilter,
|
||||
isMatchAllFilter,
|
||||
MissingFilter,
|
||||
isMissingFilter,
|
||||
};
|
||||
|
||||
// Any filter associated with a field (used in the filter bar/editor)
|
||||
|
@ -47,4 +63,19 @@ export type FieldFilter =
|
|||
| GeoPolygonFilter
|
||||
| PhraseFilter
|
||||
| PhrasesFilter
|
||||
| RangeFilter;
|
||||
| RangeFilter
|
||||
| MatchAllFilter
|
||||
| MissingFilter;
|
||||
|
||||
export enum FILTERS {
|
||||
CUSTOM = 'custom',
|
||||
PHRASES = 'phrases',
|
||||
PHRASE = 'phrase',
|
||||
EXISTS = 'exists',
|
||||
MATCH_ALL = 'match_all',
|
||||
MISSING = 'missing',
|
||||
QUERY_STRING = 'query_string',
|
||||
RANGE = 'range',
|
||||
GEO_BOUNDING_BOX = 'geo_bounding_box',
|
||||
GEO_POLYGON = 'geo_polygon',
|
||||
}
|
||||
|
|
33
packages/kbn-es-query/src/filters/lib/match_all_filter.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { Filter, FilterMeta } from './meta_filter';
|
||||
|
||||
export interface MatchAllFilterMeta extends FilterMeta {
|
||||
field: any;
|
||||
formattedValue: string;
|
||||
}
|
||||
|
||||
export type MatchAllFilter = Filter & {
|
||||
meta: MatchAllFilterMeta;
|
||||
match_all: any;
|
||||
};
|
||||
|
||||
export const isMatchAllFilter = (filter: any): filter is MatchAllFilter =>
|
||||
filter && filter.match_all;
|
|
@ -35,12 +35,13 @@ export interface FilterMeta {
|
|||
alias: string | null;
|
||||
key?: string;
|
||||
value?: string;
|
||||
params?: any;
|
||||
}
|
||||
|
||||
export interface Filter {
|
||||
$state?: FilterState;
|
||||
meta: FilterMeta;
|
||||
query?: object;
|
||||
query?: any;
|
||||
}
|
||||
|
||||
export interface LatLon {
|
||||
|
|
|
@ -17,13 +17,13 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import { mapFilter } from './map_filter';
|
||||
import { Filter, FilterMeta } from './meta_filter';
|
||||
|
||||
export function mapAndFlattenFilters(indexPatterns, filters) {
|
||||
const flattened = _(filters)
|
||||
.flatten()
|
||||
.compact()
|
||||
.map(item => mapFilter(indexPatterns, item)).value();
|
||||
return Promise.all(flattened);
|
||||
}
|
||||
export type MissingFilterMeta = FilterMeta;
|
||||
|
||||
export type MissingFilter = Filter & {
|
||||
meta: MissingFilterMeta;
|
||||
missing: any;
|
||||
};
|
||||
|
||||
export const isMissingFilter = (filter: any): filter is MissingFilter => filter && filter.missing;
|
|
@ -17,14 +17,27 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { get } from 'lodash';
|
||||
import { Filter, FilterMeta } from './meta_filter';
|
||||
|
||||
export type PhraseFilterMeta = FilterMeta & {
|
||||
params: {
|
||||
query: string; // The unformatted value
|
||||
};
|
||||
script?: {
|
||||
script: {
|
||||
params: any;
|
||||
};
|
||||
};
|
||||
field?: any;
|
||||
};
|
||||
|
||||
export type PhraseFilter = Filter & {
|
||||
meta: PhraseFilterMeta;
|
||||
};
|
||||
|
||||
export const isPhraseFilter = (filter: any): filter is PhraseFilter =>
|
||||
filter && (filter.query && filter.query.match);
|
||||
|
||||
export const isScriptedPhraseFilter = (filter: any): filter is PhraseFilter =>
|
||||
Boolean(get(filter, 'script.script.params.value'));
|
||||
|
|
|
@ -21,8 +21,12 @@ import { Filter, FilterMeta } from './meta_filter';
|
|||
|
||||
export type PhrasesFilterMeta = FilterMeta & {
|
||||
params: string[]; // The unformatted values
|
||||
field?: string;
|
||||
};
|
||||
|
||||
export type PhrasesFilter = Filter & {
|
||||
meta: PhrasesFilterMeta;
|
||||
};
|
||||
|
||||
export const isPhrasesFilter = (filter: any): filter is PhrasesFilter =>
|
||||
filter && filter.meta.type === 'phrases';
|
||||
|
|
|
@ -23,4 +23,12 @@ export type QueryStringFilterMeta = FilterMeta;
|
|||
|
||||
export type QueryStringFilter = Filter & {
|
||||
meta: QueryStringFilterMeta;
|
||||
query?: {
|
||||
query_string: {
|
||||
query: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export const isQueryStringFilter = (filter: any): filter is QueryStringFilter =>
|
||||
filter && filter.query && filter.query.query_string;
|
||||
|
|
|
@ -16,20 +16,50 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { get, keys } from 'lodash';
|
||||
import { Filter, FilterMeta } from './meta_filter';
|
||||
|
||||
export interface RangeFilterParams {
|
||||
interface FilterRange {
|
||||
from?: number | string;
|
||||
to?: number | string;
|
||||
}
|
||||
|
||||
interface FilterRangeGt {
|
||||
gt?: number | string;
|
||||
gte?: number | string;
|
||||
lte?: number | string;
|
||||
lt?: number | string;
|
||||
}
|
||||
|
||||
interface FilterRangeGte {
|
||||
gte?: number | string;
|
||||
lte?: number | string;
|
||||
}
|
||||
|
||||
export type RangeFilterParams = FilterRange & FilterRangeGt & FilterRangeGte;
|
||||
|
||||
export type RangeFilterMeta = FilterMeta & {
|
||||
params: RangeFilterParams;
|
||||
field?: any;
|
||||
};
|
||||
|
||||
export type RangeFilter = Filter & {
|
||||
meta: RangeFilterMeta;
|
||||
script?: {
|
||||
script: {
|
||||
params: any;
|
||||
};
|
||||
};
|
||||
range: { [key: string]: RangeFilterParams };
|
||||
};
|
||||
|
||||
const hasRangeKeys = (params: RangeFilterParams) =>
|
||||
Boolean(
|
||||
keys(params).find((key: string) => ['gte', 'gt', 'lte', 'lt', 'from', 'to'].includes(key))
|
||||
);
|
||||
|
||||
export const isRangeFilter = (filter: any): filter is RangeFilter => filter && filter.range;
|
||||
|
||||
export const isScriptedRangeFilter = (filter: any): filter is RangeFilter => {
|
||||
const params: RangeFilterParams = get(filter, 'script.script.params', {});
|
||||
|
||||
return hasRangeKeys(params);
|
||||
};
|
||||
|
|
|
@ -887,7 +887,7 @@ describe('I18n engine', () => {
|
|||
});
|
||||
|
||||
describe('load', () => {
|
||||
let mockFetch: jest.Mock;
|
||||
let mockFetch: jest.SpyInstance;
|
||||
beforeEach(() => {
|
||||
mockFetch = jest.spyOn(global as any, 'fetch').mockImplementation();
|
||||
});
|
||||
|
|
|
@ -27,6 +27,6 @@
|
|||
"node-sass": "^4.9.4",
|
||||
"through2": "^2.0.3",
|
||||
"through2-map": "^3.0.0",
|
||||
"vinyl-fs": "^3.0.0"
|
||||
"vinyl-fs": "^3.0.3"
|
||||
}
|
||||
}
|
||||
|
|
13
packages/kbn-pm/dist/index.js
vendored
|
@ -48209,7 +48209,7 @@ function mixinDeep(target, objects) {
|
|||
*/
|
||||
|
||||
function copy(val, key) {
|
||||
if (key === '__proto__') {
|
||||
if (!isValidKey(key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -48232,6 +48232,17 @@ function isObject(val) {
|
|||
return isExtendable(val) && !Array.isArray(val);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if `key` is a valid key to use when extending objects.
|
||||
*
|
||||
* @param {String} `key`
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
function isValidKey(key) {
|
||||
return key !== '__proto__' && key !== 'constructor' && key !== 'prototype';
|
||||
};
|
||||
|
||||
/**
|
||||
* Expose `mixinDeep`
|
||||
*/
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
"@types/mkdirp": "^0.5.2",
|
||||
"@types/ncp": "^2.0.1",
|
||||
"@types/node": "^10.12.27",
|
||||
"@types/ora": "^1.3.2",
|
||||
"@types/ora": "^1.3.5",
|
||||
"@types/read-pkg": "^4.0.0",
|
||||
"@types/strip-ansi": "^3.0.0",
|
||||
"@types/strong-log-transformer": "^1.0.0",
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
},
|
||||
"homepage": "https://github.com/jbudz/spec-to-console#readme",
|
||||
"devDependencies": {
|
||||
"jest": "^24.8.0",
|
||||
"jest": "^24.9.0",
|
||||
"prettier": "^1.14.3"
|
||||
},
|
||||
"dependencies": {
|
||||
|
|
|
@ -18,7 +18,12 @@
|
|||
*/
|
||||
|
||||
export class HttpFetchError extends Error {
|
||||
constructor(message: string, public readonly response?: Response, public readonly body?: any) {
|
||||
constructor(
|
||||
message: string,
|
||||
public readonly request: Request,
|
||||
public readonly response?: Response,
|
||||
public readonly body?: any
|
||||
) {
|
||||
super(message);
|
||||
|
||||
// captureStackTrace is only available in the V8 engine, so any browser using
|
||||
|
|
|
@ -25,6 +25,10 @@ import { readFileSync } from 'fs';
|
|||
import { join } from 'path';
|
||||
import { setup, SetupTap } from '../../../test_utils/public/http_test_setup';
|
||||
|
||||
function delay<T>(duration: number) {
|
||||
return new Promise<T>(r => setTimeout(r, duration));
|
||||
}
|
||||
|
||||
const setupFakeBasePath: SetupTap = injectedMetadata => {
|
||||
injectedMetadata.getBasePath.mockReturnValue('/foo/bar');
|
||||
};
|
||||
|
@ -341,169 +345,228 @@ describe('interception', () => {
|
|||
});
|
||||
|
||||
it('should skip remaining interceptors when controller halts during request', async () => {
|
||||
const order: string[] = [];
|
||||
const usedSpy = jest.fn();
|
||||
const unusedSpy = jest.fn();
|
||||
|
||||
http.intercept({
|
||||
request() {
|
||||
order.push('Request 1');
|
||||
},
|
||||
response() {
|
||||
order.push('Response 1');
|
||||
},
|
||||
});
|
||||
http.intercept({ request: unusedSpy, response: unusedSpy });
|
||||
http.intercept({
|
||||
request(request, controller) {
|
||||
controller.halt();
|
||||
order.push('Request 2');
|
||||
},
|
||||
response() {
|
||||
order.push('Response 2');
|
||||
},
|
||||
response: unusedSpy,
|
||||
});
|
||||
http.intercept({
|
||||
request() {
|
||||
order.push('Request 3');
|
||||
},
|
||||
response() {
|
||||
order.push('Response 3');
|
||||
},
|
||||
request: usedSpy,
|
||||
response: unusedSpy,
|
||||
});
|
||||
|
||||
await expect(http.fetch('/my/wat')).rejects.toThrow(/HTTP Intercept Halt/);
|
||||
http.fetch('/my/path').then(unusedSpy, unusedSpy);
|
||||
await delay(1000);
|
||||
|
||||
expect(unusedSpy).toHaveBeenCalledTimes(0);
|
||||
expect(usedSpy).toHaveBeenCalledTimes(1);
|
||||
expect(fetchMock.called()).toBe(false);
|
||||
expect(order).toEqual(['Request 3', 'Request 2']);
|
||||
});
|
||||
|
||||
it('should skip remaining interceptors when controller halts during response', async () => {
|
||||
const order: string[] = [];
|
||||
const usedSpy = jest.fn();
|
||||
const unusedSpy = jest.fn();
|
||||
|
||||
http.intercept({
|
||||
request() {
|
||||
order.push('Request 1');
|
||||
},
|
||||
request: usedSpy,
|
||||
response(response, controller) {
|
||||
controller.halt();
|
||||
order.push('Response 1');
|
||||
},
|
||||
});
|
||||
http.intercept({ request: usedSpy, response: unusedSpy });
|
||||
http.intercept({ request: usedSpy, response: unusedSpy });
|
||||
|
||||
http.fetch('/my/path').then(unusedSpy, unusedSpy);
|
||||
await delay(1000);
|
||||
|
||||
expect(fetchMock.called()).toBe(true);
|
||||
expect(usedSpy).toHaveBeenCalledTimes(3);
|
||||
expect(unusedSpy).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('should skip remaining interceptors when controller halts during responseError', async () => {
|
||||
fetchMock.post('*', 401);
|
||||
|
||||
const unusedSpy = jest.fn();
|
||||
|
||||
http.intercept({ response: unusedSpy });
|
||||
http.intercept({
|
||||
request() {
|
||||
order.push('Request 2');
|
||||
},
|
||||
response() {
|
||||
order.push('Response 2');
|
||||
},
|
||||
});
|
||||
http.intercept({
|
||||
request() {
|
||||
order.push('Request 3');
|
||||
},
|
||||
response() {
|
||||
order.push('Response 3');
|
||||
responseError(response, controller) {
|
||||
controller.halt();
|
||||
},
|
||||
});
|
||||
|
||||
await expect(http.fetch('/my/wat')).rejects.toThrow(/HTTP Intercept Halt/);
|
||||
http.post('/my/path').then(unusedSpy, unusedSpy);
|
||||
await delay(1000);
|
||||
|
||||
expect(fetchMock.called()).toBe(true);
|
||||
expect(order).toEqual(['Request 3', 'Request 2', 'Request 1', 'Response 1']);
|
||||
expect(unusedSpy).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('should not fetch if exception occurs during request interception', async () => {
|
||||
const order: string[] = [];
|
||||
const usedSpy = jest.fn();
|
||||
const unusedSpy = jest.fn();
|
||||
|
||||
http.intercept({
|
||||
request() {
|
||||
order.push('Request 1');
|
||||
},
|
||||
requestError() {
|
||||
order.push('RequestError 1');
|
||||
},
|
||||
response() {
|
||||
order.push('Response 1');
|
||||
},
|
||||
responseError() {
|
||||
order.push('ResponseError 1');
|
||||
},
|
||||
request: unusedSpy,
|
||||
requestError: usedSpy,
|
||||
response: unusedSpy,
|
||||
responseError: usedSpy,
|
||||
});
|
||||
http.intercept({
|
||||
request() {
|
||||
order.push('Request 2');
|
||||
throw new Error('Interception Error');
|
||||
},
|
||||
response() {
|
||||
order.push('Response 2');
|
||||
},
|
||||
responseError() {
|
||||
order.push('ResponseError 2');
|
||||
},
|
||||
});
|
||||
http.intercept({
|
||||
request() {
|
||||
order.push('Request 3');
|
||||
},
|
||||
response() {
|
||||
order.push('Response 3');
|
||||
},
|
||||
responseError() {
|
||||
order.push('ResponseError 3');
|
||||
},
|
||||
response: unusedSpy,
|
||||
responseError: usedSpy,
|
||||
});
|
||||
http.intercept({ request: usedSpy, response: unusedSpy, responseError: usedSpy });
|
||||
|
||||
await expect(http.fetch('/my/wat')).rejects.toThrow(/Interception Error/);
|
||||
await expect(http.fetch('/my/path')).rejects.toThrow(/Interception Error/);
|
||||
expect(fetchMock.called()).toBe(false);
|
||||
expect(order).toEqual([
|
||||
'Request 3',
|
||||
'Request 2',
|
||||
'RequestError 1',
|
||||
'ResponseError 1',
|
||||
'ResponseError 2',
|
||||
'ResponseError 3',
|
||||
]);
|
||||
expect(unusedSpy).toHaveBeenCalledTimes(0);
|
||||
expect(usedSpy).toHaveBeenCalledTimes(5);
|
||||
});
|
||||
|
||||
it('should succeed if request throws but caught by interceptor', async () => {
|
||||
const order: string[] = [];
|
||||
const usedSpy = jest.fn();
|
||||
const unusedSpy = jest.fn();
|
||||
|
||||
http.intercept({
|
||||
request() {
|
||||
order.push('Request 1');
|
||||
},
|
||||
request: unusedSpy,
|
||||
requestError({ request }) {
|
||||
order.push('RequestError 1');
|
||||
return new Request('/my/route', request);
|
||||
},
|
||||
response() {
|
||||
order.push('Response 1');
|
||||
},
|
||||
response: usedSpy,
|
||||
});
|
||||
http.intercept({
|
||||
request() {
|
||||
order.push('Request 2');
|
||||
throw new Error('Interception Error');
|
||||
},
|
||||
response() {
|
||||
order.push('Response 2');
|
||||
},
|
||||
});
|
||||
http.intercept({
|
||||
request() {
|
||||
order.push('Request 3');
|
||||
},
|
||||
response() {
|
||||
order.push('Response 3');
|
||||
},
|
||||
response: usedSpy,
|
||||
});
|
||||
http.intercept({ request: usedSpy, response: usedSpy });
|
||||
|
||||
await expect(http.fetch('/my/route')).resolves.toEqual({ foo: 'bar' });
|
||||
expect(fetchMock.called()).toBe(true);
|
||||
expect(order).toEqual([
|
||||
'Request 3',
|
||||
'Request 2',
|
||||
'RequestError 1',
|
||||
'Response 1',
|
||||
'Response 2',
|
||||
'Response 3',
|
||||
]);
|
||||
expect(unusedSpy).toHaveBeenCalledTimes(0);
|
||||
expect(usedSpy).toHaveBeenCalledTimes(4);
|
||||
});
|
||||
|
||||
describe('request availability during interception', () => {
|
||||
it('should not be available to responseError when request throws', async () => {
|
||||
expect.assertions(3);
|
||||
|
||||
let spiedRequest: Request | undefined;
|
||||
|
||||
http.intercept({
|
||||
request() {
|
||||
throw new Error('Internal Server Error');
|
||||
},
|
||||
responseError({ request }) {
|
||||
spiedRequest = request;
|
||||
},
|
||||
});
|
||||
|
||||
await expect(http.fetch('/my/path')).rejects.toThrow();
|
||||
expect(fetchMock.called()).toBe(false);
|
||||
expect(spiedRequest).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should be available to responseError when response throws', async () => {
|
||||
let spiedRequest: Request | undefined;
|
||||
|
||||
http.intercept({
|
||||
response() {
|
||||
throw new Error('Internal Server Error');
|
||||
},
|
||||
});
|
||||
http.intercept({
|
||||
responseError({ request }) {
|
||||
spiedRequest = request;
|
||||
},
|
||||
});
|
||||
|
||||
await expect(http.fetch('/my/path')).rejects.toThrow();
|
||||
expect(fetchMock.called()).toBe(true);
|
||||
expect(spiedRequest).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('response availability during interception', () => {
|
||||
it('should be available to responseError when network request fails', async () => {
|
||||
fetchMock.restore();
|
||||
fetchMock.get('*', { status: 500 });
|
||||
|
||||
let spiedResponse: Response | undefined;
|
||||
|
||||
http.intercept({
|
||||
responseError({ response }) {
|
||||
spiedResponse = response;
|
||||
},
|
||||
});
|
||||
|
||||
await expect(http.fetch('/my/path')).rejects.toThrow();
|
||||
expect(spiedResponse).toBeDefined();
|
||||
});
|
||||
|
||||
it('should not be available to responseError when request throws', async () => {
|
||||
let spiedResponse: Response | undefined;
|
||||
|
||||
http.intercept({
|
||||
request() {
|
||||
throw new Error('Internal Server Error');
|
||||
},
|
||||
responseError({ response }) {
|
||||
spiedResponse = response;
|
||||
},
|
||||
});
|
||||
|
||||
await expect(http.fetch('/my/path')).rejects.toThrow();
|
||||
expect(spiedResponse).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
it('should actually halt request interceptors in reverse order', async () => {
|
||||
const unusedSpy = jest.fn();
|
||||
|
||||
http.intercept({ request: unusedSpy });
|
||||
http.intercept({
|
||||
request(request, controller) {
|
||||
controller.halt();
|
||||
},
|
||||
});
|
||||
|
||||
http.fetch('/my/path');
|
||||
await delay(500);
|
||||
|
||||
expect(unusedSpy).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('should recover from failing request interception via request error interceptor', async () => {
|
||||
const usedSpy = jest.fn();
|
||||
|
||||
http.intercept({
|
||||
requestError(httpErrorRequest) {
|
||||
return httpErrorRequest.request;
|
||||
},
|
||||
response: usedSpy,
|
||||
});
|
||||
|
||||
http.intercept({
|
||||
request(request, controller) {
|
||||
throw new Error('Request Error');
|
||||
},
|
||||
response: usedSpy,
|
||||
});
|
||||
|
||||
await expect(http.fetch('/my/path')).resolves.toEqual({ foo: 'bar' });
|
||||
expect(usedSpy).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -40,6 +40,14 @@ import { BasePath } from './base_path_service';
|
|||
const JSON_CONTENT = /^(application\/(json|x-javascript)|text\/(x-)?javascript|x-json)(;.*)?$/;
|
||||
const NDJSON_CONTENT = /^(application\/ndjson)(;.*)?$/;
|
||||
|
||||
function checkHalt(controller: HttpInterceptController, error?: Error) {
|
||||
if (error instanceof HttpInterceptHaltError) {
|
||||
throw error;
|
||||
} else if (controller.halted) {
|
||||
throw new HttpInterceptHaltError();
|
||||
}
|
||||
}
|
||||
|
||||
export const setup = (
|
||||
injectedMetadata: InjectedMetadataSetup,
|
||||
fatalErrors: FatalErrorsSetup | null
|
||||
|
@ -102,9 +110,7 @@ export const setup = (
|
|||
(promise, interceptor) =>
|
||||
promise.then(
|
||||
async (current: Request) => {
|
||||
if (controller.halted) {
|
||||
throw new HttpInterceptHaltError();
|
||||
}
|
||||
checkHalt(controller);
|
||||
|
||||
if (!interceptor.request) {
|
||||
return current;
|
||||
|
@ -115,11 +121,7 @@ export const setup = (
|
|||
return next;
|
||||
},
|
||||
async error => {
|
||||
if (error instanceof HttpInterceptHaltError) {
|
||||
throw error;
|
||||
} else if (controller.halted) {
|
||||
throw new HttpInterceptHaltError();
|
||||
}
|
||||
checkHalt(controller, error);
|
||||
|
||||
if (!interceptor.requestError) {
|
||||
throw error;
|
||||
|
@ -147,15 +149,13 @@ export const setup = (
|
|||
responsePromise: Promise<HttpResponse>,
|
||||
controller: HttpInterceptController
|
||||
) {
|
||||
let current: HttpResponse;
|
||||
let current: HttpResponse | undefined;
|
||||
|
||||
const finalHttpResponse = await [...interceptors].reduce(
|
||||
(promise, interceptor) =>
|
||||
promise.then(
|
||||
async httpResponse => {
|
||||
if (controller.halted) {
|
||||
throw new HttpInterceptHaltError();
|
||||
}
|
||||
checkHalt(controller);
|
||||
|
||||
if (!interceptor.response) {
|
||||
return httpResponse;
|
||||
|
@ -166,26 +166,40 @@ export const setup = (
|
|||
return current;
|
||||
},
|
||||
async error => {
|
||||
if (error instanceof HttpInterceptHaltError) {
|
||||
throw error;
|
||||
} else if (controller.halted) {
|
||||
throw new HttpInterceptHaltError();
|
||||
}
|
||||
checkHalt(controller, error);
|
||||
|
||||
if (!interceptor.responseError) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
const next = await interceptor.responseError({ ...current, error }, controller);
|
||||
try {
|
||||
const next = await interceptor.responseError(
|
||||
{
|
||||
error,
|
||||
request: error.request || (current && current.request),
|
||||
response: error.response || (current && current.response),
|
||||
body: error.body || (current && current.body),
|
||||
},
|
||||
controller
|
||||
);
|
||||
|
||||
if (!next) {
|
||||
throw error;
|
||||
checkHalt(controller, error);
|
||||
|
||||
if (!next) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return next;
|
||||
} catch (err) {
|
||||
checkHalt(controller, err);
|
||||
throw err;
|
||||
}
|
||||
|
||||
return next;
|
||||
}
|
||||
),
|
||||
responsePromise
|
||||
responsePromise.then(httpResponse => {
|
||||
current = httpResponse;
|
||||
return httpResponse;
|
||||
})
|
||||
);
|
||||
|
||||
return finalHttpResponse.body;
|
||||
|
@ -198,7 +212,7 @@ export const setup = (
|
|||
try {
|
||||
response = await window.fetch(request);
|
||||
} catch (err) {
|
||||
throw new HttpFetchError(err.message);
|
||||
throw new HttpFetchError(err.message, request);
|
||||
}
|
||||
|
||||
const contentType = response.headers.get('Content-Type') || '';
|
||||
|
@ -218,24 +232,36 @@ export const setup = (
|
|||
}
|
||||
}
|
||||
} catch (err) {
|
||||
throw new HttpFetchError(err.message, response, body);
|
||||
throw new HttpFetchError(err.message, request, response, body);
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
throw new HttpFetchError(response.statusText, response, body);
|
||||
throw new HttpFetchError(response.statusText, request, response, body);
|
||||
}
|
||||
|
||||
return { response, body, request };
|
||||
}
|
||||
|
||||
function fetch(path: string, options: HttpFetchOptions = {}) {
|
||||
async function fetch(path: string, options: HttpFetchOptions = {}) {
|
||||
const controller = new HttpInterceptController();
|
||||
const initialRequest = createRequest(path, options);
|
||||
|
||||
return interceptResponse(
|
||||
interceptRequest(initialRequest, controller).then(fetcher),
|
||||
controller
|
||||
);
|
||||
// We wrap the interception in a separate promise to ensure that when
|
||||
// a halt is called we do not resolve or reject, halting handling of the promise.
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const value = await interceptResponse(
|
||||
interceptRequest(initialRequest, controller).then(fetcher),
|
||||
controller
|
||||
);
|
||||
|
||||
resolve(value);
|
||||
} catch (err) {
|
||||
if (!(err instanceof HttpInterceptHaltError)) {
|
||||
reject(err);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function shorthand(method: string) {
|
||||
|
|
|
@ -89,17 +89,20 @@ export type HttpHandler = (path: string, options?: HttpFetchOptions) => Promise<
|
|||
export type HttpBody = BodyInit | null | any;
|
||||
/** @public */
|
||||
export interface HttpResponse {
|
||||
request: Request;
|
||||
request?: Request;
|
||||
response?: Response;
|
||||
body?: HttpBody;
|
||||
}
|
||||
/** @public */
|
||||
export interface HttpErrorResponse extends HttpResponse {
|
||||
export interface HttpErrorResponse {
|
||||
error: Error | HttpFetchError;
|
||||
request?: Request;
|
||||
response?: Response;
|
||||
body?: HttpBody;
|
||||
}
|
||||
/** @public */
|
||||
export interface HttpErrorRequest {
|
||||
request?: Request;
|
||||
request: Request;
|
||||
error: Error;
|
||||
}
|
||||
/** @public */
|
||||
|
|
|
@ -105,6 +105,7 @@ export {
|
|||
HttpResponse,
|
||||
HttpHandler,
|
||||
HttpBody,
|
||||
HttpInterceptController,
|
||||
} from './http';
|
||||
|
||||
export {
|
||||
|
|
|
@ -39,7 +39,9 @@ describe('FlyoutService', () => {
|
|||
expect(mockReactDomRender.mock.calls).toMatchSnapshot();
|
||||
});
|
||||
describe('with a currently active flyout', () => {
|
||||
let target: HTMLElement, flyoutService: FlyoutService, ref1: FlyoutRef;
|
||||
let target: HTMLElement;
|
||||
let flyoutService: FlyoutService;
|
||||
let ref1: FlyoutRef;
|
||||
beforeEach(() => {
|
||||
target = document.createElement('div');
|
||||
flyoutService = new FlyoutService(target);
|
||||
|
|
|
@ -39,7 +39,9 @@ describe('ModalService', () => {
|
|||
expect(mockReactDomRender.mock.calls).toMatchSnapshot();
|
||||
});
|
||||
describe('with a currently active modal', () => {
|
||||
let target: HTMLElement, modalService: ModalService, ref1: ModalRef;
|
||||
let target: HTMLElement;
|
||||
let modalService: ModalService;
|
||||
let ref1: ModalRef;
|
||||
beforeEach(() => {
|
||||
target = document.createElement('div');
|
||||
modalService = new ModalService(target);
|
||||
|
|
|
@ -20,8 +20,11 @@
|
|||
import { CoreWindow, loadPluginBundle } from './plugin_loader';
|
||||
|
||||
let createdScriptTags = [] as any[];
|
||||
let appendChildSpy: jest.Mock<Node, [Node]>;
|
||||
let createElementSpy: jest.Mock<HTMLElement, [string, (ElementCreationOptions | undefined)?]>;
|
||||
let appendChildSpy: jest.SpyInstance<Node, [Node]>;
|
||||
let createElementSpy: jest.SpyInstance<
|
||||
HTMLElement,
|
||||
[string, (ElementCreationOptions | undefined)?]
|
||||
>;
|
||||
|
||||
const coreWindow = (window as unknown) as CoreWindow;
|
||||
|
||||
|
|
|
@ -397,15 +397,21 @@ export interface HttpErrorRequest {
|
|||
// (undocumented)
|
||||
error: Error;
|
||||
// (undocumented)
|
||||
request?: Request;
|
||||
request: Request;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface HttpErrorResponse extends HttpResponse {
|
||||
export interface HttpErrorResponse {
|
||||
// (undocumented)
|
||||
body?: HttpBody;
|
||||
// Warning: (ae-forgotten-export) The symbol "HttpFetchError" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// (undocumented)
|
||||
error: Error | HttpFetchError;
|
||||
// (undocumented)
|
||||
request?: Request;
|
||||
// (undocumented)
|
||||
response?: Response;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
|
@ -433,10 +439,18 @@ export interface HttpHeadersInit {
|
|||
[name: string]: any;
|
||||
}
|
||||
|
||||
// Warning: (ae-missing-release-tag) "HttpInterceptController" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export class HttpInterceptController {
|
||||
// (undocumented)
|
||||
halt(): void;
|
||||
// (undocumented)
|
||||
readonly halted: boolean;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface HttpInterceptor {
|
||||
// Warning: (ae-forgotten-export) The symbol "HttpInterceptController" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// (undocumented)
|
||||
request?(request: Request, controller: HttpInterceptController): Promise<Request> | Request | void;
|
||||
// (undocumented)
|
||||
|
@ -482,7 +496,7 @@ export interface HttpResponse {
|
|||
// (undocumented)
|
||||
body?: HttpBody;
|
||||
// (undocumented)
|
||||
request: Request;
|
||||
request?: Request;
|
||||
// (undocumented)
|
||||
response?: Response;
|
||||
}
|
||||
|
|
|
@ -52,7 +52,7 @@ const createSetupContractMock = () => {
|
|||
route: jest.fn(),
|
||||
start: jest.fn(),
|
||||
stop: jest.fn(),
|
||||
} as unknown) as Server,
|
||||
} as unknown) as jest.MockedClass<Server>,
|
||||
createCookieSessionStorageFactory: jest.fn(),
|
||||
registerOnPreAuth: jest.fn(),
|
||||
registerAuth: jest.fn(),
|
||||
|
|
|
@ -20,15 +20,16 @@
|
|||
import { SavedObjectsClientContract } from '../types';
|
||||
import { SavedObjectsErrorHelpers } from './lib/errors';
|
||||
|
||||
const create = (): jest.Mocked<SavedObjectsClientContract> => ({
|
||||
errors: SavedObjectsErrorHelpers,
|
||||
create: jest.fn(),
|
||||
bulkCreate: jest.fn(),
|
||||
delete: jest.fn(),
|
||||
bulkGet: jest.fn(),
|
||||
find: jest.fn(),
|
||||
get: jest.fn(),
|
||||
update: jest.fn(),
|
||||
});
|
||||
const create = () =>
|
||||
(({
|
||||
errors: SavedObjectsErrorHelpers,
|
||||
create: jest.fn(),
|
||||
bulkCreate: jest.fn(),
|
||||
delete: jest.fn(),
|
||||
bulkGet: jest.fn(),
|
||||
find: jest.fn(),
|
||||
get: jest.fn(),
|
||||
update: jest.fn(),
|
||||
} as unknown) as jest.Mocked<SavedObjectsClientContract>);
|
||||
|
||||
export const SavedObjectsClientMock = { create };
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
/* eslint-disable */
|
||||
|
||||
i18n('plugin_3.duplicate_id', { defaultMessage: 'Message 1' });
|
||||
|
||||
i18n.translate('plugin_3.duplicate_id', {
|
||||
defaultMessage: 'Message 2',
|
||||
description: 'Message description',
|
||||
});
|
|
@ -23,7 +23,7 @@ import { resolve } from 'path';
|
|||
import { normalizePath, readFileAsync } from '.';
|
||||
|
||||
export interface I18nConfig {
|
||||
paths: Record<string, string>;
|
||||
paths: Record<string, string[]>;
|
||||
exclude: string[];
|
||||
translations: string[];
|
||||
prefix?: string;
|
||||
|
@ -49,8 +49,9 @@ export async function assignConfigFromPath(
|
|||
...JSON.parse(await readFileAsync(resolve(configPath))),
|
||||
};
|
||||
|
||||
for (const [namespace, path] of Object.entries(additionalConfig.paths)) {
|
||||
config.paths[namespace] = normalizePath(resolve(configPath, '..', path));
|
||||
for (const [namespace, namespacePaths] of Object.entries(additionalConfig.paths)) {
|
||||
const paths = Array.isArray(namespacePaths) ? namespacePaths : [namespacePaths];
|
||||
config.paths[namespace] = paths.map(path => normalizePath(resolve(configPath, '..', path)));
|
||||
}
|
||||
|
||||
for (const exclude of additionalConfig.exclude) {
|
||||
|
@ -71,7 +72,7 @@ export async function assignConfigFromPath(
|
|||
* @param config I18n config instance.
|
||||
*/
|
||||
export function filterConfigPaths(inputPaths: string[], config: I18nConfig) {
|
||||
const availablePaths = Object.values(config.paths);
|
||||
const availablePaths = Object.values(config.paths).flat();
|
||||
const pathsForExtraction = new Set();
|
||||
|
||||
for (const inputPath of inputPaths) {
|
||||
|
|
|
@ -50,8 +50,8 @@ function filterEntries(entries, exclude) {
|
|||
export function validateMessageNamespace(id, filePath, allowedPaths, reporter) {
|
||||
const normalizedPath = normalizePath(filePath);
|
||||
|
||||
const [expectedNamespace] = Object.entries(allowedPaths).find(([, pluginPath]) =>
|
||||
normalizedPath.startsWith(`${pluginPath}/`)
|
||||
const [expectedNamespace] = Object.entries(allowedPaths).find(([, pluginPaths]) =>
|
||||
pluginPaths.some(pluginPath => normalizedPath.startsWith(`${pluginPath}/`))
|
||||
);
|
||||
|
||||
if (!id.startsWith(`${expectedNamespace}.`)) {
|
||||
|
|
|
@ -30,13 +30,17 @@ const pluginsPaths = [
|
|||
path.join(fixturesPath, 'test_plugin_1'),
|
||||
path.join(fixturesPath, 'test_plugin_2'),
|
||||
path.join(fixturesPath, 'test_plugin_3'),
|
||||
path.join(fixturesPath, 'test_plugin_3_additional_path'),
|
||||
];
|
||||
|
||||
const config = {
|
||||
paths: {
|
||||
plugin_1: 'src/dev/i18n/__fixtures__/extract_default_translations/test_plugin_1',
|
||||
plugin_2: 'src/dev/i18n/__fixtures__/extract_default_translations/test_plugin_2',
|
||||
plugin_3: 'src/dev/i18n/__fixtures__/extract_default_translations/test_plugin_3',
|
||||
plugin_1: ['src/dev/i18n/__fixtures__/extract_default_translations/test_plugin_1'],
|
||||
plugin_2: ['src/dev/i18n/__fixtures__/extract_default_translations/test_plugin_2'],
|
||||
plugin_3: [
|
||||
'src/dev/i18n/__fixtures__/extract_default_translations/test_plugin_3',
|
||||
'src/dev/i18n/__fixtures__/extract_default_translations/test_plugin_3_additional_path'
|
||||
],
|
||||
},
|
||||
exclude: [],
|
||||
};
|
||||
|
@ -69,6 +73,20 @@ describe('dev/i18n/extract_default_translations', () => {
|
|||
expect(() => validateMessageNamespace(id, filePath, config.paths)).not.toThrow();
|
||||
});
|
||||
|
||||
test('validates message namespace with multiple paths', () => {
|
||||
const id = 'plugin_3.message-id';
|
||||
const filePath1 = path.resolve(
|
||||
__dirname,
|
||||
'__fixtures__/extract_default_translations/test_plugin_3/test_file.html'
|
||||
);
|
||||
const filePath2 = path.resolve(
|
||||
__dirname,
|
||||
'__fixtures__/extract_default_translations/test_plugin_3_additional_path/test_file.html'
|
||||
);
|
||||
expect(() => validateMessageNamespace(id, filePath1, config.paths)).not.toThrow();
|
||||
expect(() => validateMessageNamespace(id, filePath2, config.paths)).not.toThrow();
|
||||
});
|
||||
|
||||
test('throws on wrong message namespace', () => {
|
||||
const report = jest.fn();
|
||||
const id = 'wrong_plugin_namespace.message-id';
|
||||
|
|
|
@ -40,8 +40,8 @@ const defaultIntegrateOptions = {
|
|||
ignoreUnused: false,
|
||||
config: {
|
||||
paths: {
|
||||
'plugin-1': 'src/dev/i18n/__fixtures__/integrate_locale_files/test_plugin_1',
|
||||
'plugin-2': 'src/dev/i18n/__fixtures__/integrate_locale_files/test_plugin_2',
|
||||
'plugin-1': ['src/dev/i18n/__fixtures__/integrate_locale_files/test_plugin_1'],
|
||||
'plugin-2': ['src/dev/i18n/__fixtures__/integrate_locale_files/test_plugin_2'],
|
||||
},
|
||||
exclude: [],
|
||||
translations: [],
|
||||
|
|
|
@ -160,17 +160,19 @@ async function writeMessages(
|
|||
// Use basename of source file name to write the same locale name as the source file has.
|
||||
const fileName = path.basename(options.sourceFileName);
|
||||
for (const [namespace, messages] of localizedMessagesByNamespace) {
|
||||
const destPath = path.resolve(options.config.paths[namespace], 'translations');
|
||||
for (const namespacedPath of options.config.paths[namespace]) {
|
||||
const destPath = path.resolve(namespacedPath, 'translations');
|
||||
|
||||
try {
|
||||
await accessAsync(destPath);
|
||||
} catch (_) {
|
||||
await makeDirAsync(destPath);
|
||||
try {
|
||||
await accessAsync(destPath);
|
||||
} catch (_) {
|
||||
await makeDirAsync(destPath);
|
||||
}
|
||||
|
||||
const writePath = path.resolve(destPath, fileName);
|
||||
await writeFileAsync(writePath, serializeToJson(messages, formats));
|
||||
options.log.success(`Translations have been integrated to ${normalizePath(writePath)}`);
|
||||
}
|
||||
|
||||
const writePath = path.resolve(destPath, fileName);
|
||||
await writeFileAsync(writePath, serializeToJson(messages, formats));
|
||||
options.log.success(`Translations have been integrated to ${normalizePath(writePath)}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ export async function extractUntrackedMessagesTask({
|
|||
reporter: any;
|
||||
}) {
|
||||
const inputPaths = Array.isArray(path) ? path : [path || './'];
|
||||
const availablePaths = Object.values(config.paths);
|
||||
const availablePaths = Object.values(config.paths).flat();
|
||||
const ignore = availablePaths.concat([
|
||||
'**/build/**',
|
||||
'**/webpackShims/**',
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { castEsToKbnFieldTypeName } from '../legacy/utils';
|
||||
import { castEsToKbnFieldTypeName } from '../plugins/data/common';
|
||||
// eslint-disable-next-line max-len
|
||||
import { shouldReadFieldFromDocValues } from '../legacy/server/index_patterns/service/lib/field_capabilities/should_read_field_from_doc_values';
|
||||
|
||||
|
|
|
@ -19,7 +19,8 @@
|
|||
|
||||
import TestUtilsStubIndexPatternProvider from 'test_utils/stub_index_pattern';
|
||||
import stubbedLogstashFields from 'fixtures/logstash_fields';
|
||||
import { getKbnFieldType } from '../legacy/utils';
|
||||
|
||||
import { getKbnFieldType } from '../plugins/data/common';
|
||||
|
||||
export default function stubbedLogstashIndexPatternService(Private) {
|
||||
const StubIndexPattern = Private(TestUtilsStubIndexPatternProvider);
|
||||
|
@ -28,7 +29,7 @@ export default function stubbedLogstashIndexPatternService(Private) {
|
|||
const fields = mockLogstashFields.map(function (field) {
|
||||
const kbnType = getKbnFieldType(field.type);
|
||||
|
||||
if (kbnType.name === 'unknown') {
|
||||
if (!kbnType || kbnType.name === 'unknown') {
|
||||
throw new TypeError(`unknown type ${field.type}`);
|
||||
}
|
||||
|
||||
|
|
|
@ -19,14 +19,7 @@
|
|||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import {
|
||||
EuiGlobalToastList,
|
||||
EuiGlobalToastListToast as Toast,
|
||||
EuiButtonIcon,
|
||||
EuiContextMenuPanel,
|
||||
EuiContextMenuItem,
|
||||
EuiPopover,
|
||||
} from '@elastic/eui';
|
||||
import { EuiButtonIcon, EuiContextMenuPanel, EuiContextMenuItem, EuiPopover } from '@elastic/eui';
|
||||
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
@ -35,13 +28,12 @@ interface Props {
|
|||
getCurl: (cb: (text: string) => void) => void;
|
||||
getDocumentation: () => Promise<string | null>;
|
||||
autoIndent: (ev?: React.MouseEvent) => void;
|
||||
addNotification?: (opts: { title: string }) => void;
|
||||
}
|
||||
|
||||
interface State {
|
||||
isPopoverOpen: boolean;
|
||||
curlCode: string;
|
||||
toasts: Toast[];
|
||||
toastId: number;
|
||||
}
|
||||
|
||||
export class ConsoleMenu extends Component<Props, State> {
|
||||
|
@ -51,8 +43,6 @@ export class ConsoleMenu extends Component<Props, State> {
|
|||
this.state = {
|
||||
curlCode: '',
|
||||
isPopoverOpen: false,
|
||||
toasts: [],
|
||||
toastId: 1,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -65,6 +55,14 @@ export class ConsoleMenu extends Component<Props, State> {
|
|||
|
||||
copyAsCurl() {
|
||||
this.copyText(this.state.curlCode);
|
||||
const { addNotification } = this.props;
|
||||
if (addNotification) {
|
||||
addNotification({
|
||||
title: i18n.translate('console.consoleMenu.copyAsCurlMessage', {
|
||||
defaultMessage: 'Request copied as cURL',
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
copyText(text: string) {
|
||||
|
@ -74,7 +72,6 @@ export class ConsoleMenu extends Component<Props, State> {
|
|||
textField.select();
|
||||
document.execCommand('copy');
|
||||
textField.remove();
|
||||
this.addCopyToast();
|
||||
}
|
||||
|
||||
onButtonClick = () => {
|
||||
|
@ -104,26 +101,6 @@ export class ConsoleMenu extends Component<Props, State> {
|
|||
this.props.autoIndent(event);
|
||||
};
|
||||
|
||||
addCopyToast = () => {
|
||||
const toast: Toast = {
|
||||
id: String(this.state.toastId),
|
||||
title: i18n.translate('console.consoleMenu.copyRequestAsCurlMessage', {
|
||||
defaultMessage: 'Request copied as cURL',
|
||||
}),
|
||||
color: 'success',
|
||||
};
|
||||
|
||||
this.setState({
|
||||
toastId: this.state.toastId + 1,
|
||||
toasts: this.state.toasts.concat(toast),
|
||||
});
|
||||
};
|
||||
|
||||
removeCopyToast = (removedToast: Toast) => {
|
||||
const newToastsArray = this.state.toasts.filter(tst => tst.id !== removedToast.id);
|
||||
this.setState({ toasts: newToastsArray });
|
||||
};
|
||||
|
||||
render() {
|
||||
const button = (
|
||||
<EuiButtonIcon
|
||||
|
@ -174,26 +151,17 @@ export class ConsoleMenu extends Component<Props, State> {
|
|||
];
|
||||
|
||||
return (
|
||||
<span>
|
||||
<span onMouseEnter={this.mouseEnter}>
|
||||
<EuiPopover
|
||||
id="contextMenu"
|
||||
button={button}
|
||||
isOpen={this.state.isPopoverOpen}
|
||||
closePopover={this.closePopover}
|
||||
panelPaddingSize="none"
|
||||
anchorPosition="downLeft"
|
||||
>
|
||||
<EuiContextMenuPanel items={items} />
|
||||
</EuiPopover>
|
||||
</span>
|
||||
<span>
|
||||
<EuiGlobalToastList
|
||||
toasts={this.state.toasts}
|
||||
dismissToast={this.removeCopyToast}
|
||||
toastLifeTimeMs={6000}
|
||||
/>
|
||||
</span>
|
||||
<span onMouseEnter={this.mouseEnter}>
|
||||
<EuiPopover
|
||||
id="contextMenu"
|
||||
button={button}
|
||||
isOpen={this.state.isPopoverOpen}
|
||||
closePopover={this.closePopover}
|
||||
panelPaddingSize="none"
|
||||
anchorPosition="downLeft"
|
||||
>
|
||||
<EuiContextMenuPanel items={items} />
|
||||
</EuiPopover>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ const DEFAULT_INPUT_VALUE = `GET _search
|
|||
|
||||
function _Editor({ previousStateLocation = 'stored' }: EditorProps) {
|
||||
const {
|
||||
services: { history },
|
||||
services: { history, notifications },
|
||||
ResizeChecker,
|
||||
docLinkVersion,
|
||||
} = useAppContext();
|
||||
|
@ -197,6 +197,7 @@ function _Editor({ previousStateLocation = 'stored' }: EditorProps) {
|
|||
autoIndent={(event: any) => {
|
||||
autoIndent(editorInstanceRef.current!, event);
|
||||
}}
|
||||
addNotification={({ title }) => notifications.toasts.add({ title })}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
*/
|
||||
|
||||
import React, { createContext, useContext } from 'react';
|
||||
import { NotificationsSetup } from '../../../../../../../core/public';
|
||||
import { History, Storage, Settings } from '../../services';
|
||||
|
||||
interface ContextValue {
|
||||
|
@ -25,6 +26,7 @@ interface ContextValue {
|
|||
history: History;
|
||||
storage: Storage;
|
||||
settings: Settings;
|
||||
notifications: NotificationsSetup;
|
||||
};
|
||||
docLinkVersion: string;
|
||||
ResizeChecker: any;
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { NotificationsSetup } from '../../../../../../core/public';
|
||||
import { AppContextProvider } from './context';
|
||||
import { EditorContextProvider } from './containers/editor/context';
|
||||
import { Main } from './containers';
|
||||
|
@ -28,8 +29,13 @@ export function legacyBackDoorToSettings() {
|
|||
return settingsRef;
|
||||
}
|
||||
|
||||
export function boot(deps: { docLinkVersion: string; I18nContext: any; ResizeChecker: any }) {
|
||||
const { I18nContext, ResizeChecker } = deps;
|
||||
export function boot(deps: {
|
||||
docLinkVersion: string;
|
||||
I18nContext: any;
|
||||
ResizeChecker: any;
|
||||
notifications: NotificationsSetup;
|
||||
}) {
|
||||
const { I18nContext, ResizeChecker, notifications, docLinkVersion } = deps;
|
||||
|
||||
const storage = createStorage({
|
||||
engine: window.localStorage,
|
||||
|
@ -42,7 +48,11 @@ export function boot(deps: { docLinkVersion: string; I18nContext: any; ResizeChe
|
|||
return (
|
||||
<I18nContext>
|
||||
<AppContextProvider
|
||||
value={{ ...deps, services: { storage, history, settings }, ResizeChecker }}
|
||||
value={{
|
||||
docLinkVersion,
|
||||
services: { storage, history, settings, notifications },
|
||||
ResizeChecker,
|
||||
}}
|
||||
>
|
||||
<EditorContextProvider settings={settings.toJSON()}>
|
||||
<Main />
|
||||
|
|
|
@ -24,6 +24,7 @@ import 'brace/mode/json';
|
|||
import 'brace/mode/text';
|
||||
|
||||
/* eslint-disable @kbn/eslint/no-restricted-paths */
|
||||
import { toastNotifications as notifications } from 'ui/notify';
|
||||
import { npSetup, npStart } from 'ui/new_platform';
|
||||
import uiRoutes from 'ui/routes';
|
||||
import { DOC_LINK_VERSION } from 'ui/documentation_links';
|
||||
|
@ -34,7 +35,7 @@ import 'ui/capabilities/route_setup';
|
|||
/* eslint-enable @kbn/eslint/no-restricted-paths */
|
||||
|
||||
import template from '../../public/quarantined/index.html';
|
||||
import { App, AppUnmount } from '../../../../../core/public';
|
||||
import { App, AppUnmount, NotificationsSetup } from '../../../../../core/public';
|
||||
|
||||
export interface XPluginSet {
|
||||
__LEGACY: {
|
||||
|
@ -68,6 +69,7 @@ uiRoutes.when('/dev_tools/console', {
|
|||
|
||||
const mockedSetupCore = {
|
||||
...npSetup.core,
|
||||
notifications: (notifications as unknown) as NotificationsSetup,
|
||||
application: {
|
||||
register(app: App): void {
|
||||
try {
|
||||
|
|
|
@ -27,7 +27,7 @@ export class ConsoleUIPlugin implements Plugin<any, any> {
|
|||
// @ts-ignore
|
||||
constructor(private readonly ctx: PluginInitializerContext) {}
|
||||
|
||||
async setup({ application }: CoreSetup, pluginSet: XPluginSet) {
|
||||
async setup({ application, notifications }: CoreSetup, pluginSet: XPluginSet) {
|
||||
const {
|
||||
__LEGACY: { docLinkVersion, I18nContext, ResizeChecker },
|
||||
} = pluginSet;
|
||||
|
@ -37,7 +37,7 @@ export class ConsoleUIPlugin implements Plugin<any, any> {
|
|||
order: 1,
|
||||
title: 'Console',
|
||||
mount(ctx, { element }) {
|
||||
render(boot({ docLinkVersion, I18nContext, ResizeChecker }), element);
|
||||
render(boot({ docLinkVersion, I18nContext, ResizeChecker, notifications }), element);
|
||||
return () => {
|
||||
unmountComponentAtNode(element);
|
||||
};
|
||||
|
|
|
@ -8,15 +8,15 @@
|
|||
}
|
||||
|
||||
.globalFilterGroup__filterBar {
|
||||
margin-top: $euiSizeS;
|
||||
margin-top: $euiSizeXS;
|
||||
}
|
||||
|
||||
// sass-lint:disable quotes
|
||||
.globalFilterGroup__branch {
|
||||
padding: $euiSizeM $euiSizeM 0 0;
|
||||
padding: $euiSizeS $euiSizeM 0 0;
|
||||
background-repeat: no-repeat;
|
||||
background-position: $euiSizeM ($euiSizeXS * -1);
|
||||
background-image: url("data:image/svg+xml,%0A%3Csvg width='28px' height='28px' viewBox='0 0 28 28' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='#{hexToRGB($euiColorLightShade)}'%3E%3Crect x='14' y='27' width='14' height='1'%3E%3C/rect%3E%3Crect x='0' y='0' width='1' height='14'%3E%3C/rect%3E%3C/g%3E%3C/svg%3E");
|
||||
background-position: $euiSizeM ($euiSizeS * -1);
|
||||
background-image: url("data:image/svg+xml,%0A%3Csvg width='28px' height='28px' viewBox='0 0 28 28' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='#{hexToRGB($euiColorLightShade)}'%3E%3Crect x='14' y='27' width='14' height='1'%3E%3C/rect%3E%3C/g%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.globalFilterGroup__wrapper {
|
||||
|
|
|
@ -30,95 +30,68 @@ import {
|
|||
} from '@kbn/es-query';
|
||||
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
|
||||
import classNames from 'classnames';
|
||||
import React, { Component } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { UiSettingsClientContract } from 'src/core/public';
|
||||
import { IndexPattern } from '../../index_patterns';
|
||||
import { FilterEditor } from './filter_editor';
|
||||
import { FilterItem } from './filter_item';
|
||||
import { FilterOptions } from './filter_options';
|
||||
import { useKibana } from '../../../../../../plugins/kibana_react/public';
|
||||
|
||||
interface Props {
|
||||
filters: Filter[];
|
||||
onFiltersUpdated: (filters: Filter[]) => void;
|
||||
onFiltersUpdated?: (filters: Filter[]) => void;
|
||||
className: string;
|
||||
indexPatterns: IndexPattern[];
|
||||
intl: InjectedIntl;
|
||||
uiSettings: UiSettingsClientContract;
|
||||
|
||||
// Only for directives!
|
||||
uiSettings?: UiSettingsClientContract;
|
||||
}
|
||||
|
||||
interface State {
|
||||
isAddFilterPopoverOpen: boolean;
|
||||
}
|
||||
function FilterBarUI(props: Props) {
|
||||
const [isAddFilterPopoverOpen, setIsAddFilterPopoverOpen] = useState(false);
|
||||
const kibana = useKibana();
|
||||
let { uiSettings } = kibana.services;
|
||||
|
||||
class FilterBarUI extends Component<Props, State> {
|
||||
public state = {
|
||||
isAddFilterPopoverOpen: false,
|
||||
};
|
||||
|
||||
public render() {
|
||||
if (!this.props.uiSettings) {
|
||||
return null;
|
||||
}
|
||||
const classes = classNames('globalFilterBar', this.props.className);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup
|
||||
className="globalFilterGroup"
|
||||
gutterSize="none"
|
||||
alignItems="flexStart"
|
||||
responsive={false}
|
||||
>
|
||||
<EuiFlexItem className="globalFilterGroup__branch" grow={false}>
|
||||
<FilterOptions
|
||||
onEnableAll={this.onEnableAll}
|
||||
onDisableAll={this.onDisableAll}
|
||||
onPinAll={this.onPinAll}
|
||||
onUnpinAll={this.onUnpinAll}
|
||||
onToggleAllNegated={this.onToggleAllNegated}
|
||||
onToggleAllDisabled={this.onToggleAllDisabled}
|
||||
onRemoveAll={this.onRemoveAll}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem className="globalFilterGroup__filterFlexItem">
|
||||
<EuiFlexGroup
|
||||
className={classes}
|
||||
wrap={true}
|
||||
responsive={false}
|
||||
gutterSize="xs"
|
||||
alignItems="center"
|
||||
>
|
||||
{this.renderItems()}
|
||||
{this.renderAddFilter()}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
if (!uiSettings) {
|
||||
// Only for directives!
|
||||
uiSettings = props.uiSettings;
|
||||
}
|
||||
|
||||
private renderItems() {
|
||||
return this.props.filters.map((filter, i) => (
|
||||
function onFiltersUpdated(filters: Filter[]) {
|
||||
if (props.onFiltersUpdated) {
|
||||
props.onFiltersUpdated(filters);
|
||||
}
|
||||
}
|
||||
|
||||
function renderItems() {
|
||||
return props.filters.map((filter, i) => (
|
||||
<EuiFlexItem key={i} grow={false} className="globalFilterBar__flexItem">
|
||||
<FilterItem
|
||||
id={`${i}`}
|
||||
filter={filter}
|
||||
onUpdate={newFilter => this.onUpdate(i, newFilter)}
|
||||
onRemove={() => this.onRemove(i)}
|
||||
indexPatterns={this.props.indexPatterns}
|
||||
uiSettings={this.props.uiSettings}
|
||||
onUpdate={newFilter => onUpdate(i, newFilter)}
|
||||
onRemove={() => onRemove(i)}
|
||||
indexPatterns={props.indexPatterns}
|
||||
uiSettings={uiSettings!}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
));
|
||||
}
|
||||
|
||||
private renderAddFilter() {
|
||||
const isPinned = this.props.uiSettings.get('filters:pinnedByDefault');
|
||||
const [indexPattern] = this.props.indexPatterns;
|
||||
function renderAddFilter() {
|
||||
const isPinned = uiSettings!.get('filters:pinnedByDefault');
|
||||
const [indexPattern] = props.indexPatterns;
|
||||
const index = indexPattern && indexPattern.id;
|
||||
const newFilter = buildEmptyFilter(isPinned, index);
|
||||
|
||||
const button = (
|
||||
<EuiButtonEmpty size="xs" onClick={this.onOpenAddFilterPopover} data-test-subj="addFilter">
|
||||
<EuiButtonEmpty
|
||||
size="xs"
|
||||
onClick={() => setIsAddFilterPopoverOpen(true)}
|
||||
data-test-subj="addFilter"
|
||||
>
|
||||
+{' '}
|
||||
<FormattedMessage
|
||||
id="data.filter.filterBar.addFilterButtonLabel"
|
||||
|
@ -132,8 +105,8 @@ class FilterBarUI extends Component<Props, State> {
|
|||
<EuiPopover
|
||||
id="addFilterPopover"
|
||||
button={button}
|
||||
isOpen={this.state.isAddFilterPopoverOpen}
|
||||
closePopover={this.onCloseAddFilterPopover}
|
||||
isOpen={isAddFilterPopoverOpen}
|
||||
closePopover={() => setIsAddFilterPopoverOpen(false)}
|
||||
anchorPosition="downLeft"
|
||||
withTitle
|
||||
panelPaddingSize="none"
|
||||
|
@ -143,11 +116,11 @@ class FilterBarUI extends Component<Props, State> {
|
|||
<div style={{ width: 400 }}>
|
||||
<FilterEditor
|
||||
filter={newFilter}
|
||||
indexPatterns={this.props.indexPatterns}
|
||||
onSubmit={this.onAdd}
|
||||
onCancel={this.onCloseAddFilterPopover}
|
||||
indexPatterns={props.indexPatterns}
|
||||
onSubmit={onAdd}
|
||||
onCancel={() => setIsAddFilterPopoverOpen(false)}
|
||||
key={JSON.stringify(newFilter)}
|
||||
uiSettings={this.props.uiSettings}
|
||||
uiSettings={uiSettings!}
|
||||
/>
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
|
@ -156,69 +129,93 @@ class FilterBarUI extends Component<Props, State> {
|
|||
);
|
||||
}
|
||||
|
||||
private onAdd = (filter: Filter) => {
|
||||
this.onCloseAddFilterPopover();
|
||||
const filters = [...this.props.filters, filter];
|
||||
this.props.onFiltersUpdated(filters);
|
||||
};
|
||||
function onAdd(filter: Filter) {
|
||||
setIsAddFilterPopoverOpen(false);
|
||||
const filters = [...props.filters, filter];
|
||||
onFiltersUpdated(filters);
|
||||
}
|
||||
|
||||
private onRemove = (i: number) => {
|
||||
const filters = [...this.props.filters];
|
||||
function onRemove(i: number) {
|
||||
const filters = [...props.filters];
|
||||
filters.splice(i, 1);
|
||||
this.props.onFiltersUpdated(filters);
|
||||
};
|
||||
onFiltersUpdated(filters);
|
||||
}
|
||||
|
||||
private onUpdate = (i: number, filter: Filter) => {
|
||||
const filters = [...this.props.filters];
|
||||
function onUpdate(i: number, filter: Filter) {
|
||||
const filters = [...props.filters];
|
||||
filters[i] = filter;
|
||||
this.props.onFiltersUpdated(filters);
|
||||
};
|
||||
onFiltersUpdated(filters);
|
||||
}
|
||||
|
||||
private onEnableAll = () => {
|
||||
const filters = this.props.filters.map(enableFilter);
|
||||
this.props.onFiltersUpdated(filters);
|
||||
};
|
||||
function onEnableAll() {
|
||||
const filters = props.filters.map(enableFilter);
|
||||
onFiltersUpdated(filters);
|
||||
}
|
||||
|
||||
private onDisableAll = () => {
|
||||
const filters = this.props.filters.map(disableFilter);
|
||||
this.props.onFiltersUpdated(filters);
|
||||
};
|
||||
function onDisableAll() {
|
||||
const filters = props.filters.map(disableFilter);
|
||||
onFiltersUpdated(filters);
|
||||
}
|
||||
|
||||
private onPinAll = () => {
|
||||
const filters = this.props.filters.map(pinFilter);
|
||||
this.props.onFiltersUpdated(filters);
|
||||
};
|
||||
function onPinAll() {
|
||||
const filters = props.filters.map(pinFilter);
|
||||
onFiltersUpdated(filters);
|
||||
}
|
||||
|
||||
private onUnpinAll = () => {
|
||||
const filters = this.props.filters.map(unpinFilter);
|
||||
this.props.onFiltersUpdated(filters);
|
||||
};
|
||||
function onUnpinAll() {
|
||||
const filters = props.filters.map(unpinFilter);
|
||||
onFiltersUpdated(filters);
|
||||
}
|
||||
|
||||
private onToggleAllNegated = () => {
|
||||
const filters = this.props.filters.map(toggleFilterNegated);
|
||||
this.props.onFiltersUpdated(filters);
|
||||
};
|
||||
function onToggleAllNegated() {
|
||||
const filters = props.filters.map(toggleFilterNegated);
|
||||
onFiltersUpdated(filters);
|
||||
}
|
||||
|
||||
private onToggleAllDisabled = () => {
|
||||
const filters = this.props.filters.map(toggleFilterDisabled);
|
||||
this.props.onFiltersUpdated(filters);
|
||||
};
|
||||
function onToggleAllDisabled() {
|
||||
const filters = props.filters.map(toggleFilterDisabled);
|
||||
onFiltersUpdated(filters);
|
||||
}
|
||||
|
||||
private onRemoveAll = () => {
|
||||
this.props.onFiltersUpdated([]);
|
||||
};
|
||||
function onRemoveAll() {
|
||||
onFiltersUpdated([]);
|
||||
}
|
||||
|
||||
private onOpenAddFilterPopover = () => {
|
||||
this.setState({
|
||||
isAddFilterPopoverOpen: true,
|
||||
});
|
||||
};
|
||||
const classes = classNames('globalFilterBar', props.className);
|
||||
|
||||
private onCloseAddFilterPopover = () => {
|
||||
this.setState({
|
||||
isAddFilterPopoverOpen: false,
|
||||
});
|
||||
};
|
||||
return (
|
||||
<EuiFlexGroup
|
||||
className="globalFilterGroup"
|
||||
gutterSize="none"
|
||||
alignItems="flexStart"
|
||||
responsive={false}
|
||||
>
|
||||
<EuiFlexItem className="globalFilterGroup__branch" grow={false}>
|
||||
<FilterOptions
|
||||
onEnableAll={onEnableAll}
|
||||
onDisableAll={onDisableAll}
|
||||
onPinAll={onPinAll}
|
||||
onUnpinAll={onUnpinAll}
|
||||
onToggleAllNegated={onToggleAllNegated}
|
||||
onToggleAllDisabled={onToggleAllDisabled}
|
||||
onRemoveAll={onRemoveAll}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem className="globalFilterGroup__filterFlexItem">
|
||||
<EuiFlexGroup
|
||||
className={classes}
|
||||
wrap={true}
|
||||
responsive={false}
|
||||
gutterSize="xs"
|
||||
alignItems="center"
|
||||
>
|
||||
{renderItems()}
|
||||
{renderAddFilter()}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
||||
|
||||
export const FilterBar = injectI18n(FilterBarUI);
|
||||
|
|
|
@ -233,8 +233,10 @@ class FilterEditorUI extends Component<Props, State> {
|
|||
return (
|
||||
<div>
|
||||
<EuiFlexGroup responsive={false} gutterSize="s">
|
||||
<EuiFlexItem style={{ maxWidth: '188px' }}>{this.renderFieldInput()}</EuiFlexItem>
|
||||
<EuiFlexItem style={{ maxWidth: '188px' }}>{this.renderOperatorInput()}</EuiFlexItem>
|
||||
<EuiFlexItem grow={2}>{this.renderFieldInput()}</EuiFlexItem>
|
||||
<EuiFlexItem grow={false} style={{ flexBasis: 160 }}>
|
||||
{this.renderOperatorInput()}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="s" />
|
||||
<div data-test-subj="filterParams">{this.renderParamsEditor()}</div>
|
||||
|
@ -257,7 +259,7 @@ class FilterEditorUI extends Component<Props, State> {
|
|||
isDisabled={!selectedIndexPattern}
|
||||
placeholder={this.props.intl.formatMessage({
|
||||
id: 'data.filter.filterEditor.fieldSelectPlaceholder',
|
||||
defaultMessage: 'Select a field',
|
||||
defaultMessage: 'Select a field first',
|
||||
})}
|
||||
options={fields}
|
||||
selectedOptions={selectedField ? [selectedField] : []}
|
||||
|
@ -283,10 +285,17 @@ class FilterEditorUI extends Component<Props, State> {
|
|||
>
|
||||
<OperatorComboBox
|
||||
isDisabled={!selectedField}
|
||||
placeholder={this.props.intl.formatMessage({
|
||||
id: 'data.filter.filterEditor.operatorSelectPlaceholder',
|
||||
defaultMessage: 'Select an operator',
|
||||
})}
|
||||
placeholder={
|
||||
selectedField
|
||||
? this.props.intl.formatMessage({
|
||||
id: 'data.filter.filterEditor.operatorSelectPlaceholderSelect',
|
||||
defaultMessage: 'Select',
|
||||
})
|
||||
: this.props.intl.formatMessage({
|
||||
id: 'data.filter.filterEditor.operatorSelectPlaceholderWaiting',
|
||||
defaultMessage: 'Waiting',
|
||||
})
|
||||
}
|
||||
options={operators}
|
||||
selectedOptions={selectedOperator ? [selectedOperator] : []}
|
||||
getLabel={({ message }) => message}
|
||||
|
|
|
@ -36,4 +36,5 @@ export const rangeFilter: RangeFilter = {
|
|||
$state: {
|
||||
store: FilterStateStore.APP_STATE,
|
||||
},
|
||||
range: {},
|
||||
};
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiIcon, EuiLink } from '@elastic/eui';
|
||||
import { EuiIcon, EuiLink, EuiFormHelpText, EuiFormControlLayoutDelimited } from '@elastic/eui';
|
||||
import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react';
|
||||
import { get } from 'lodash';
|
||||
import { Component } from 'react';
|
||||
|
@ -50,52 +50,46 @@ class RangeValueInputUI extends Component<Props> {
|
|||
|
||||
return (
|
||||
<div>
|
||||
<EuiFlexGroup style={{ maxWidth: 600 }}>
|
||||
<EuiFlexItem>
|
||||
<EuiFormRow
|
||||
label={this.props.intl.formatMessage({
|
||||
id: 'data.filter.filterEditor.rangeStartInputLabel',
|
||||
defaultMessage: 'From',
|
||||
<EuiFormControlLayoutDelimited
|
||||
aria-label={this.props.intl.formatMessage({
|
||||
id: 'data.filter.filterEditor.rangeInputLabel',
|
||||
defaultMessage: 'Range',
|
||||
})}
|
||||
startControl={
|
||||
<ValueInputType
|
||||
controlOnly
|
||||
type={type}
|
||||
value={this.props.value ? this.props.value.from : undefined}
|
||||
onChange={this.onFromChange}
|
||||
placeholder={this.props.intl.formatMessage({
|
||||
id: 'data.filter.filterEditor.rangeStartInputPlaceholder',
|
||||
defaultMessage: 'Start of the range',
|
||||
})}
|
||||
>
|
||||
<ValueInputType
|
||||
type={type}
|
||||
value={this.props.value ? this.props.value.from : undefined}
|
||||
onChange={this.onFromChange}
|
||||
placeholder={this.props.intl.formatMessage({
|
||||
id: 'data.filter.filterEditor.rangeStartInputPlaceholder',
|
||||
defaultMessage: 'Start of the range',
|
||||
})}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFormRow
|
||||
label={this.props.intl.formatMessage({
|
||||
id: 'data.filter.filterEditor.rangeEndInputLabel',
|
||||
defaultMessage: 'To',
|
||||
/>
|
||||
}
|
||||
endControl={
|
||||
<ValueInputType
|
||||
controlOnly
|
||||
type={type}
|
||||
value={this.props.value ? this.props.value.to : undefined}
|
||||
onChange={this.onToChange}
|
||||
placeholder={this.props.intl.formatMessage({
|
||||
id: 'data.filter.filterEditor.rangeEndInputPlaceholder',
|
||||
defaultMessage: 'End of the range',
|
||||
})}
|
||||
>
|
||||
<ValueInputType
|
||||
type={type}
|
||||
value={this.props.value ? this.props.value.to : undefined}
|
||||
onChange={this.onToChange}
|
||||
placeholder={this.props.intl.formatMessage({
|
||||
id: 'data.filter.filterEditor.rangeEndInputPlaceholder',
|
||||
defaultMessage: 'End of the range',
|
||||
})}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{type === 'date' ? (
|
||||
<EuiLink target="_blank" href={getDocLink('date.dateMath')}>
|
||||
<FormattedMessage
|
||||
id="data.filter.filterEditor.dateFormatHelpLinkLabel"
|
||||
defaultMessage="Accepted date formats"
|
||||
/>{' '}
|
||||
<EuiIcon type="link" />
|
||||
</EuiLink>
|
||||
<EuiFormHelpText>
|
||||
<EuiLink target="_blank" href={getDocLink('date.dateMath')}>
|
||||
<FormattedMessage
|
||||
id="data.filter.filterEditor.dateFormatHelpLinkLabel"
|
||||
defaultMessage="Accepted date formats"
|
||||
/>{' '}
|
||||
<EuiIcon type="popout" size="s" />
|
||||
</EuiLink>
|
||||
</EuiFormHelpText>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
|
|
|
@ -29,6 +29,8 @@ interface Props {
|
|||
onChange: (value: string | number | boolean) => void;
|
||||
placeholder: string;
|
||||
intl: InjectedIntl;
|
||||
controlOnly?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
class ValueInputTypeUI extends Component<Props> {
|
||||
|
@ -42,6 +44,8 @@ class ValueInputTypeUI extends Component<Props> {
|
|||
placeholder={this.props.placeholder}
|
||||
value={value}
|
||||
onChange={this.onChange}
|
||||
controlOnly={this.props.controlOnly}
|
||||
className={this.props.className}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
|
@ -51,6 +55,8 @@ class ValueInputTypeUI extends Component<Props> {
|
|||
placeholder={this.props.placeholder}
|
||||
value={typeof value === 'string' ? parseFloat(value) : value}
|
||||
onChange={this.onChange}
|
||||
controlOnly={this.props.controlOnly}
|
||||
className={this.props.className}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
|
@ -61,6 +67,8 @@ class ValueInputTypeUI extends Component<Props> {
|
|||
value={value}
|
||||
onChange={this.onChange}
|
||||
isInvalid={!isEmpty(value) && !validateParams(value, this.props.type)}
|
||||
controlOnly={this.props.controlOnly}
|
||||
className={this.props.className}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
|
@ -71,6 +79,8 @@ class ValueInputTypeUI extends Component<Props> {
|
|||
value={value}
|
||||
onChange={this.onChange}
|
||||
isInvalid={!isEmpty(value) && !validateParams(value, this.props.type)}
|
||||
controlOnly={this.props.controlOnly}
|
||||
className={this.props.className}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
|
@ -96,6 +106,7 @@ class ValueInputTypeUI extends Component<Props> {
|
|||
]}
|
||||
value={value}
|
||||
onChange={this.onBoolChange}
|
||||
className={this.props.className}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
|
|
|
@ -163,7 +163,7 @@ class FilterItemUI extends Component<Props, State> {
|
|||
},
|
||||
{
|
||||
id: 1,
|
||||
width: 400,
|
||||
width: 420,
|
||||
content: (
|
||||
<div>
|
||||
<FilterEditor
|
||||
|
|