diff --git a/.ci/es-snapshots/Jenkinsfile_verify_es b/.ci/es-snapshots/Jenkinsfile_verify_es index ade79f27e10e96..2655ca1b48c18b 100644 --- a/.ci/es-snapshots/Jenkinsfile_verify_es +++ b/.ci/es-snapshots/Jenkinsfile_verify_es @@ -21,6 +21,7 @@ def SNAPSHOT_MANIFEST = "https://storage.googleapis.com/kibana-ci-es-snapshots-d kibanaPipeline(timeoutMinutes: 120) { catchErrors { + retryable.enable(2) withEnv(["ES_SNAPSHOT_MANIFEST=${SNAPSHOT_MANIFEST}"]) { parallel([ 'kibana-intake-agent': workers.intake('kibana-intake', './test/scripts/jenkins_unit.sh'), diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index a008fa7ea9239f..6e616cf78c2065 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -8,8 +8,6 @@ /x-pack/plugins/graph/ @elastic/kibana-app /src/legacy/server/url_shortening/ @elastic/kibana-app /src/legacy/server/sample_data/ @elastic/kibana-app -/src/legacy/core_plugins/kibana/public/dashboard/ @elastic/kibana-app -/src/legacy/core_plugins/kibana/public/discover/ @elastic/kibana-app /src/legacy/core_plugins/kibana/public/local_application_service/ @elastic/kibana-app /src/legacy/core_plugins/kibana/public/dev_tools/ @elastic/kibana-app /src/plugins/dashboard/ @elastic/kibana-app diff --git a/docs/canvas/canvas-function-reference.asciidoc b/docs/canvas/canvas-function-reference.asciidoc index 16aaf55802b170..657e3ec8b8bb18 100644 --- a/docs/canvas/canvas-function-reference.asciidoc +++ b/docs/canvas/canvas-function-reference.asciidoc @@ -42,10 +42,10 @@ filters | metric "Average uptime" metricFont={ font size=48 family="'Open Sans', Helvetica, Arial, sans-serif" - color={ - if {all {gte 0} {lt 0.8}} then="red" else="green" - } - align="center" lHeight=48 + color={ + if {all {gte 0} {lt 0.8}} then="red" else="green" + } + align="center" lHeight=48 } | render ---- @@ -324,12 +324,14 @@ case if={lte 50} then="green" ---- math "random()" | progress shape="gauge" label={formatnumber "0%"} - font={font size=24 family="'Open Sans', Helvetica, Arial, sans-serif" align="center" - color={ - switch {case if={lte 0.5} then="green"} - {case if={all {gt 0.5} {lte 0.75}} then="orange"} - default="red" - }} + font={ + font size=24 family="'Open Sans', Helvetica, Arial, sans-serif" align="center" + color={ + switch {case if={lte 0.5} then="green"} + {case if={all {gt 0.5} {lte 0.75}} then="orange"} + default="red" + } + } valueColor={ switch {case if={lte 0.5} then="green"} {case if={all {gt 0.5} {lte 0.75}} then="orange"} @@ -693,7 +695,25 @@ Alias: `value` [[demodata_fn]] === `demodata` -A mock data set that includes project CI times with usernames, countries, and run phases. +A sample data set that includes project CI times with usernames, countries, and run phases. + +*Expression syntax* +[source,js] +---- +demodata +demodata "ci" +demodata type="shirts" +---- + +*Code example* +[source,text] +---- +filters +| demodata +| table +| render +---- +`demodata` is a mock data set that you can use to start playing around in Canvas. *Accepts:* `filter` @@ -837,6 +857,28 @@ Alias: `value` Query Elasticsearch for the number of hits matching the specified query. +*Expression syntax* +[source,js] +---- +escount index="logstash-*" +escount "currency:"EUR"" index="kibana_sample_data_ecommerce" +escount query="response:404" index="kibana_sample_data_logs" +---- + +*Code example* +[source,text] +---- +filters +| escount "Cancelled:true" index="kibana_sample_data_flights" +| math "value" +| progress shape="semicircle" + label={formatnumber 0,0} + font={font size=24 family="'Open Sans', Helvetica, Arial, sans-serif" color="#000000" align=center} + max={filters | escount index="kibana_sample_data_flights"} +| render +---- +The first `escount` expression retrieves the number of flights that were cancelled. The second `escount` expression retrieves the total number of flights. + *Accepts:* `filter` [cols="3*^<"] @@ -867,6 +909,34 @@ Default: `_all` Query Elasticsearch for raw documents. Specify the fields you want to retrieve, especially if you are asking for a lot of rows. +*Expression syntax* +[source,js] +---- +esdocs index="logstash-*" +esdocs "currency:"EUR"" index="kibana_sample_data_ecommerce" +esdocs query="response:404" index="kibana_sample_data_logs" +esdocs index="kibana_sample_data_flights" count=100 +esdocs index="kibana_sample_data_flights" sort="AvgTicketPrice, asc" +---- + +*Code example* +[source,text] +---- +filters +| esdocs index="kibana_sample_data_ecommerce" + fields="customer_gender, taxful_total_price, order_date" + sort="order_date, asc" + count=10000 +| mapColumn "order_date" + fn={getCell "order_date" | date {context} | rounddate "YYYY-MM-DD"} +| alterColumn "order_date" type="date" +| pointseries x="order_date" y="sum(taxful_total_price)" color="customer_gender" +| plot defaultStyle={seriesStyle lines=3} + palette={palette "#7ECAE3" "#003A4D" gradient=true} +| render +---- +This retrieves the first 10000 documents data from the `kibana_sample_data_ecommerce` index sorted by `order_date` in ascending order, and only requests the `customer_gender`, `taxful_total_price`, and `order_date` fields. + *Accepts:* `filter` [cols="3*^<"] @@ -915,6 +985,23 @@ Default: `_all` Queries Elasticsearch using Elasticsearch SQL. +*Expression syntax* +[source,js] +---- +essql query="SELECT * FROM "logstash*"" +essql "SELECT * FROM "apm*"" count=10000 +---- + +*Code example* +[source,text] +---- +filters +| essql query="SELECT Carrier, FlightDelayMin, AvgTicketPrice FROM "kibana_sample_data_flights"" +| table +| render +---- +This retrieves the `Carrier`, `FlightDelayMin`, and `AvgTicketPrice` fields from the "kibana_sample_data_flights" index. + *Accepts:* `filter` [cols="3*^<"] @@ -1107,7 +1194,7 @@ Default: `false` [[font_fn]] === `font` -Creates a font style. +Create a font style. *Expression syntax* [source,js] @@ -1244,7 +1331,7 @@ Alias: `format` [[formatnumber_fn]] === `formatnumber` -Formats a number into a formatted number string using the <>. +Formats a number into a formatted number string using the Numeral pattern. *Expression syntax* [source,js] @@ -1276,7 +1363,7 @@ The `formatnumber` subexpression receives the same `context` as the `progress` f Alias: `format` |`string` -|A <> string. For example, `"0.0a"` or `"0%"`. +|A Numeral pattern format string. For example, `"0.0a"` or `"0%"`. |=== *Returns:* `string` @@ -1559,6 +1646,34 @@ Alias: `value` [[m_fns]] == M +[float] +[[mapCenter_fn]] +=== `mapCenter` + +Returns an object with the center coordinates and zoom level of the map. + +*Accepts:* `null` + +[cols="3*^<"] +|=== +|Argument |Type |Description + +|`lat` *** +|`number` +|Latitude for the center of the map + +|`lon` *** +|`number` +|Longitude for the center of the map + +|`zoom` *** +|`number` +|Zoom level of the map +|=== + +*Returns:* `mapCenter` + + [float] [[mapColumn_fn]] === `mapColumn` @@ -1612,6 +1727,12 @@ Default: `""` |The CSS font properties for the content. For example, "font-family" or "font-weight". Default: `${font}` + +|`openLinksInNewTab` +|`boolean` +|A true or false value for opening links in a new tab. The default value is `false`. Setting to `true` opens all links in a new tab. + +Default: `false` |=== *Returns:* `render` @@ -1675,7 +1796,7 @@ Default: `${font size=48 family="'Open Sans', Helvetica, Arial, sans-serif" colo Alias: `format` |`string` -|A <> string. For example, `"0.0a"` or `"0%"`. +|A Numeral pattern format string. For example, `"0.0a"` or `"0%"`. |=== *Returns:* `render` @@ -2184,6 +2305,102 @@ Returns the number of rows. Pairs with <> to get the count of unique col [[s_fns]] == S +[float] +[[savedLens_fn]] +=== `savedLens` + +Returns an embeddable for a saved Lens visualization object. + +*Accepts:* `any` + +[cols="3*^<"] +|=== +|Argument |Type |Description + +|`id` +|`string` +|The ID of the saved Lens visualization object + +|`timerange` +|`timerange` +|The timerange of data that should be included + +|`title` +|`string` +|The title for the Lens visualization object +|=== + +*Returns:* `embeddable` + + +[float] +[[savedMap_fn]] +=== `savedMap` + +Returns an embeddable for a saved map object. + +*Accepts:* `any` + +[cols="3*^<"] +|=== +|Argument |Type |Description + +|`center` +|`mapCenter` +|The center and zoom level the map should have + +|`hideLayer` † +|`string` +|The IDs of map layers that should be hidden + +|`id` +|`string` +|The ID of the saved map object + +|`timerange` +|`timerange` +|The timerange of data that should be included + +|`title` +|`string` +|The title for the map +|=== + +*Returns:* `embeddable` + + +[float] +[[savedVisualization_fn]] +=== `savedVisualization` + +Returns an embeddable for a saved visualization object. + +*Accepts:* `any` + +[cols="3*^<"] +|=== +|Argument |Type |Description + +|`colors` † +|`seriesStyle` +|Defines the color to use for a specific series + +|`hideLegend` +|`boolean` +|Specifies the option to hide the legend + +|`id` +|`string` +|The ID of the saved visualization object + +|`timerange` +|`timerange` +|The timerange of data that should be included +|=== + +*Returns:* `embeddable` + + [float] [[seriesStyle_fn]] === `seriesStyle` @@ -2579,6 +2796,30 @@ Default: `"now"` *Returns:* `datatable` +[float] +[[timerange_fn]] +=== `timerange` + +An object that represents a span of time. + +*Accepts:* `null` + +[cols="3*^<"] +|=== +|Argument |Type |Description + +|`from` *** +|`string` +|The start of the time range + +|`to` *** +|`string` +|The end of the time range +|=== + +*Returns:* `timerange` + + [float] [[to_fn]] === `to` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.field._constructor_.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.field._constructor_.md index c3b2ac8d30b5a9..faa793d4e9dfdc 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.field._constructor_.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.field._constructor_.md @@ -9,7 +9,7 @@ Constructs a new instance of the `Field` class Signature: ```typescript -constructor(indexPattern: IndexPattern, spec: FieldSpec | Field, shortDotsEnable?: boolean); +constructor(indexPattern: IndexPattern, spec: FieldSpec | Field, shortDotsEnable: boolean, { fieldFormats, toastNotifications }: FieldDependencies); ``` ## Parameters @@ -19,4 +19,5 @@ constructor(indexPattern: IndexPattern, spec: FieldSpec | Field, shortDotsEnable | indexPattern | IndexPattern | | | spec | FieldSpec | Field | | | shortDotsEnable | boolean | | +| { fieldFormats, toastNotifications } | FieldDependencies | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.field.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.field.md index 86ff2b2c28ae94..692f34e0d81df9 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.field.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.field.md @@ -14,7 +14,7 @@ export declare class Field implements IFieldType | Constructor | Modifiers | Description | | --- | --- | --- | -| [(constructor)(indexPattern, spec, shortDotsEnable)](./kibana-plugin-plugins-data-public.field._constructor_.md) | | Constructs a new instance of the Field class | +| [(constructor)(indexPattern, spec, shortDotsEnable, { fieldFormats, toastNotifications })](./kibana-plugin-plugins-data-public.field._constructor_.md) | | Constructs a new instance of the Field class | ## Properties diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.getindexpatternfieldlistcreator.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.getindexpatternfieldlistcreator.md new file mode 100644 index 00000000000000..60302286cbd728 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.getindexpatternfieldlistcreator.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [getIndexPatternFieldListCreator](./kibana-plugin-plugins-data-public.getindexpatternfieldlistcreator.md) + +## getIndexPatternFieldListCreator variable + +Signature: + +```typescript +getIndexPatternFieldListCreator: ({ fieldFormats, toastNotifications, }: FieldListDependencies) => CreateIndexPatternFieldList +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpatternfieldlist.add.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpatternfieldlist.add.md new file mode 100644 index 00000000000000..0f3469ae9c5505 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpatternfieldlist.add.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IIndexPatternFieldList](./kibana-plugin-plugins-data-public.iindexpatternfieldlist.md) > [add](./kibana-plugin-plugins-data-public.iindexpatternfieldlist.add.md) + +## IIndexPatternFieldList.add() method + +Signature: + +```typescript +add(field: FieldSpec): void; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| field | FieldSpec | | + +Returns: + +`void` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpatternfieldlist.getbyname.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpatternfieldlist.getbyname.md new file mode 100644 index 00000000000000..14b5aa7137dc2c --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpatternfieldlist.getbyname.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IIndexPatternFieldList](./kibana-plugin-plugins-data-public.iindexpatternfieldlist.md) > [getByName](./kibana-plugin-plugins-data-public.iindexpatternfieldlist.getbyname.md) + +## IIndexPatternFieldList.getByName() method + +Signature: + +```typescript +getByName(name: Field['name']): Field | undefined; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| name | Field['name'] | | + +Returns: + +`Field | undefined` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpatternfieldlist.getbytype.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpatternfieldlist.getbytype.md new file mode 100644 index 00000000000000..3c65b78e5291db --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpatternfieldlist.getbytype.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IIndexPatternFieldList](./kibana-plugin-plugins-data-public.iindexpatternfieldlist.md) > [getByType](./kibana-plugin-plugins-data-public.iindexpatternfieldlist.getbytype.md) + +## IIndexPatternFieldList.getByType() method + +Signature: + +```typescript +getByType(type: Field['type']): Field[]; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| type | Field['type'] | | + +Returns: + +`Field[]` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpatternfieldlist.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpatternfieldlist.md new file mode 100644 index 00000000000000..47d7c7491aa86f --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpatternfieldlist.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IIndexPatternFieldList](./kibana-plugin-plugins-data-public.iindexpatternfieldlist.md) + +## IIndexPatternFieldList interface + +Signature: + +```typescript +export interface IIndexPatternFieldList extends Array +``` + +## Methods + +| Method | Description | +| --- | --- | +| [add(field)](./kibana-plugin-plugins-data-public.iindexpatternfieldlist.add.md) | | +| [getByName(name)](./kibana-plugin-plugins-data-public.iindexpatternfieldlist.getbyname.md) | | +| [getByType(type)](./kibana-plugin-plugins-data-public.iindexpatternfieldlist.getbytype.md) | | +| [remove(field)](./kibana-plugin-plugins-data-public.iindexpatternfieldlist.remove.md) | | +| [update(field)](./kibana-plugin-plugins-data-public.iindexpatternfieldlist.update.md) | | + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpatternfieldlist.remove.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpatternfieldlist.remove.md new file mode 100644 index 00000000000000..3b6bbb0691930c --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpatternfieldlist.remove.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IIndexPatternFieldList](./kibana-plugin-plugins-data-public.iindexpatternfieldlist.md) > [remove](./kibana-plugin-plugins-data-public.iindexpatternfieldlist.remove.md) + +## IIndexPatternFieldList.remove() method + +Signature: + +```typescript +remove(field: IFieldType): void; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| field | IFieldType | | + +Returns: + +`void` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpatternfieldlist.update.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpatternfieldlist.update.md new file mode 100644 index 00000000000000..121ffb65f26a52 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.iindexpatternfieldlist.update.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IIndexPatternFieldList](./kibana-plugin-plugins-data-public.iindexpatternfieldlist.md) > [update](./kibana-plugin-plugins-data-public.iindexpatternfieldlist.update.md) + +## IIndexPatternFieldList.update() method + +Signature: + +```typescript +update(field: FieldSpec): void; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| field | FieldSpec | | + +Returns: + +`void` + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.fields.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.fields.md index fcd682340eb539..9a93148e4a466f 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.fields.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.fields.md @@ -7,5 +7,5 @@ Signature: ```typescript -fields: IFieldList; +fields: IIndexPatternFieldList; ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.md index 35075e19dcaf6a..21a155ba977c96 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.md @@ -21,7 +21,7 @@ export declare class IndexPattern implements IIndexPattern | Property | Modifiers | Type | Description | | --- | --- | --- | --- | | [fieldFormatMap](./kibana-plugin-plugins-data-public.indexpattern.fieldformatmap.md) | | any | | -| [fields](./kibana-plugin-plugins-data-public.indexpattern.fields.md) | | IFieldList | | +| [fields](./kibana-plugin-plugins-data-public.indexpattern.fields.md) | | IIndexPatternFieldList | | | [fieldsFetcher](./kibana-plugin-plugins-data-public.indexpattern.fieldsfetcher.md) | | any | | | [flattenHit](./kibana-plugin-plugins-data-public.indexpattern.flattenhit.md) | | any | | | [formatField](./kibana-plugin-plugins-data-public.indexpattern.formatfield.md) | | any | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfieldlist._constructor_.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfieldlist._constructor_.md deleted file mode 100644 index 2207107db8b2b4..00000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfieldlist._constructor_.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternFieldList](./kibana-plugin-plugins-data-public.indexpatternfieldlist.md) > [(constructor)](./kibana-plugin-plugins-data-public.indexpatternfieldlist._constructor_.md) - -## IndexPatternFieldList.(constructor) - -Constructs a new instance of the `FieldList` class - -Signature: - -```typescript -constructor(indexPattern: IndexPattern, specs?: FieldSpec[], shortDotsEnable?: boolean); -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| indexPattern | IndexPattern | | -| specs | FieldSpec[] | | -| shortDotsEnable | boolean | | - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfieldlist.add.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfieldlist.add.md deleted file mode 100644 index dce2f38bbcf100..00000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfieldlist.add.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternFieldList](./kibana-plugin-plugins-data-public.indexpatternfieldlist.md) > [add](./kibana-plugin-plugins-data-public.indexpatternfieldlist.add.md) - -## IndexPatternFieldList.add property - -Signature: - -```typescript -add: (field: Record) => void; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfieldlist.getbyname.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfieldlist.getbyname.md deleted file mode 100644 index bf6bc51b603012..00000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfieldlist.getbyname.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternFieldList](./kibana-plugin-plugins-data-public.indexpatternfieldlist.md) > [getByName](./kibana-plugin-plugins-data-public.indexpatternfieldlist.getbyname.md) - -## IndexPatternFieldList.getByName property - -Signature: - -```typescript -getByName: (name: string) => Field | undefined; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfieldlist.getbytype.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfieldlist.getbytype.md deleted file mode 100644 index 86c5ae32940d48..00000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfieldlist.getbytype.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternFieldList](./kibana-plugin-plugins-data-public.indexpatternfieldlist.md) > [getByType](./kibana-plugin-plugins-data-public.indexpatternfieldlist.getbytype.md) - -## IndexPatternFieldList.getByType property - -Signature: - -```typescript -getByType: (type: string) => any[]; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfieldlist.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfieldlist.md deleted file mode 100644 index 478b73f5f85813..00000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfieldlist.md +++ /dev/null @@ -1,28 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternFieldList](./kibana-plugin-plugins-data-public.indexpatternfieldlist.md) - -## IndexPatternFieldList class - -Signature: - -```typescript -export declare class FieldList extends Array implements IFieldList -``` - -## Constructors - -| Constructor | Modifiers | Description | -| --- | --- | --- | -| [(constructor)(indexPattern, specs, shortDotsEnable)](./kibana-plugin-plugins-data-public.indexpatternfieldlist._constructor_.md) | | Constructs a new instance of the FieldList class | - -## Properties - -| Property | Modifiers | Type | Description | -| --- | --- | --- | --- | -| [add](./kibana-plugin-plugins-data-public.indexpatternfieldlist.add.md) | | (field: Record<string, any>) => void | | -| [getByName](./kibana-plugin-plugins-data-public.indexpatternfieldlist.getbyname.md) | | (name: string) => Field | undefined | | -| [getByType](./kibana-plugin-plugins-data-public.indexpatternfieldlist.getbytype.md) | | (type: string) => any[] | | -| [remove](./kibana-plugin-plugins-data-public.indexpatternfieldlist.remove.md) | | (field: IFieldType) => void | | -| [update](./kibana-plugin-plugins-data-public.indexpatternfieldlist.update.md) | | (field: Record<string, any>) => void | | - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfieldlist.remove.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfieldlist.remove.md deleted file mode 100644 index 1f2e0883d272e0..00000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfieldlist.remove.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternFieldList](./kibana-plugin-plugins-data-public.indexpatternfieldlist.md) > [remove](./kibana-plugin-plugins-data-public.indexpatternfieldlist.remove.md) - -## IndexPatternFieldList.remove property - -Signature: - -```typescript -remove: (field: IFieldType) => void; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfieldlist.update.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfieldlist.update.md deleted file mode 100644 index d5156ed41e493e..00000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfieldlist.update.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternFieldList](./kibana-plugin-plugins-data-public.indexpatternfieldlist.md) > [update](./kibana-plugin-plugins-data-public.indexpatternfieldlist.update.md) - -## IndexPatternFieldList.update property - -Signature: - -```typescript -update: (field: Record) => void; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md index 13e38ba5e6e5dc..f1024c0954251f 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md @@ -13,7 +13,6 @@ | [FieldFormat](./kibana-plugin-plugins-data-public.fieldformat.md) | | | [FilterManager](./kibana-plugin-plugins-data-public.filtermanager.md) | | | [IndexPattern](./kibana-plugin-plugins-data-public.indexpattern.md) | | -| [IndexPatternFieldList](./kibana-plugin-plugins-data-public.indexpatternfieldlist.md) | | | [IndexPatternSelect](./kibana-plugin-plugins-data-public.indexpatternselect.md) | | | [OptionedParamType](./kibana-plugin-plugins-data-public.optionedparamtype.md) | | | [Plugin](./kibana-plugin-plugins-data-public.plugin.md) | | @@ -61,6 +60,7 @@ | [IFieldSubType](./kibana-plugin-plugins-data-public.ifieldsubtype.md) | | | [IFieldType](./kibana-plugin-plugins-data-public.ifieldtype.md) | | | [IIndexPattern](./kibana-plugin-plugins-data-public.iindexpattern.md) | | +| [IIndexPatternFieldList](./kibana-plugin-plugins-data-public.iindexpatternfieldlist.md) | | | [IKibanaSearchRequest](./kibana-plugin-plugins-data-public.ikibanasearchrequest.md) | | | [IKibanaSearchResponse](./kibana-plugin-plugins-data-public.ikibanasearchresponse.md) | | | [IndexPatternAttributes](./kibana-plugin-plugins-data-public.indexpatternattributes.md) | Use data plugin interface instead | @@ -103,6 +103,7 @@ | [esQuery](./kibana-plugin-plugins-data-public.esquery.md) | | | [fieldFormats](./kibana-plugin-plugins-data-public.fieldformats.md) | | | [FilterBar](./kibana-plugin-plugins-data-public.filterbar.md) | | +| [getIndexPatternFieldListCreator](./kibana-plugin-plugins-data-public.getindexpatternfieldlistcreator.md) | | | [getKbnTypeNames](./kibana-plugin-plugins-data-public.getkbntypenames.md) | Get the esTypes known by all kbnFieldTypes {Array} | | [indexPatterns](./kibana-plugin-plugins-data-public.indexpatterns.md) | | | [QueryStringInput](./kibana-plugin-plugins-data-public.querystringinput.md) | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinput.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinput.md index d0d4cc491e1428..58690300b3bd62 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinput.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinput.md @@ -7,5 +7,5 @@ Signature: ```typescript -QueryStringInput: React.FC> +QueryStringInput: React.FC> ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchbar.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchbar.md index a0b879673e553c..0d5e0e42af27fa 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchbar.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchbar.md @@ -7,7 +7,7 @@ Signature: ```typescript -SearchBar: React.ComponentClass, "query" | "isLoading" | "filters" | "indexPatterns" | "refreshInterval" | "screenTitle" | "dataTestSubj" | "customSubmitButton" | "showQueryBar" | "showQueryInput" | "showFilterBar" | "showDatePicker" | "showAutoRefreshOnly" | "isRefreshPaused" | "dateRangeFrom" | "dateRangeTo" | "showSaveQuery" | "savedQuery" | "onQueryChange" | "onQuerySubmit" | "onSaved" | "onSavedQueryUpdated" | "onClearSavedQuery" | "onRefresh" | "timeHistory" | "onFiltersUpdated" | "onRefreshChange">, any> & { - WrappedComponent: React.ComponentType & ReactIntl.InjectedIntlProps>; +SearchBar: React.ComponentClass, "query" | "isLoading" | "filters" | "indexPatterns" | "refreshInterval" | "customSubmitButton" | "screenTitle" | "dataTestSubj" | "showQueryBar" | "showQueryInput" | "showFilterBar" | "showDatePicker" | "showAutoRefreshOnly" | "isRefreshPaused" | "dateRangeFrom" | "dateRangeTo" | "showSaveQuery" | "savedQuery" | "onQueryChange" | "onQuerySubmit" | "onSaved" | "onSavedQueryUpdated" | "onClearSavedQuery" | "onRefresh" | "timeHistory" | "onFiltersUpdated" | "onRefreshChange">, any> & { + WrappedComponent: React.ComponentType & ReactIntl.InjectedIntlProps>; } ``` diff --git a/docs/discover/images/autorefresh-interval.png b/docs/discover/images/autorefresh-interval.png new file mode 100644 index 00000000000000..a9f72a1cb73cde Binary files /dev/null and b/docs/discover/images/autorefresh-interval.png differ diff --git a/docs/discover/search.asciidoc b/docs/discover/search.asciidoc index 21ae4560fba946..9fe35f03027604 100644 --- a/docs/discover/search.asciidoc +++ b/docs/discover/search.asciidoc @@ -1,25 +1,53 @@ [[search]] == Searching your data -You can search the indices that match the current <> by entering -your search criteria in the Query bar. By default you can use Kibana's <> -which features autocomplete and a simple, easy to use syntax. Kibana's legacy query -language (based on Lucene https://lucene.apache.org/core/2_9_4/queryparsersyntax.html[query syntax]) -is still available for the time being under the options menu in the Query Bar. When this -legacy query language is selected, the full JSON-based {ref}/query-dsl.html[Elasticsearch Query DSL] -can also be used. - -When you submit a search request, the histogram, Documents table, and Fields -list are updated to reflect the search results. The total number of hits -(matching documents) is shown in the toolbar. The Documents table shows the -first five hundred hits. By default, the hits are listed in reverse -chronological order, with the newest documents shown first. You can reverse -the sort order by clicking the Time column header. You can also sort the table -by the values in any indexed field. For more information, see <>. - -To search your data, enter your search criteria in the Query bar and -press *Enter* or click *Search* image:images/search-button.jpg[] to submit -the request to Elasticsearch. +Many Kibana apps embed a query bar for real-time search, including +*Discover*, *Visualize*, and *Dashboard*. + +[float] +=== Search your data + +To search the indices that match the current <>, +enter your search criteria in the query bar. By default, you'll use +{kib}'s <> (KQL), which +features autocomplete and a simple, easy-to-use syntax. If you prefer to use +{kib}'s legacy query +language, based on the +Lucene https://lucene.apache.org/core/2_9_4/queryparsersyntax.html[query syntax], +you can switch to it from the KQL popup in the query bar. When you enable the +legacy query language, you can use the full +JSON-based {ref}/query-dsl.html[Elasticsearch Query DSL]. + + +[float] +[[autorefresh]] +=== Refresh search results +As more documents are added to the indices you're searching, the search results +shown in *Discover*, and used to display visualizations, get stale. Using the +time filter, you can +configure a refresh interval to periodically resubmit your searches to +retrieve the latest results. + +[role="screenshot"] +image::images/autorefresh-interval.png[] + +You can also manually refresh the search results by +clicking the *Refresh* button. + +[float] +=== Searching large amounts of data + +Sometimes you want to search through large amounts of data no matter how long +the search takes. While this might not happen often, there are times +that long-running queries are required. Consider a threat hunting scenario +where you need to search through years of data. + +If you run a query, and the run time gets close to the +timeout, you're presented the option to ignore the timeout. This enables you to +run queries with large amounts of data to completion. + +By default, a query times out after 30 seconds. +The timeout is in place to avoid unintentional load on the cluster. + include::kuery.asciidoc[] @@ -160,31 +188,3 @@ To completely delete a query: image::discover/images/saved-query-management-component-delete-query-button.png["Example of the saved query management popover when a query is hovered over and we are about to delete a query",width="80%"] You can import, export, and delete saved queries from <>. - -[[select-pattern]] -=== Change the indices you're searching -When you submit a search request, the indices that match the currently-selected -index pattern are searched. -To change the indices you are searching, click the index pattern and select a -different <>. - -[[autorefresh]] -=== Refresh the search results -As more documents are added to the indices you're searching, the search results -shown in Discover and used to display visualizations get stale. You can -configure a refresh interval to periodically resubmit your searches to -retrieve the latest results. - -. Click image:images/time-filter-calendar.png[]. - -. In the *Refresh every* field, enter the refresh rate, then select the interval - from the dropdown. - -. Click *Start*. -+ -image::images/autorefresh-intervals.png[] - -To disable auto refresh, click *Stop*. - -If auto refresh is not enabled, click *Refresh* to manually refresh the search -results. diff --git a/docs/images/autorefresh-intervals.png b/docs/images/autorefresh-intervals.png index a79ae2f1f6c46c..49be46fefd4aad 100644 Binary files a/docs/images/autorefresh-intervals.png and b/docs/images/autorefresh-intervals.png differ diff --git a/docs/user/discover.asciidoc b/docs/user/discover.asciidoc index 2547b38a226168..7bac80237a26ee 100644 --- a/docs/user/discover.asciidoc +++ b/docs/user/discover.asciidoc @@ -21,6 +21,7 @@ image::images/Discover-Start.png[Discover] [float] +[[select-pattern]] === Set up your index pattern The first thing to do in *Discover* is to select an <>, which diff --git a/docs/user/reporting/development/pdf-integration.asciidoc b/docs/user/reporting/development/pdf-integration.asciidoc index af5ba5be1636e3..e9f32de41baab6 100644 --- a/docs/user/reporting/development/pdf-integration.asciidoc +++ b/docs/user/reporting/development/pdf-integration.asciidoc @@ -63,3 +63,5 @@ If there are multiple visualizations, the `data-shared-items-count` attribute sh many Visualizations to look for. Reporting will look at every element with the `data-shared-item` attribute and use the corresponding `data-render-complete` attribute and `renderComplete` events to listen for rendering to complete. When rendering is complete for a visualization the `data-render-complete` attribute should be set to "true" and it should dispatch a custom DOM `renderComplete` event. + +If the reporting job uses multiple URLs, before looking for any of the `data-shared-item` or `data-shared-items-count` attributes, it waits for a `data-shared-page` attribute that specifies which page is being loaded. diff --git a/package.json b/package.json index 1e3ddc976aa67c..1f0658bd2a1383 100644 --- a/package.json +++ b/package.json @@ -122,7 +122,7 @@ "@babel/core": "^7.9.0", "@babel/register": "^7.9.0", "@elastic/apm-rum": "^5.1.1", - "@elastic/charts": "18.4.2", + "@elastic/charts": "19.1.2", "@elastic/datemath": "5.0.3", "@elastic/ems-client": "7.8.0", "@elastic/eui": "22.3.0", @@ -146,6 +146,7 @@ "@types/tar": "^4.0.3", "JSONStream": "1.3.5", "abortcontroller-polyfill": "^1.4.0", + "accept": "3.0.2", "angular": "^1.7.9", "angular-aria": "^1.7.9", "angular-elastic": "^2.5.1", @@ -310,6 +311,7 @@ "@percy/agent": "^0.26.0", "@testing-library/react": "^9.3.2", "@testing-library/react-hooks": "^3.2.1", + "@types/accept": "3.1.1", "@types/angular": "^1.6.56", "@types/angular-mocks": "^1.7.0", "@types/babel__core": "^7.1.2", diff --git a/packages/kbn-optimizer/package.json b/packages/kbn-optimizer/package.json index b3e5a8c518682e..b7c9a63897bf9f 100644 --- a/packages/kbn-optimizer/package.json +++ b/packages/kbn-optimizer/package.json @@ -14,6 +14,7 @@ "@kbn/babel-preset": "1.0.0", "@kbn/dev-utils": "1.0.0", "@kbn/ui-shared-deps": "1.0.0", + "@types/compression-webpack-plugin": "^2.0.1", "@types/estree": "^0.0.44", "@types/loader-utils": "^1.1.3", "@types/watchpack": "^1.1.5", @@ -23,6 +24,7 @@ "autoprefixer": "^9.7.4", "babel-loader": "^8.0.6", "clean-webpack-plugin": "^3.0.0", + "compression-webpack-plugin": "^3.1.0", "cpy": "^8.0.0", "css-loader": "^3.4.2", "del": "^5.1.0", diff --git a/packages/kbn-optimizer/src/integration_tests/basic_optimization.test.ts b/packages/kbn-optimizer/src/integration_tests/basic_optimization.test.ts index ad743933e11711..248b0b7cf4c971 100644 --- a/packages/kbn-optimizer/src/integration_tests/basic_optimization.test.ts +++ b/packages/kbn-optimizer/src/integration_tests/basic_optimization.test.ts @@ -19,6 +19,7 @@ import Path from 'path'; import Fs from 'fs'; +import Zlib from 'zlib'; import { inspect } from 'util'; import cpy from 'cpy'; @@ -124,17 +125,12 @@ it('builds expected bundles, saves bundle counts to metadata', async () => { ); assert('produce zero unexpected states', otherStates.length === 0, otherStates); - expect( - Fs.readFileSync(Path.resolve(MOCK_REPO_DIR, 'plugins/foo/target/public/foo.plugin.js'), 'utf8') - ).toMatchSnapshot('foo bundle'); - - expect( - Fs.readFileSync(Path.resolve(MOCK_REPO_DIR, 'plugins/foo/target/public/1.plugin.js'), 'utf8') - ).toMatchSnapshot('1 async bundle'); - - expect( - Fs.readFileSync(Path.resolve(MOCK_REPO_DIR, 'plugins/bar/target/public/bar.plugin.js'), 'utf8') - ).toMatchSnapshot('bar bundle'); + expectFileMatchesSnapshotWithCompression('plugins/foo/target/public/foo.plugin.js', 'foo bundle'); + expectFileMatchesSnapshotWithCompression( + 'plugins/foo/target/public/1.plugin.js', + '1 async bundle' + ); + expectFileMatchesSnapshotWithCompression('plugins/bar/target/public/bar.plugin.js', 'bar bundle'); const foo = config.bundles.find(b => b.id === 'foo')!; expect(foo).toBeTruthy(); @@ -203,3 +199,24 @@ it('uses cache on second run and exist cleanly', async () => { ] `); }); + +/** + * Verifies that the file matches the expected output and has matching compressed variants. + */ +const expectFileMatchesSnapshotWithCompression = (filePath: string, snapshotLabel: string) => { + const raw = Fs.readFileSync(Path.resolve(MOCK_REPO_DIR, filePath), 'utf8'); + expect(raw).toMatchSnapshot(snapshotLabel); + + // Verify the brotli variant matches + expect( + // @ts-ignore @types/node is missing the brotli functions + Zlib.brotliDecompressSync( + Fs.readFileSync(Path.resolve(MOCK_REPO_DIR, `${filePath}.br`)) + ).toString() + ).toEqual(raw); + + // Verify the gzip variant matches + expect( + Zlib.gunzipSync(Fs.readFileSync(Path.resolve(MOCK_REPO_DIR, `${filePath}.gz`))).toString() + ).toEqual(raw); +}; diff --git a/packages/kbn-optimizer/src/worker/webpack.config.ts b/packages/kbn-optimizer/src/worker/webpack.config.ts index cc3fa8c2720ded..95e826e7620aa6 100644 --- a/packages/kbn-optimizer/src/worker/webpack.config.ts +++ b/packages/kbn-optimizer/src/worker/webpack.config.ts @@ -28,6 +28,7 @@ import TerserPlugin from 'terser-webpack-plugin'; import webpackMerge from 'webpack-merge'; // @ts-ignore import { CleanWebpackPlugin } from 'clean-webpack-plugin'; +import CompressionPlugin from 'compression-webpack-plugin'; import * as UiSharedDeps from '@kbn/ui-shared-deps'; import { Bundle, WorkerConfig, parseDirPath, DisallowedSyntaxPlugin } from '../common'; @@ -319,6 +320,16 @@ export function getWebpackConfig(bundle: Bundle, worker: WorkerConfig) { IS_KIBANA_DISTRIBUTABLE: `"true"`, }, }), + new CompressionPlugin({ + algorithm: 'brotliCompress', + filename: '[path].br', + test: /\.(js|css)$/, + }), + new CompressionPlugin({ + algorithm: 'gzip', + filename: '[path].gz', + test: /\.(js|css)$/, + }), ], optimization: { diff --git a/packages/kbn-ui-shared-deps/package.json b/packages/kbn-ui-shared-deps/package.json index a60e2b0449d958..a2248f1ae655e8 100644 --- a/packages/kbn-ui-shared-deps/package.json +++ b/packages/kbn-ui-shared-deps/package.json @@ -9,11 +9,12 @@ "kbn:watch": "node scripts/build --watch" }, "dependencies": { - "@elastic/charts": "18.4.2", + "@elastic/charts": "19.1.2", "@elastic/eui": "22.3.0", "@kbn/i18n": "1.0.0", "abortcontroller-polyfill": "^1.4.0", "angular": "^1.7.9", + "compression-webpack-plugin": "^3.1.0", "core-js": "^3.6.4", "custom-event-polyfill": "^0.3.0", "elasticsearch-browser": "^16.7.0", diff --git a/packages/kbn-ui-shared-deps/webpack.config.js b/packages/kbn-ui-shared-deps/webpack.config.js index bf63c577658595..ca913d0f16417d 100644 --- a/packages/kbn-ui-shared-deps/webpack.config.js +++ b/packages/kbn-ui-shared-deps/webpack.config.js @@ -20,6 +20,7 @@ const Path = require('path'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); +const CompressionPlugin = require('compression-webpack-plugin'); const { REPO_ROOT } = require('@kbn/dev-utils'); const webpack = require('webpack'); @@ -117,5 +118,19 @@ exports.getWebpackConfig = ({ dev = false } = {}) => ({ new webpack.DefinePlugin({ 'process.env.NODE_ENV': dev ? '"development"' : '"production"', }), + ...(dev + ? [] + : [ + new CompressionPlugin({ + algorithm: 'brotliCompress', + filename: '[path].br', + test: /\.(js|css)$/, + }), + new CompressionPlugin({ + algorithm: 'gzip', + filename: '[path].gz', + test: /\.(js|css)$/, + }), + ]), ], }); diff --git a/renovate.json5 b/renovate.json5 index c0ddcaf4f23c8f..61b2485ecf44b0 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -398,6 +398,8 @@ '@types/good-squeeze', 'inert', '@types/inert', + 'accept', + '@types/accept', ], }, { diff --git a/src/core/public/application/__snapshots__/application_service.test.ts.snap b/src/core/public/application/__snapshots__/application_service.test.ts.snap index 376b320b64ea9a..c085fb028cd5a7 100644 --- a/src/core/public/application/__snapshots__/application_service.test.ts.snap +++ b/src/core/public/application/__snapshots__/application_service.test.ts.snap @@ -80,5 +80,6 @@ exports[`#start() getComponent returns renderable JSX tree 1`] = ` } mounters={Map {}} setAppLeaveHandler={[Function]} + setIsMounting={[Function]} /> `; diff --git a/src/core/public/application/application_service.test.ts b/src/core/public/application/application_service.test.ts index e29837aecb1253..04ff844ffc1505 100644 --- a/src/core/public/application/application_service.test.ts +++ b/src/core/public/application/application_service.test.ts @@ -20,7 +20,7 @@ import { createElement } from 'react'; import { BehaviorSubject, Subject } from 'rxjs'; import { bufferCount, take, takeUntil } from 'rxjs/operators'; -import { shallow } from 'enzyme'; +import { shallow, mount } from 'enzyme'; import { injectedMetadataServiceMock } from '../injected_metadata/injected_metadata_service.mock'; import { contextServiceMock } from '../context/context_service.mock'; @@ -30,6 +30,7 @@ import { MockCapabilitiesService, MockHistory } from './application_service.test import { MockLifecycle } from './test_types'; import { ApplicationService } from './application_service'; import { App, AppNavLinkStatus, AppStatus, AppUpdater, LegacyApp } from './types'; +import { act } from 'react-dom/test-utils'; const createApp = (props: Partial): App => { return { @@ -452,9 +453,9 @@ describe('#setup()', () => { const container = setupDeps.context.createContextContainer.mock.results[0].value; const pluginId = Symbol(); - const mount = () => () => undefined; - registerMountContext(pluginId, 'test' as any, mount); - expect(container.registerContext).toHaveBeenCalledWith(pluginId, 'test', mount); + const appMount = () => () => undefined; + registerMountContext(pluginId, 'test' as any, appMount); + expect(container.registerContext).toHaveBeenCalledWith(pluginId, 'test', appMount); }); }); @@ -809,6 +810,74 @@ describe('#start()', () => { `); }); + it('updates httpLoadingCount$ while mounting', async () => { + // Use a memory history so that mounting the component will work + const { createMemoryHistory } = jest.requireActual('history'); + const history = createMemoryHistory(); + setupDeps.history = history; + + const flushPromises = () => new Promise(resolve => setImmediate(resolve)); + // Create an app and a promise that allows us to control when the app completes mounting + const createWaitingApp = (props: Partial): [App, () => void] => { + let finishMount: () => void; + const mountPromise = new Promise(resolve => (finishMount = resolve)); + const app = { + id: 'some-id', + title: 'some-title', + mount: async () => { + await mountPromise; + return () => undefined; + }, + ...props, + }; + + return [app, finishMount!]; + }; + + // Create some dummy applications + const { register } = service.setup(setupDeps); + const [alphaApp, finishAlphaMount] = createWaitingApp({ id: 'alpha' }); + const [betaApp, finishBetaMount] = createWaitingApp({ id: 'beta' }); + register(Symbol(), alphaApp); + register(Symbol(), betaApp); + + const { navigateToApp, getComponent } = await service.start(startDeps); + const httpLoadingCount$ = startDeps.http.addLoadingCountSource.mock.calls[0][0]; + const stop$ = new Subject(); + const currentLoadingCount$ = new BehaviorSubject(0); + httpLoadingCount$.pipe(takeUntil(stop$)).subscribe(currentLoadingCount$); + const loadingPromise = httpLoadingCount$.pipe(bufferCount(5), takeUntil(stop$)).toPromise(); + mount(getComponent()!); + + await act(() => navigateToApp('alpha')); + expect(currentLoadingCount$.value).toEqual(1); + await act(async () => { + finishAlphaMount(); + await flushPromises(); + }); + expect(currentLoadingCount$.value).toEqual(0); + + await act(() => navigateToApp('beta')); + expect(currentLoadingCount$.value).toEqual(1); + await act(async () => { + finishBetaMount(); + await flushPromises(); + }); + expect(currentLoadingCount$.value).toEqual(0); + + stop$.next(); + const loadingCounts = await loadingPromise; + expect(loadingCounts).toMatchInlineSnapshot(` + Array [ + 0, + 1, + 0, + 1, + 0, + ] + `); + }); + it('sets window.location.href when navigating to legacy apps', async () => { setupDeps.http = httpServiceMock.createSetupContract({ basePath: '/test' }); setupDeps.injectedMetadata.getLegacyMode.mockReturnValue(true); diff --git a/src/core/public/application/application_service.tsx b/src/core/public/application/application_service.tsx index bafa1932e5e927..0dd77072e9eafb 100644 --- a/src/core/public/application/application_service.tsx +++ b/src/core/public/application/application_service.tsx @@ -238,6 +238,9 @@ export class ApplicationService { throw new Error('ApplicationService#setup() must be invoked before start.'); } + const httpLoadingCount$ = new BehaviorSubject(0); + http.addLoadingCountSource(httpLoadingCount$); + this.registrationClosed = true; window.addEventListener('beforeunload', this.onBeforeUnload); @@ -303,6 +306,7 @@ export class ApplicationService { mounters={availableMounters} appStatuses$={applicationStatuses$} setAppLeaveHandler={this.setAppLeaveHandler} + setIsMounting={isMounting => httpLoadingCount$.next(isMounting ? 1 : 0)} /> ); }, diff --git a/src/core/public/application/integration_tests/router.test.tsx b/src/core/public/application/integration_tests/router.test.tsx index 2f26bc1409104d..915c58b28ad6d1 100644 --- a/src/core/public/application/integration_tests/router.test.tsx +++ b/src/core/public/application/integration_tests/router.test.tsx @@ -40,7 +40,7 @@ describe('AppContainer', () => { }; const mockMountersToMounters = () => new Map([...mounters].map(([appId, { mounter }]) => [appId, mounter])); - const setAppLeaveHandlerMock = () => undefined; + const noop = () => undefined; const mountersToAppStatus$ = () => { return new BehaviorSubject( @@ -86,7 +86,8 @@ describe('AppContainer', () => { history={globalHistory} mounters={mockMountersToMounters()} appStatuses$={appStatuses$} - setAppLeaveHandler={setAppLeaveHandlerMock} + setAppLeaveHandler={noop} + setIsMounting={noop} /> ); }); @@ -98,7 +99,7 @@ describe('AppContainer', () => { expect(app1.mounter.mount).toHaveBeenCalled(); expect(dom?.html()).toMatchInlineSnapshot(` - "
+ "
basename: /app/app1 html: App 1
" @@ -110,7 +111,7 @@ describe('AppContainer', () => { expect(app1Unmount).toHaveBeenCalled(); expect(app2.mounter.mount).toHaveBeenCalled(); expect(dom?.html()).toMatchInlineSnapshot(` - "
+ "
basename: /app/app2 html:
App 2
" @@ -124,7 +125,7 @@ describe('AppContainer', () => { expect(standardApp.mounter.mount).toHaveBeenCalled(); expect(dom?.html()).toMatchInlineSnapshot(` - "
+ "
basename: /app/app1 html: App 1
" @@ -136,7 +137,7 @@ describe('AppContainer', () => { expect(standardAppUnmount).toHaveBeenCalled(); expect(chromelessApp.mounter.mount).toHaveBeenCalled(); expect(dom?.html()).toMatchInlineSnapshot(` - "
+ "
basename: /chromeless-a/path html:
Chromeless A
" @@ -148,7 +149,7 @@ describe('AppContainer', () => { expect(chromelessAppUnmount).toHaveBeenCalled(); expect(standardApp.mounter.mount).toHaveBeenCalledTimes(2); expect(dom?.html()).toMatchInlineSnapshot(` - "
+ "
basename: /app/app1 html: App 1
" @@ -162,7 +163,7 @@ describe('AppContainer', () => { expect(chromelessAppA.mounter.mount).toHaveBeenCalled(); expect(dom?.html()).toMatchInlineSnapshot(` - "
+ "
basename: /chromeless-a/path html:
Chromeless A
" @@ -174,7 +175,7 @@ describe('AppContainer', () => { expect(chromelessAppAUnmount).toHaveBeenCalled(); expect(chromelessAppB.mounter.mount).toHaveBeenCalled(); expect(dom?.html()).toMatchInlineSnapshot(` - "
+ "
basename: /chromeless-b/path html:
Chromeless B
" @@ -186,7 +187,7 @@ describe('AppContainer', () => { expect(chromelessAppBUnmount).toHaveBeenCalled(); expect(chromelessAppA.mounter.mount).toHaveBeenCalledTimes(2); expect(dom?.html()).toMatchInlineSnapshot(` - "
+ "
basename: /chromeless-a/path html:
Chromeless A
" @@ -214,7 +215,8 @@ describe('AppContainer', () => { history={globalHistory} mounters={mockMountersToMounters()} appStatuses$={mountersToAppStatus$()} - setAppLeaveHandler={setAppLeaveHandlerMock} + setAppLeaveHandler={noop} + setIsMounting={noop} /> ); @@ -245,7 +247,8 @@ describe('AppContainer', () => { history={globalHistory} mounters={mockMountersToMounters()} appStatuses$={mountersToAppStatus$()} - setAppLeaveHandler={setAppLeaveHandlerMock} + setAppLeaveHandler={noop} + setIsMounting={noop} /> ); @@ -286,7 +289,8 @@ describe('AppContainer', () => { history={globalHistory} mounters={mockMountersToMounters()} appStatuses$={mountersToAppStatus$()} - setAppLeaveHandler={setAppLeaveHandlerMock} + setAppLeaveHandler={noop} + setIsMounting={noop} /> ); diff --git a/src/core/public/application/integration_tests/utils.tsx b/src/core/public/application/integration_tests/utils.tsx index 9092177da5ad4b..fa04b56f83ba1d 100644 --- a/src/core/public/application/integration_tests/utils.tsx +++ b/src/core/public/application/integration_tests/utils.tsx @@ -18,6 +18,7 @@ */ import React, { ReactElement } from 'react'; +import { act } from 'react-dom/test-utils'; import { mount } from 'enzyme'; import { I18nProvider } from '@kbn/i18n/react'; @@ -34,7 +35,9 @@ export const createRenderer = (element: ReactElement | null): Renderer => { return () => new Promise(async resolve => { if (dom) { - dom.update(); + await act(async () => { + dom.update(); + }); } setImmediate(() => resolve(dom)); // flushes any pending promises }); diff --git a/src/core/public/application/ui/app_container.scss b/src/core/public/application/ui/app_container.scss new file mode 100644 index 00000000000000..4f8fec10a97e15 --- /dev/null +++ b/src/core/public/application/ui/app_container.scss @@ -0,0 +1,25 @@ +.appContainer__loading { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index: $euiZLevel1; + animation-name: appContainerFadeIn; + animation-iteration-count: 1; + animation-timing-function: ease-in; + animation-duration: 2s; +} + +@keyframes appContainerFadeIn { + 0% { + opacity: 0; + } + + 50% { + opacity: 0; + } + + 100% { + opacity: 1; + } +} diff --git a/src/core/public/application/ui/app_container.test.tsx b/src/core/public/application/ui/app_container.test.tsx index c538227e8f0988..2ee71a5bde7dc1 100644 --- a/src/core/public/application/ui/app_container.test.tsx +++ b/src/core/public/application/ui/app_container.test.tsx @@ -18,6 +18,7 @@ */ import React from 'react'; +import { act } from 'react-dom/test-utils'; import { mount } from 'enzyme'; import { AppContainer } from './app_container'; @@ -28,6 +29,12 @@ import { ScopedHistory } from '../scoped_history'; describe('AppContainer', () => { const appId = 'someApp'; const setAppLeaveHandler = jest.fn(); + const setIsMounting = jest.fn(); + + beforeEach(() => { + setAppLeaveHandler.mockClear(); + setIsMounting.mockClear(); + }); const flushPromises = async () => { await new Promise(async resolve => { @@ -67,6 +74,7 @@ describe('AppContainer', () => { appStatus={AppStatus.inaccessible} mounter={mounter} setAppLeaveHandler={setAppLeaveHandler} + setIsMounting={setIsMounting} createScopedHistory={(appPath: string) => // Create a history using the appPath as the current location new ScopedHistory(createMemoryHistory({ initialEntries: [appPath] }), appPath) @@ -86,10 +94,86 @@ describe('AppContainer', () => { expect(wrapper.text()).toEqual(''); - resolvePromise(); - await flushPromises(); - wrapper.update(); + await act(async () => { + resolvePromise(); + await flushPromises(); + wrapper.update(); + }); expect(wrapper.text()).toContain('some-content'); }); + + it('should call setIsMounting while mounting', async () => { + const [waitPromise, resolvePromise] = createResolver(); + const mounter = createMounter(waitPromise); + + const wrapper = mount( + + // Create a history using the appPath as the current location + new ScopedHistory(createMemoryHistory({ initialEntries: [appPath] }), appPath) + } + /> + ); + + expect(setIsMounting).toHaveBeenCalledTimes(1); + expect(setIsMounting).toHaveBeenLastCalledWith(true); + + await act(async () => { + resolvePromise(); + await flushPromises(); + wrapper.update(); + }); + + expect(setIsMounting).toHaveBeenCalledTimes(2); + expect(setIsMounting).toHaveBeenLastCalledWith(false); + }); + + it('should call setIsMounting(false) if mounting throws', async () => { + const [waitPromise, resolvePromise] = createResolver(); + const mounter = { + appBasePath: '/base-path', + appRoute: '/some-route', + unmountBeforeMounting: false, + mount: async ({ element }: AppMountParameters) => { + await waitPromise; + throw new Error(`Mounting failed!`); + }, + }; + + const wrapper = mount( + + // Create a history using the appPath as the current location + new ScopedHistory(createMemoryHistory({ initialEntries: [appPath] }), appPath) + } + /> + ); + + expect(setIsMounting).toHaveBeenCalledTimes(1); + expect(setIsMounting).toHaveBeenLastCalledWith(true); + + // await expect( + await act(async () => { + resolvePromise(); + await flushPromises(); + wrapper.update(); + }); + // ).rejects.toThrow(); + + expect(setIsMounting).toHaveBeenCalledTimes(2); + expect(setIsMounting).toHaveBeenLastCalledWith(false); + }); }); diff --git a/src/core/public/application/ui/app_container.tsx b/src/core/public/application/ui/app_container.tsx index e12a0f2cf2fcd0..aad7e6dcf270a7 100644 --- a/src/core/public/application/ui/app_container.tsx +++ b/src/core/public/application/ui/app_container.tsx @@ -26,9 +26,11 @@ import React, { MutableRefObject, } from 'react'; +import { EuiLoadingSpinner } from '@elastic/eui'; import { AppLeaveHandler, AppStatus, AppUnmount, Mounter } from '../types'; import { AppNotFound } from './app_not_found_screen'; import { ScopedHistory } from '../scoped_history'; +import './app_container.scss'; interface Props { /** Path application is mounted on without the global basePath */ @@ -38,6 +40,7 @@ interface Props { appStatus: AppStatus; setAppLeaveHandler: (appId: string, handler: AppLeaveHandler) => void; createScopedHistory: (appUrl: string) => ScopedHistory; + setIsMounting: (isMounting: boolean) => void; } export const AppContainer: FunctionComponent = ({ @@ -47,7 +50,9 @@ export const AppContainer: FunctionComponent = ({ setAppLeaveHandler, createScopedHistory, appStatus, + setIsMounting, }: Props) => { + const [showSpinner, setShowSpinner] = useState(true); const [appNotFound, setAppNotFound] = useState(false); const elementRef = useRef(null); const unmountRef: MutableRefObject = useRef(null); @@ -65,28 +70,42 @@ export const AppContainer: FunctionComponent = ({ } setAppNotFound(false); + setIsMounting(true); if (mounter.unmountBeforeMounting) { unmount(); } const mount = async () => { - unmountRef.current = - (await mounter.mount({ - appBasePath: mounter.appBasePath, - history: createScopedHistory(appPath), - element: elementRef.current!, - onAppLeave: handler => setAppLeaveHandler(appId, handler), - })) || null; + setShowSpinner(true); + try { + unmountRef.current = + (await mounter.mount({ + appBasePath: mounter.appBasePath, + history: createScopedHistory(appPath), + element: elementRef.current!, + onAppLeave: handler => setAppLeaveHandler(appId, handler), + })) || null; + } catch (e) { + // TODO: add error UI + } finally { + setShowSpinner(false); + setIsMounting(false); + } }; mount(); return unmount; - }, [appId, appStatus, mounter, createScopedHistory, setAppLeaveHandler, appPath]); + }, [appId, appStatus, mounter, createScopedHistory, setAppLeaveHandler, appPath, setIsMounting]); return ( {appNotFound && } + {showSpinner && ( +
+ +
+ )}
); diff --git a/src/core/public/application/ui/app_router.tsx b/src/core/public/application/ui/app_router.tsx index 4c135c57690676..ea7c5c9308fe2a 100644 --- a/src/core/public/application/ui/app_router.tsx +++ b/src/core/public/application/ui/app_router.tsx @@ -32,6 +32,7 @@ interface Props { history: History; appStatuses$: Observable>; setAppLeaveHandler: (appId: string, handler: AppLeaveHandler) => void; + setIsMounting: (isMounting: boolean) => void; } interface Params { @@ -43,6 +44,7 @@ export const AppRouter: FunctionComponent = ({ mounters, setAppLeaveHandler, appStatuses$, + setIsMounting, }) => { const appStatuses = useObservable(appStatuses$, new Map()); const createScopedHistory = useMemo( @@ -67,7 +69,7 @@ export const AppRouter: FunctionComponent = ({ appPath={url} appStatus={appStatuses.get(appId) ?? AppStatus.inaccessible} createScopedHistory={createScopedHistory} - {...{ appId, mounter, setAppLeaveHandler }} + {...{ appId, mounter, setAppLeaveHandler, setIsMounting }} /> )} />, @@ -92,7 +94,7 @@ export const AppRouter: FunctionComponent = ({ appId={id} appStatus={appStatuses.get(id) ?? AppStatus.inaccessible} createScopedHistory={createScopedHistory} - {...{ mounter, setAppLeaveHandler }} + {...{ mounter, setAppLeaveHandler, setIsMounting }} /> ); }} diff --git a/src/core/server/elasticsearch/elasticsearch_service.mock.ts b/src/core/server/elasticsearch/elasticsearch_service.mock.ts index da8846f6dddbb9..a7d78b56ff3fdd 100644 --- a/src/core/server/elasticsearch/elasticsearch_service.mock.ts +++ b/src/core/server/elasticsearch/elasticsearch_service.mock.ts @@ -18,6 +18,7 @@ */ import { BehaviorSubject } from 'rxjs'; +import { Client } from 'elasticsearch'; import { IClusterClient, ICustomClusterClient } from './cluster_client'; import { IScopedClusterClient } from './scoped_cluster_client'; import { ElasticsearchConfig } from './elasticsearch_config'; @@ -130,6 +131,55 @@ const createMock = () => { return mocked; }; +const createElasticsearchClientMock = () => { + const mocked: jest.Mocked = { + cat: {} as any, + cluster: {} as any, + indices: {} as any, + ingest: {} as any, + nodes: {} as any, + snapshot: {} as any, + tasks: {} as any, + bulk: jest.fn(), + clearScroll: jest.fn(), + count: jest.fn(), + create: jest.fn(), + delete: jest.fn(), + deleteByQuery: jest.fn(), + deleteScript: jest.fn(), + deleteTemplate: jest.fn(), + exists: jest.fn(), + explain: jest.fn(), + fieldStats: jest.fn(), + get: jest.fn(), + getScript: jest.fn(), + getSource: jest.fn(), + getTemplate: jest.fn(), + index: jest.fn(), + info: jest.fn(), + mget: jest.fn(), + msearch: jest.fn(), + msearchTemplate: jest.fn(), + mtermvectors: jest.fn(), + ping: jest.fn(), + putScript: jest.fn(), + putTemplate: jest.fn(), + reindex: jest.fn(), + reindexRethrottle: jest.fn(), + renderSearchTemplate: jest.fn(), + scroll: jest.fn(), + search: jest.fn(), + searchShards: jest.fn(), + searchTemplate: jest.fn(), + suggest: jest.fn(), + termvectors: jest.fn(), + update: jest.fn(), + updateByQuery: jest.fn(), + close: jest.fn(), + }; + return mocked; +}; + export const elasticsearchServiceMock = { create: createMock, createInternalSetup: createInternalSetupContractMock, @@ -138,4 +188,5 @@ export const elasticsearchServiceMock = { createClusterClient: createClusterClientMock, createCustomClusterClient: createCustomClusterClientMock, createScopedClusterClient: createScopedClusterClientMock, + createElasticsearchClient: createElasticsearchClientMock, }; diff --git a/src/core/server/http/integration_tests/core_service.test.mocks.ts b/src/core/server/http/integration_tests/core_service.test.mocks.ts index 6fa3357168027d..b3adda8dd22d1b 100644 --- a/src/core/server/http/integration_tests/core_service.test.mocks.ts +++ b/src/core/server/http/integration_tests/core_service.test.mocks.ts @@ -24,3 +24,14 @@ jest.doMock('../../elasticsearch/scoped_cluster_client', () => ({ return elasticsearchServiceMock.createScopedClusterClient(); }), })); + +jest.doMock('elasticsearch', () => { + const realES = jest.requireActual('elasticsearch'); + return { + ...realES, + // eslint-disable-next-line object-shorthand + Client: function() { + return elasticsearchServiceMock.createElasticsearchClient(); + }, + }; +}); diff --git a/src/core/server/http/integration_tests/core_services.test.ts b/src/core/server/http/integration_tests/core_services.test.ts index 7b1630a7de0be0..5726486a0930a3 100644 --- a/src/core/server/http/integration_tests/core_services.test.ts +++ b/src/core/server/http/integration_tests/core_services.test.ts @@ -43,7 +43,7 @@ describe('http service', () => { describe('auth', () => { let root: ReturnType; beforeEach(async () => { - root = kbnTestServer.createRoot({ migrations: { skip: true } }); + root = kbnTestServer.createRoot(); }, 30000); afterEach(async () => { @@ -192,7 +192,7 @@ describe('http service', () => { let root: ReturnType; beforeEach(async () => { - root = kbnTestServer.createRoot({ migrations: { skip: true } }); + root = kbnTestServer.createRoot(); }, 30000); afterEach(async () => { @@ -326,7 +326,7 @@ describe('http service', () => { describe('#basePath()', () => { let root: ReturnType; beforeEach(async () => { - root = kbnTestServer.createRoot({ migrations: { skip: true } }); + root = kbnTestServer.createRoot(); }, 30000); afterEach(async () => await root.shutdown()); @@ -355,7 +355,7 @@ describe('http service', () => { describe('elasticsearch', () => { let root: ReturnType; beforeEach(async () => { - root = kbnTestServer.createRoot({ migrations: { skip: true } }); + root = kbnTestServer.createRoot(); }, 30000); afterEach(async () => { diff --git a/src/dev/renovate/package_groups.ts b/src/dev/renovate/package_groups.ts index 1bc65fd149f471..9f5aa8556ac214 100644 --- a/src/dev/renovate/package_groups.ts +++ b/src/dev/renovate/package_groups.ts @@ -159,7 +159,17 @@ export const RENOVATE_PACKAGE_GROUPS: PackageGroup[] = [ { name: 'hapi', packageWords: ['hapi'], - packageNames: ['hapi', 'joi', 'boom', 'hoek', 'h2o2', '@elastic/good', 'good-squeeze', 'inert'], + packageNames: [ + 'hapi', + 'joi', + 'boom', + 'hoek', + 'h2o2', + '@elastic/good', + 'good-squeeze', + 'inert', + 'accept', + ], }, { diff --git a/src/legacy/core_plugins/interpreter/README.md b/src/legacy/core_plugins/interpreter/README.md deleted file mode 100644 index 6d90ce2d5e2ebd..00000000000000 --- a/src/legacy/core_plugins/interpreter/README.md +++ /dev/null @@ -1,2 +0,0 @@ -Interpreter legacy plugin has been migrated to the New Platform. Use -`expressions` New Platform plugin instead. diff --git a/src/legacy/core_plugins/interpreter/index.ts b/src/legacy/core_plugins/interpreter/index.ts deleted file mode 100644 index 9427a2f8a2d0f3..00000000000000 --- a/src/legacy/core_plugins/interpreter/index.ts +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { resolve } from 'path'; -import { Legacy } from '../../../../kibana'; -import { init } from './init'; - -// eslint-disable-next-line -export default function InterpreterPlugin(kibana: any) { - const config: Legacy.PluginSpecOptions = { - id: 'interpreter', - require: ['kibana', 'elasticsearch'], - publicDir: resolve(__dirname, 'public'), - uiExports: { - injectDefaultVars: server => ({ - serverBasePath: server.config().get('server.basePath'), - }), - }, - config: (Joi: any) => { - return Joi.object({ - enabled: Joi.boolean().default(true), - }).default(); - }, - init, - }; - - return new kibana.Plugin(config); -} diff --git a/src/legacy/core_plugins/interpreter/init.ts b/src/legacy/core_plugins/interpreter/init.ts deleted file mode 100644 index 46da1539afadb6..00000000000000 --- a/src/legacy/core_plugins/interpreter/init.ts +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/* eslint-disable max-classes-per-file */ - -// @ts-ignore -import { register, registryFactory, Registry, Fn } from '@kbn/interpreter/common'; - -import { Legacy } from '../../../../kibana'; - -export async function init(server: Legacy.Server /* options */) { - server.injectUiAppVars('canvas', () => { - const config = server.config(); - const basePath = config.get('server.basePath'); - const reportingBrowserType = (() => { - const configKey = 'xpack.reporting.capture.browser.type'; - if (!config.has(configKey)) { - return null; - } - return config.get(configKey); - })(); - - return { - kbnIndex: config.get('kibana.index'), - serverFunctions: (server.newPlatform.setup.plugins.expressions as any).__LEGACY - .registries() - .serverFunctions.toArray(), - basePath, - reportingBrowserType, - }; - }); - - // Expose server.plugins.interpreter.register(specs) and - // server.plugins.interpreter.registries() (a getter). - server.expose((server.newPlatform.setup.plugins.expressions as any).__LEGACY); -} diff --git a/src/legacy/core_plugins/interpreter/package.json b/src/legacy/core_plugins/interpreter/package.json deleted file mode 100644 index 3265dadd7fbfc2..00000000000000 --- a/src/legacy/core_plugins/interpreter/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "interpreter", - "version": "kibana" -} diff --git a/src/legacy/core_plugins/interpreter/public/canvas/load_legacy_server_function_wrappers.ts b/src/legacy/core_plugins/interpreter/public/canvas/load_legacy_server_function_wrappers.ts deleted file mode 100644 index fed157846a1a15..00000000000000 --- a/src/legacy/core_plugins/interpreter/public/canvas/load_legacy_server_function_wrappers.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/** - * This file needs to be deleted by 8.0 release. It is here to load available - * server side functions and create a wrappers around them on client side, to - * execute them from client side. This functionality is used only by Canvas - * and all server side functions are in Canvas plugin. - * - * In 8.0 there will be no server-side functions, plugins will register only - * client side functions and if they need those to execute something on the - * server side, it should be respective function's internal implementation detail. - */ - -import { npSetup } from 'ui/new_platform'; - -export const { loadLegacyServerFunctionWrappers } = npSetup.plugins.expressions.__LEGACY; diff --git a/src/legacy/core_plugins/interpreter/public/interpreter.ts b/src/legacy/core_plugins/interpreter/public/interpreter.ts deleted file mode 100644 index 319a2779010c31..00000000000000 --- a/src/legacy/core_plugins/interpreter/public/interpreter.ts +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import 'uiExports/interpreter'; -// @ts-ignore -import { register, registryFactory } from '@kbn/interpreter/common'; -import { npSetup } from 'ui/new_platform'; -import { registries } from './registries'; -import { Executor, ExpressionExecutor } from '../../../../plugins/expressions/public'; - -// Expose kbnInterpreter.register(specs) and kbnInterpreter.registries() globally so that plugins -// can register without a transpile step. -// TODO: This will be left behind in then legacy platform? -(global as any).kbnInterpreter = Object.assign( - (global as any).kbnInterpreter || {}, - registryFactory(registries) -); - -// TODO: This function will be left behind in the legacy platform. -let executorPromise: Promise | undefined; -export const getInterpreter = async () => { - if (!executorPromise) { - const executor = npSetup.plugins.expressions.__LEGACY.getExecutor(); - executorPromise = Promise.resolve(executor); - } - return await executorPromise; -}; - -// TODO: This function will be left behind in the legacy platform. -export const interpretAst: Executor['run'] = async (ast, context, handlers) => { - const { interpreter } = await getInterpreter(); - return await interpreter.interpretAst(ast, context, handlers); -}; diff --git a/src/legacy/core_plugins/interpreter/public/registries.karma_mock.ts b/src/legacy/core_plugins/interpreter/public/registries.karma_mock.ts deleted file mode 100644 index 0f37f33cc1b13d..00000000000000 --- a/src/legacy/core_plugins/interpreter/public/registries.karma_mock.ts +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import sinon from 'sinon'; - -export const functionsRegistry = {}; -export const renderersRegistry = {}; -export const typesRegistry = {}; -export const registries = { - browserFunctions: functionsRegistry, - renderers: renderersRegistry, - types: typesRegistry, - loadLegacyServerFunctionWrappers: () => Promise.resolve(), -}; - -const resetRegistry = (registry: any) => { - registry.wrapper = sinon.stub(); - registry.register = sinon.stub(); - registry.toJS = sinon.stub(); - registry.toArray = sinon.stub(); - registry.get = sinon.stub(); - registry.getProp = sinon.stub(); - registry.reset = sinon.stub(); -}; -const resetAll = () => Object.values(registries).forEach(resetRegistry); - -resetAll(); -afterEach(resetAll); diff --git a/src/legacy/core_plugins/interpreter/public/registries.ts b/src/legacy/core_plugins/interpreter/public/registries.ts deleted file mode 100644 index 63fd9089acf4a8..00000000000000 --- a/src/legacy/core_plugins/interpreter/public/registries.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { npSetup } from 'ui/new_platform'; - -export const functionsRegistry = npSetup.plugins.expressions.__LEGACY.functions; -export const renderersRegistry = npSetup.plugins.expressions.__LEGACY.renderers; -export const typesRegistry = npSetup.plugins.expressions.__LEGACY.types; -export const registries = { - browserFunctions: functionsRegistry, - renderers: renderersRegistry, - types: typesRegistry, -}; diff --git a/src/legacy/core_plugins/kibana/index.js b/src/legacy/core_plugins/kibana/index.js index 48d86e3628e491..51577456135d16 100644 --- a/src/legacy/core_plugins/kibana/index.js +++ b/src/legacy/core_plugins/kibana/index.js @@ -53,7 +53,7 @@ export default function(kibana) { }, uiExports: { - hacks: ['plugins/kibana/discover/legacy', 'plugins/kibana/dev_tools'], + hacks: ['plugins/kibana/dev_tools'], app: { id: 'kibana', title: 'Kibana', diff --git a/src/legacy/core_plugins/kibana/public/discover/__tests__/doc_table/doc_table.js b/src/legacy/core_plugins/kibana/public/__tests__/discover/doc_table.js similarity index 96% rename from src/legacy/core_plugins/kibana/public/discover/__tests__/doc_table/doc_table.js rename to src/legacy/core_plugins/kibana/public/__tests__/discover/doc_table.js index 9e74df08233dad..edf65fdb56220d 100644 --- a/src/legacy/core_plugins/kibana/public/discover/__tests__/doc_table/doc_table.js +++ b/src/legacy/core_plugins/kibana/public/__tests__/discover/doc_table.js @@ -16,18 +16,15 @@ * specific language governing permissions and limitations * under the License. */ - import angular from 'angular'; import expect from '@kbn/expect'; import _ from 'lodash'; import ngMock from 'ng_mock'; import 'ui/private'; -import { pluginInstance } from 'plugins/kibana/discover/legacy'; +import { pluginInstance } from './legacy'; import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; import hits from 'fixtures/real_hits'; -// Load the kibana app dependencies. - let $parentScope; let $scope; @@ -60,6 +57,7 @@ const destroy = function() { describe('docTable', function() { let $elem; + beforeEach(() => pluginInstance.initializeInnerAngular()); beforeEach(() => pluginInstance.initializeServices()); beforeEach(ngMock.module('app/discover')); diff --git a/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/fixed_scroll.js b/src/legacy/core_plugins/kibana/public/__tests__/discover/fixed_scroll.js similarity index 89% rename from src/legacy/core_plugins/kibana/public/discover/__tests__/directives/fixed_scroll.js rename to src/legacy/core_plugins/kibana/public/__tests__/discover/fixed_scroll.js index 49a0df54079ea5..4a8736cc0d6a45 100644 --- a/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/fixed_scroll.js +++ b/src/legacy/core_plugins/kibana/public/__tests__/discover/fixed_scroll.js @@ -17,21 +17,33 @@ * under the License. */ +/* eslint-disable @kbn/eslint/no-restricted-paths */ + +import angular from 'angular'; import expect from '@kbn/expect'; -import { pluginInstance } from 'plugins/kibana/discover/legacy'; import ngMock from 'ng_mock'; import $ from 'jquery'; import sinon from 'sinon'; +import { PrivateProvider } from '../../../../../../plugins/kibana_legacy/public'; +import { FixedScrollProvider } from '../../../../../../plugins/discover/public/application/angular/directives/fixed_scroll'; +import { DebounceProviderTimeout } from '../../../../../../plugins/discover/public/application/angular/directives/debounce/debounce'; + +const testModuleName = 'fixedScroll'; + +angular + .module(testModuleName, []) + .provider('Private', PrivateProvider) + .service('debounce', ['$timeout', DebounceProviderTimeout]) + .directive('fixedScroll', FixedScrollProvider); + describe('FixedScroll directive', function() { const sandbox = sinon.createSandbox(); let compile; let flushPendingTasks; const trash = []; - beforeEach(() => pluginInstance.initializeServices()); - beforeEach(() => pluginInstance.initializeInnerAngular()); - beforeEach(ngMock.module('app/discover')); + beforeEach(ngMock.module(testModuleName)); beforeEach( ngMock.inject(function($compile, $rootScope, $timeout) { flushPendingTasks = function flushPendingTasks() { diff --git a/src/legacy/core_plugins/kibana/public/discover/legacy.ts b/src/legacy/core_plugins/kibana/public/__tests__/discover/legacy.ts similarity index 80% rename from src/legacy/core_plugins/kibana/public/discover/legacy.ts rename to src/legacy/core_plugins/kibana/public/__tests__/discover/legacy.ts index f08fd22c718504..ecda2a8c153951 100644 --- a/src/legacy/core_plugins/kibana/public/discover/legacy.ts +++ b/src/legacy/core_plugins/kibana/public/__tests__/discover/legacy.ts @@ -17,11 +17,11 @@ * under the License. */ -import { PluginInitializerContext } from 'kibana/public'; import { npSetup, npStart } from 'ui/new_platform'; -import { plugin } from './index'; +import { plugin } from '../../../../../../plugins/discover/public'; +import { coreMock } from '../../../../../../core/public/mocks'; +const context = coreMock.createPluginInitializerContext(); -// Legacy compatibility part - to be removed at cutover, replaced by a kibana.json file -export const pluginInstance = plugin({} as PluginInitializerContext); +export const pluginInstance = plugin(context); export const setup = pluginInstance.setup(npSetup.core, npSetup.plugins); export const start = pluginInstance.start(npStart.core, npStart.plugins); diff --git a/src/legacy/core_plugins/kibana/public/discover/__tests__/doc_table/lib/rows_headers.js b/src/legacy/core_plugins/kibana/public/__tests__/discover/row_headers.js similarity index 98% rename from src/legacy/core_plugins/kibana/public/discover/__tests__/doc_table/lib/rows_headers.js rename to src/legacy/core_plugins/kibana/public/__tests__/discover/row_headers.js index 9b63b8cd18f3e9..5450a4127b63c8 100644 --- a/src/legacy/core_plugins/kibana/public/discover/__tests__/doc_table/lib/rows_headers.js +++ b/src/legacy/core_plugins/kibana/public/__tests__/discover/row_headers.js @@ -16,7 +16,6 @@ * specific language governing permissions and limitations * under the License. */ - import angular from 'angular'; import _ from 'lodash'; import sinon from 'sinon'; @@ -24,7 +23,7 @@ import expect from '@kbn/expect'; import ngMock from 'ng_mock'; import { getFakeRow, getFakeRowVals } from 'fixtures/fake_row'; import $ from 'jquery'; -import { pluginInstance } from 'plugins/kibana/discover/legacy'; +import { pluginInstance } from './legacy'; import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; describe('Doc Table', function() { @@ -218,7 +217,7 @@ describe('Doc Table', function() { }); /** this no longer works with the new plugin approach - it('should render even when the row source contains a field with the same name as a meta field', function () { + it('should render even when the row source contains a field with the same name as a meta field', function () { setTimeout(() => { //this should be overridden by later changes }, 100); diff --git a/src/legacy/core_plugins/kibana/public/discover/_index.scss b/src/legacy/core_plugins/kibana/public/discover/_index.scss deleted file mode 100644 index 386472a9f6e015..00000000000000 --- a/src/legacy/core_plugins/kibana/public/discover/_index.scss +++ /dev/null @@ -1,2 +0,0 @@ -// Discover plugin styles -@import 'np_ready/index'; diff --git a/src/legacy/core_plugins/kibana/public/discover/plugin.ts b/src/legacy/core_plugins/kibana/public/discover/plugin.ts deleted file mode 100644 index 702331529b8794..00000000000000 --- a/src/legacy/core_plugins/kibana/public/discover/plugin.ts +++ /dev/null @@ -1,236 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { BehaviorSubject } from 'rxjs'; -import { filter, map } from 'rxjs/operators'; -import { AppMountParameters, CoreSetup, CoreStart, Plugin } from 'kibana/public'; -import angular, { auto } from 'angular'; -import { UiActionsSetup, UiActionsStart } from 'src/plugins/ui_actions/public'; -import { - DataPublicPluginStart, - DataPublicPluginSetup, - esFilters, -} from '../../../../../plugins/data/public'; -import { registerFeature } from './np_ready/register_feature'; -import './kibana_services'; -import { EmbeddableStart, EmbeddableSetup } from '../../../../../plugins/embeddable/public'; -import { getInnerAngularModule, getInnerAngularModuleEmbeddable } from './get_inner_angular'; -import { getHistory, setAngularModule, setServices, setUrlTracker } from './kibana_services'; -import { NavigationPublicPluginStart as NavigationStart } from '../../../../../plugins/navigation/public'; -import { ChartsPluginStart } from '../../../../../plugins/charts/public'; -import { buildServices } from './build_services'; -import { SharePluginStart } from '../../../../../plugins/share/public'; -import { - KibanaLegacySetup, - AngularRenderedAppUpdater, -} from '../../../../../plugins/kibana_legacy/public'; -import { DiscoverSetup, DiscoverStart } from '../../../../../plugins/discover/public'; -import { HomePublicPluginSetup } from '../../../../../plugins/home/public'; -import { - VisualizationsStart, - VisualizationsSetup, -} from '../../../../../plugins/visualizations/public'; -import { createKbnUrlTracker } from '../../../../../plugins/kibana_utils/public'; - -export interface DiscoverSetupPlugins { - uiActions: UiActionsSetup; - embeddable: EmbeddableSetup; - kibanaLegacy: KibanaLegacySetup; - home: HomePublicPluginSetup; - visualizations: VisualizationsSetup; - data: DataPublicPluginSetup; - discover: DiscoverSetup; -} -export interface DiscoverStartPlugins { - uiActions: UiActionsStart; - embeddable: EmbeddableStart; - navigation: NavigationStart; - charts: ChartsPluginStart; - data: DataPublicPluginStart; - share: SharePluginStart; - inspector: any; - visualizations: VisualizationsStart; - discover: DiscoverStart; -} -const innerAngularName = 'app/discover'; -const embeddableAngularName = 'app/discoverEmbeddable'; - -/** - * Contains Discover, one of the oldest parts of Kibana - * There are 2 kinds of Angular bootstrapped for rendering, additionally to the main Angular - * Discover provides embeddables, those contain a slimmer Angular - */ -export class DiscoverPlugin implements Plugin { - private servicesInitialized: boolean = false; - private innerAngularInitialized: boolean = false; - private embeddableInjector: auto.IInjectorService | null = null; - private getEmbeddableInjector: (() => Promise) | null = null; - private appStateUpdater = new BehaviorSubject(() => ({})); - private stopUrlTracking: (() => void) | undefined = undefined; - - /** - * why are those functions public? they are needed for some mocha tests - * can be removed once all is Jest - */ - public initializeInnerAngular?: () => void; - public initializeServices?: () => Promise<{ core: CoreStart; plugins: DiscoverStartPlugins }>; - - setup(core: CoreSetup, plugins: DiscoverSetupPlugins) { - const { - appMounted, - appUnMounted, - stop: stopUrlTracker, - setActiveUrl: setTrackedUrl, - } = createKbnUrlTracker({ - // we pass getter here instead of plain `history`, - // so history is lazily created (when app is mounted) - // this prevents redundant `#` when not in discover app - getHistory, - baseUrl: core.http.basePath.prepend('/app/kibana'), - defaultSubUrl: '#/discover', - storageKey: `lastUrl:${core.http.basePath.get()}:discover`, - navLinkUpdater$: this.appStateUpdater, - toastNotifications: core.notifications.toasts, - stateParams: [ - { - kbnUrlKey: '_g', - stateUpdate$: plugins.data.query.state$.pipe( - filter( - ({ changes }) => !!(changes.globalFilters || changes.time || changes.refreshInterval) - ), - map(({ state }) => ({ - ...state, - filters: state.filters?.filter(esFilters.isFilterPinned), - })) - ), - }, - ], - }); - setUrlTracker({ setTrackedUrl }); - this.stopUrlTracking = () => { - stopUrlTracker(); - }; - - this.getEmbeddableInjector = this.getInjector.bind(this); - plugins.discover.docViews.setAngularInjectorGetter(this.getEmbeddableInjector); - plugins.kibanaLegacy.registerLegacyApp({ - id: 'discover', - title: 'Discover', - updater$: this.appStateUpdater.asObservable(), - navLinkId: 'kibana:discover', - order: -1004, - euiIconType: 'discoverApp', - mount: async (params: AppMountParameters) => { - if (!this.initializeServices) { - throw Error('Discover plugin method initializeServices is undefined'); - } - if (!this.initializeInnerAngular) { - throw Error('Discover plugin method initializeInnerAngular is undefined'); - } - appMounted(); - await this.initializeServices(); - await this.initializeInnerAngular(); - - // make sure the index pattern list is up to date - const [, { data: dataStart }] = await core.getStartServices(); - await dataStart.indexPatterns.clearCache(); - const { renderApp } = await import('./np_ready/application'); - const unmount = await renderApp(innerAngularName, params.element); - return () => { - unmount(); - appUnMounted(); - }; - }, - }); - registerFeature(plugins.home); - this.registerEmbeddable(core, plugins); - } - - start(core: CoreStart, plugins: DiscoverStartPlugins) { - // we need to register the application service at setup, but to render it - // there are some start dependencies necessary, for this reason - // initializeInnerAngular + initializeServices are assigned at start and used - // when the application/embeddable is mounted - this.initializeInnerAngular = async () => { - if (this.innerAngularInitialized) { - return; - } - // this is used by application mount and tests - const module = getInnerAngularModule(innerAngularName, core, plugins); - setAngularModule(module); - this.innerAngularInitialized = true; - }; - - this.initializeServices = async () => { - if (this.servicesInitialized) { - return { core, plugins }; - } - const services = await buildServices(core, plugins, getHistory); - setServices(services); - this.servicesInitialized = true; - - return { core, plugins }; - }; - } - - stop() { - if (this.stopUrlTracking) { - this.stopUrlTracking(); - } - } - - /** - * register embeddable with a slimmer embeddable version of inner angular - */ - private async registerEmbeddable( - core: CoreSetup, - plugins: DiscoverSetupPlugins - ) { - const { SearchEmbeddableFactory } = await import('./np_ready/embeddable'); - - if (!this.getEmbeddableInjector) { - throw Error('Discover plugin method getEmbeddableInjector is undefined'); - } - - const getStartServices = async () => { - const [coreStart, deps] = await core.getStartServices(); - return { - executeTriggerActions: deps.uiActions.executeTriggerActions, - isEditable: () => coreStart.application.capabilities.discover.save as boolean, - }; - }; - - const factory = new SearchEmbeddableFactory(getStartServices, this.getEmbeddableInjector); - plugins.embeddable.registerEmbeddableFactory(factory.type, factory); - } - - private async getInjector() { - if (!this.embeddableInjector) { - if (!this.initializeServices) { - throw Error('Discover plugin getEmbeddableInjector: initializeServices is undefined'); - } - const { core, plugins } = await this.initializeServices(); - getInnerAngularModuleEmbeddable(embeddableAngularName, core, plugins); - const mountpoint = document.createElement('div'); - this.embeddableInjector = angular.bootstrap(mountpoint, [embeddableAngularName]); - } - - return this.embeddableInjector; - } -} diff --git a/src/legacy/core_plugins/kibana/public/index.scss b/src/legacy/core_plugins/kibana/public/index.scss index 26805554370b96..6a2e65e3a9ff53 100644 --- a/src/legacy/core_plugins/kibana/public/index.scss +++ b/src/legacy/core_plugins/kibana/public/index.scss @@ -7,9 +7,6 @@ // Public UI styles @import 'src/legacy/ui/public/index'; -// Discover styles -@import 'discover/index'; - // Has to come after visualize because of some // bad cascading in the Editor layout @import '../../../../plugins/maps_legacy/public/index'; diff --git a/src/legacy/core_plugins/kibana/public/kibana.js b/src/legacy/core_plugins/kibana/public/kibana.js index ea0d5ad3790b13..ad67a74121cc94 100644 --- a/src/legacy/core_plugins/kibana/public/kibana.js +++ b/src/legacy/core_plugins/kibana/public/kibana.js @@ -42,7 +42,6 @@ import 'uiExports/shareContextMenuExtensions'; import 'uiExports/interpreter'; import 'ui/autoload/all'; -import './discover/legacy'; import './management'; import './dev_tools'; import { showAppRedirectNotification } from '../../../../plugins/kibana_legacy/public'; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/__snapshots__/create_index_pattern_wizard.test.tsx.snap b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/__snapshots__/create_index_pattern_wizard.test.tsx.snap index ed65db10e0acb6..1545ab8cb9b1ce 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/__snapshots__/create_index_pattern_wizard.test.tsx.snap +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/__snapshots__/create_index_pattern_wizard.test.tsx.snap @@ -149,6 +149,8 @@ exports[`CreateIndexPatternWizard renders time field step when step is set to 2 indexPatternsService={ Object { "clearCache": [MockFunction], + "createField": [MockFunction], + "createFieldList": [MockFunction], "ensureDefaultIndexPattern": [MockFunction], "get": [MockFunction], "make": [Function], diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/create_edit_field/create_edit_field.tsx b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/create_edit_field/create_edit_field.tsx index 564f115cf2c487..3b865f7d5e1eaf 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/create_edit_field/create_edit_field.tsx +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/create_edit_field/create_edit_field.tsx @@ -24,8 +24,8 @@ import { FieldEditor } from 'ui/field_editor'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { HttpStart, DocLinksStart } from 'src/core/public'; +import { IndexPattern, DataPublicPluginStart } from 'src/plugins/data/public'; import { IndexHeader } from '../index_header'; -import { IndexPattern, IndexPatternField } from '../../../../../../../../../plugins/data/public'; import { ChromeDocTitle, NotificationsStart } from '../../../../../../../../../core/public'; import { TAB_SCRIPTED_FIELDS, TAB_INDEXED_FIELDS } from '../constants'; @@ -36,6 +36,7 @@ interface CreateEditFieldProps extends RouteComponentProps { fieldFormatEditors: any; getConfig: (name: string) => any; services: { + dataStart: DataPublicPluginStart; notifications: NotificationsStart; docTitle: ChromeDocTitle; getHttpStart: () => HttpStart; @@ -63,10 +64,14 @@ export const CreateEditField = withRouter( const field = mode === 'edit' && fieldName ? indexPattern.fields.getByName(fieldName) - : new IndexPatternField(indexPattern, { - scripted: true, - type: 'number', - }); + : services.dataStart.indexPatterns.createField( + indexPattern, + { + scripted: true, + type: 'number', + }, + false + ); const url = `/management/kibana/index_patterns/${indexPattern.id}`; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/index.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/index.js index e2f387c0291a7a..ab1fa546e5ea83 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/index.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/index.js @@ -112,6 +112,7 @@ const renderCreateEditField = ($scope, $route, getConfig, fieldFormatEditors) => fieldFormatEditors={fieldFormatEditors} getConfig={getConfig} services={{ + dataStart: npStart.plugins.data, getHttpStart: () => npStart.core.http, notifications: npStart.core.notifications, docTitle: npStart.core.chrome.docTitle, diff --git a/src/legacy/ui/public/field_editor/field_editor.test.tsx b/src/legacy/ui/public/field_editor/field_editor.test.tsx index 5716305b514830..92264fbfbc94d3 100644 --- a/src/legacy/ui/public/field_editor/field_editor.test.tsx +++ b/src/legacy/ui/public/field_editor/field_editor.test.tsx @@ -24,7 +24,7 @@ import { shallowWithI18nProvider } from 'test_utils/enzyme_helpers'; import { Field, IndexPattern, - IndexPatternFieldList, + IIndexPatternFieldList, FieldFormatInstanceType, } from 'src/plugins/data/public'; import { HttpStart } from '../../../../core/public'; @@ -114,7 +114,7 @@ describe('FieldEditor', () => { beforeEach(() => { indexPattern = ({ - fields: fields as IndexPatternFieldList, + fields: fields as IIndexPatternFieldList, } as unknown) as IndexPattern; npStart.plugins.data.fieldFormats.getDefaultType = jest.fn( diff --git a/src/legacy/ui/public/styles/_legacy/components/_table.scss b/src/legacy/ui/public/styles/_legacy/components/_table.scss index c9472cbd2faa73..d0ac9d6f798628 100644 --- a/src/legacy/ui/public/styles/_legacy/components/_table.scss +++ b/src/legacy/ui/public/styles/_legacy/components/_table.scss @@ -1,4 +1,4 @@ -@import '../../../../../core_plugins/kibana/public/discover/np_ready/mixins'; +@import '../../../../../../plugins/discover/public/application/mixins'; .table { // Nesting diff --git a/src/optimize/bundles_route/dynamic_asset_response.ts b/src/optimize/bundles_route/dynamic_asset_response.ts index a020c6935eeecf..2f5395341abb10 100644 --- a/src/optimize/bundles_route/dynamic_asset_response.ts +++ b/src/optimize/bundles_route/dynamic_asset_response.ts @@ -21,6 +21,7 @@ import Fs from 'fs'; import { resolve } from 'path'; import { promisify } from 'util'; +import Accept from 'accept'; import Boom from 'boom'; import Hapi from 'hapi'; @@ -37,6 +38,41 @@ const asyncOpen = promisify(Fs.open); const asyncClose = promisify(Fs.close); const asyncFstat = promisify(Fs.fstat); +async function tryToOpenFile(filePath: string) { + try { + return await asyncOpen(filePath, 'r'); + } catch (e) { + if (e.code === 'ENOENT') { + return undefined; + } else { + throw e; + } + } +} + +async function selectCompressedFile(acceptEncodingHeader: string | undefined, path: string) { + let fd: number | undefined; + let fileEncoding: 'gzip' | 'br' | undefined; + + const supportedEncodings = Accept.encodings(acceptEncodingHeader, ['br', 'gzip']); + + if (supportedEncodings[0] === 'br') { + fileEncoding = 'br'; + fd = await tryToOpenFile(`${path}.br`); + } + if (!fd && supportedEncodings.includes('gzip')) { + fileEncoding = 'gzip'; + fd = await tryToOpenFile(`${path}.gz`); + } + if (!fd) { + fileEncoding = undefined; + // Use raw open to trigger exception if it does not exist + fd = await asyncOpen(path, 'r'); + } + + return { fd, fileEncoding }; +} + /** * Create a Hapi response for the requested path. This is designed * to replicate a subset of the features provided by Hapi's Inert @@ -74,6 +110,7 @@ export async function createDynamicAssetResponse({ isDist: boolean; }) { let fd: number | undefined; + let fileEncoding: 'gzip' | 'br' | undefined; try { const path = resolve(bundlesPath, request.params.path); @@ -86,7 +123,7 @@ export async function createDynamicAssetResponse({ // we use and manage a file descriptor mostly because // that's what Inert does, and since we are accessing // the file 2 or 3 times per request it seems logical - fd = await asyncOpen(path, 'r'); + ({ fd, fileEncoding } = await selectCompressedFile(request.headers['accept-encoding'], path)); const stat = await asyncFstat(fd); const hash = isDist ? undefined : await getFileHash(fileHashCache, path, stat, fd); @@ -113,6 +150,12 @@ export async function createDynamicAssetResponse({ response.header('cache-control', 'must-revalidate'); } + // If we manually selected a compressed file, specify the encoding header. + // Otherwise, let Hapi automatically gzip the response. + if (fileEncoding) { + response.header('content-encoding', fileEncoding); + } + return response; } catch (error) { if (fd) { diff --git a/src/plugins/console/server/lib/spec_definitions/js/aggregations.ts b/src/plugins/console/server/lib/spec_definitions/js/aggregations.ts index 1170c9edd2366f..ce3155919bd1d0 100644 --- a/src/plugins/console/server/lib/spec_definitions/js/aggregations.ts +++ b/src/plugins/console/server/lib/spec_definitions/js/aggregations.ts @@ -148,7 +148,7 @@ const rules = { shard_size: 10, order: { __template: { - _term: 'asc', + _key: 'asc', }, _term: { __one_of: ['asc', 'desc'] }, _count: { __one_of: ['asc', 'desc'] }, diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index ebc794ed7e5953..8e48214ab0f914 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -254,11 +254,12 @@ export const indexPatterns = { export { IndexPatternsContract, IndexPattern, + IIndexPatternFieldList, Field as IndexPatternField, TypeMeta as IndexPatternTypeMeta, AggregationRestrictions as IndexPatternAggRestrictions, // TODO: exported only in stub_index_pattern test. Move into data plugin and remove export. - FieldList as IndexPatternFieldList, + getIndexPatternFieldListCreator, Field, } from './index_patterns'; diff --git a/src/plugins/data/public/index_patterns/fields/field.ts b/src/plugins/data/public/index_patterns/fields/field.ts index d83c0a7d3445eb..12db09bbb846fd 100644 --- a/src/plugins/data/public/index_patterns/fields/field.ts +++ b/src/plugins/data/public/index_patterns/fields/field.ts @@ -18,15 +18,26 @@ */ import { i18n } from '@kbn/i18n'; +import { ToastsStart } from 'kibana/public'; // @ts-ignore import { ObjDefine } from './obj_define'; import { IndexPattern } from '../index_patterns'; -import { getNotifications, getFieldFormats } from '../../services'; -import { IFieldType, getKbnFieldType, IFieldSubType, FieldFormat } from '../../../common'; -import { shortenDottedString } from '../../../common/utils'; +import { + IFieldType, + getKbnFieldType, + IFieldSubType, + FieldFormat, + shortenDottedString, +} from '../../../common'; +import { FieldFormatsStart } from '../../field_formats'; export type FieldSpec = Record; +interface FieldDependencies { + fieldFormats: FieldFormatsStart; + toastNotifications: ToastsStart; +} + export class Field implements IFieldType { name: string; type: string; @@ -52,7 +63,8 @@ export class Field implements IFieldType { constructor( indexPattern: IndexPattern, spec: FieldSpec | Field, - shortDotsEnable: boolean = false + shortDotsEnable: boolean, + { fieldFormats, toastNotifications }: FieldDependencies ) { // unwrap old instances of Field if (spec instanceof Field) spec = spec.$$spec; @@ -77,9 +89,8 @@ export class Field implements IFieldType { values: { name: spec.name, title: indexPattern.title }, defaultMessage: 'Field {name} in indexPattern {title} is using an unknown field type.', }); - const { toasts } = getNotifications(); - toasts.addDanger({ + toastNotifications.addDanger({ title, text, }); @@ -90,11 +101,9 @@ export class Field implements IFieldType { let format = spec.format; if (!FieldFormat.isInstanceOfFieldFormat(format)) { - const fieldFormatsService = getFieldFormats(); - format = indexPattern.fieldFormatMap[spec.name] || - fieldFormatsService.getDefaultInstance(spec.type, spec.esTypes); + fieldFormats.getDefaultInstance(spec.type, spec.esTypes); } const indexed = !!spec.indexed; diff --git a/src/plugins/data/public/index_patterns/fields/field_list.ts b/src/plugins/data/public/index_patterns/fields/field_list.ts index 9772370199b24a..0631e00a1fb622 100644 --- a/src/plugins/data/public/index_patterns/fields/field_list.ts +++ b/src/plugins/data/public/index_patterns/fields/field_list.ts @@ -18,13 +18,20 @@ */ import { findIndex } from 'lodash'; +import { ToastsStart } from 'kibana/public'; import { IndexPattern } from '../index_patterns'; import { IFieldType } from '../../../common'; import { Field, FieldSpec } from './field'; +import { FieldFormatsStart } from '../../field_formats'; type FieldMap = Map; -export interface IFieldList extends Array { +interface FieldListDependencies { + fieldFormats: FieldFormatsStart; + toastNotifications: ToastsStart; +} + +export interface IIndexPatternFieldList extends Array { getByName(name: Field['name']): Field | undefined; getByType(type: Field['type']): Field[]; add(field: FieldSpec): void; @@ -32,51 +39,70 @@ export interface IFieldList extends Array { update(field: FieldSpec): void; } -export class FieldList extends Array implements IFieldList { - private byName: FieldMap = new Map(); - private groups: Map = new Map(); - private indexPattern: IndexPattern; - private shortDotsEnable: boolean; - private setByName = (field: Field) => this.byName.set(field.name, field); - private setByGroup = (field: Field) => { - if (typeof this.groups.get(field.type) === 'undefined') { - this.groups.set(field.type, new Map()); - } - this.groups.get(field.type)!.set(field.name, field); - }; - private removeByGroup = (field: IFieldType) => this.groups.get(field.type)!.delete(field.name); +export type CreateIndexPatternFieldList = ( + indexPattern: IndexPattern, + specs?: FieldSpec[], + shortDotsEnable?: boolean +) => IIndexPatternFieldList; - constructor(indexPattern: IndexPattern, specs: FieldSpec[] = [], shortDotsEnable = false) { - super(); - this.indexPattern = indexPattern; - this.shortDotsEnable = shortDotsEnable; +export const getIndexPatternFieldListCreator = ({ + fieldFormats, + toastNotifications, +}: FieldListDependencies): CreateIndexPatternFieldList => (...fieldListParams) => { + class FieldList extends Array implements IIndexPatternFieldList { + private byName: FieldMap = new Map(); + private groups: Map = new Map(); + private indexPattern: IndexPattern; + private shortDotsEnable: boolean; + private setByName = (field: Field) => this.byName.set(field.name, field); + private setByGroup = (field: Field) => { + if (typeof this.groups.get(field.type) === 'undefined') { + this.groups.set(field.type, new Map()); + } + this.groups.get(field.type)!.set(field.name, field); + }; + private removeByGroup = (field: IFieldType) => this.groups.get(field.type)!.delete(field.name); - specs.map(field => this.add(field)); - } + constructor(indexPattern: IndexPattern, specs: FieldSpec[] = [], shortDotsEnable = false) { + super(); + this.indexPattern = indexPattern; + this.shortDotsEnable = shortDotsEnable; - getByName = (name: Field['name']) => this.byName.get(name); - getByType = (type: Field['type']) => [...(this.groups.get(type) || new Map()).values()]; - add = (field: FieldSpec) => { - const newField = new Field(this.indexPattern, field, this.shortDotsEnable); - this.push(newField); - this.setByName(newField); - this.setByGroup(newField); - }; + specs.map(field => this.add(field)); + } - remove = (field: IFieldType) => { - this.removeByGroup(field); - this.byName.delete(field.name); + getByName = (name: Field['name']) => this.byName.get(name); + getByType = (type: Field['type']) => [...(this.groups.get(type) || new Map()).values()]; + add = (field: FieldSpec) => { + const newField = new Field(this.indexPattern, field, this.shortDotsEnable, { + fieldFormats, + toastNotifications, + }); + this.push(newField); + this.setByName(newField); + this.setByGroup(newField); + }; - const fieldIndex = findIndex(this, { name: field.name }); - this.splice(fieldIndex, 1); - }; + remove = (field: IFieldType) => { + this.removeByGroup(field); + this.byName.delete(field.name); - update = (field: FieldSpec) => { - const newField = new Field(this.indexPattern, field, this.shortDotsEnable); - const index = this.findIndex(f => f.name === newField.name); - this.splice(index, 1, newField); - this.setByName(newField); - this.removeByGroup(newField); - this.setByGroup(newField); - }; -} + const fieldIndex = findIndex(this, { name: field.name }); + this.splice(fieldIndex, 1); + }; + + update = (field: FieldSpec) => { + const newField = new Field(this.indexPattern, field, this.shortDotsEnable, { + fieldFormats, + toastNotifications, + }); + const index = this.findIndex(f => f.name === newField.name); + this.splice(index, 1, newField); + this.setByName(newField); + this.removeByGroup(newField); + this.setByGroup(newField); + }; + } + + return new FieldList(...fieldListParams); +}; diff --git a/src/plugins/data/public/index_patterns/index.ts b/src/plugins/data/public/index_patterns/index.ts index dcf799184b01c4..e05db0e4d4cec0 100644 --- a/src/plugins/data/public/index_patterns/index.ts +++ b/src/plugins/data/public/index_patterns/index.ts @@ -29,7 +29,7 @@ export { export { getRoutes } from './utils'; export { flattenHitWrapper, formatHitProvider } from './index_patterns'; -export { Field, FieldList } from './fields'; +export { getIndexPatternFieldListCreator, Field, IIndexPatternFieldList } from './fields'; // TODO: figure out how to replace IndexPatterns in get_inner_angular. export { diff --git a/src/plugins/data/public/index_patterns/index_patterns/index_pattern.ts b/src/plugins/data/public/index_patterns/index_patterns/index_pattern.ts index 768029136879da..f39be78433710f 100644 --- a/src/plugins/data/public/index_patterns/index_patterns/index_pattern.ts +++ b/src/plugins/data/public/index_patterns/index_patterns/index_pattern.ts @@ -32,7 +32,7 @@ import { ES_FIELD_TYPES, KBN_FIELD_TYPES, IIndexPattern, IFieldType } from '../. import { findByTitle, getRoutes } from '../utils'; import { IndexPatternMissingIndices } from '../lib'; -import { Field, FieldList, IFieldList } from '../fields'; +import { Field, IIndexPatternFieldList, getIndexPatternFieldListCreator } from '../fields'; import { createFieldsFetcher } from './_fields_fetcher'; import { formatHitProvider } from './format_hit'; import { flattenHitWrapper } from './flatten_hit'; @@ -51,7 +51,7 @@ export class IndexPattern implements IIndexPattern { public type?: string; public fieldFormatMap: any; public typeMeta?: TypeMeta; - public fields: IFieldList; + public fields: IIndexPatternFieldList; public timeFieldName: string | undefined; public formatHit: any; public formatField: any; @@ -106,7 +106,12 @@ export class IndexPattern implements IIndexPattern { this.shortDotsEnable = this.getConfig('shortDots:enable'); this.metaFields = this.getConfig('metaFields'); - this.fields = new FieldList(this, [], this.shortDotsEnable); + this.createFieldList = getIndexPatternFieldListCreator({ + fieldFormats: getFieldFormats(), + toastNotifications: getNotifications().toasts, + }); + + this.fields = this.createFieldList(this, [], this.shortDotsEnable); this.fieldsFetcher = createFieldsFetcher(this, apiClient, this.getConfig('metaFields')); this.flattenHit = flattenHitWrapper(this, this.getConfig('metaFields')); this.formatHit = formatHitProvider( @@ -131,7 +136,7 @@ export class IndexPattern implements IIndexPattern { private initFields(input?: any) { const newValue = input || this.fields; - this.fields = new FieldList(this, newValue, this.shortDotsEnable); + this.fields = this.createFieldList(this, newValue, this.shortDotsEnable); } private isFieldRefreshRequired(): boolean { @@ -281,7 +286,11 @@ export class IndexPattern implements IIndexPattern { filterable: true, searchable: true, }, - false + false, + { + fieldFormats: getFieldFormats(), + toastNotifications: getNotifications().toasts, + } ) ); diff --git a/src/plugins/data/public/index_patterns/index_patterns/index_patterns.test.ts b/src/plugins/data/public/index_patterns/index_patterns/index_patterns.test.ts index cf1f83d0e28cb8..fc0be270e9c506 100644 --- a/src/plugins/data/public/index_patterns/index_patterns/index_patterns.test.ts +++ b/src/plugins/data/public/index_patterns/index_patterns/index_patterns.test.ts @@ -19,12 +19,13 @@ // eslint-disable-next-line max-classes-per-file import { IndexPatternsService } from './index_patterns'; -import { - SavedObjectsClientContract, - HttpSetup, - SavedObjectsFindResponsePublic, - CoreStart, -} from 'kibana/public'; +import { SavedObjectsClientContract, SavedObjectsFindResponsePublic } from 'kibana/public'; +import { coreMock, httpServiceMock } from '../../../../../core/public/mocks'; +import { fieldFormatsServiceMock } from '../../field_formats/mocks'; + +const core = coreMock.createStart(); +const http = httpServiceMock.createStartContract(); +const fieldFormats = fieldFormatsServiceMock.createStartContract(); jest.mock('./index_pattern', () => { class IndexPattern { @@ -61,10 +62,7 @@ describe('IndexPatterns', () => { }) as Promise> ); - const core = {} as CoreStart; - const http = {} as HttpSetup; - - indexPatterns = new IndexPatternsService(core, savedObjectsClient, http); + indexPatterns = new IndexPatternsService(core, savedObjectsClient, http, fieldFormats); }); test('does cache gets for the same id', async () => { diff --git a/src/plugins/data/public/index_patterns/index_patterns/index_patterns.ts b/src/plugins/data/public/index_patterns/index_patterns/index_patterns.ts index 73d5aeaf30710f..515f2e7cf95eec 100644 --- a/src/plugins/data/public/index_patterns/index_patterns/index_patterns.ts +++ b/src/plugins/data/public/index_patterns/index_patterns/index_patterns.ts @@ -32,6 +32,13 @@ import { createEnsureDefaultIndexPattern, EnsureDefaultIndexPattern, } from './ensure_default_index_pattern'; +import { + getIndexPatternFieldListCreator, + CreateIndexPatternFieldList, + Field, + FieldSpec, +} from '../fields'; +import { FieldFormatsStart } from '../../field_formats'; const indexPatternCache = createIndexPatternCache(); @@ -47,12 +54,33 @@ export class IndexPatternsService { private savedObjectsCache?: Array> | null; private apiClient: IndexPatternsApiClient; ensureDefaultIndexPattern: EnsureDefaultIndexPattern; - - constructor(core: CoreStart, savedObjectsClient: SavedObjectsClientContract, http: HttpStart) { + createFieldList: CreateIndexPatternFieldList; + createField: ( + indexPattern: IndexPattern, + spec: FieldSpec | Field, + shortDotsEnable: boolean + ) => Field; + + constructor( + core: CoreStart, + savedObjectsClient: SavedObjectsClientContract, + http: HttpStart, + fieldFormats: FieldFormatsStart + ) { this.apiClient = new IndexPatternsApiClient(http); this.config = core.uiSettings; this.savedObjectsClient = savedObjectsClient; this.ensureDefaultIndexPattern = createEnsureDefaultIndexPattern(core); + this.createFieldList = getIndexPatternFieldListCreator({ + fieldFormats, + toastNotifications: core.notifications.toasts, + }); + this.createField = (indexPattern, spec, shortDotsEnable) => { + return new Field(indexPattern, spec, shortDotsEnable, { + fieldFormats, + toastNotifications: core.notifications.toasts, + }); + }; } private async refreshSavedObjectsCache() { diff --git a/src/plugins/data/public/mocks.ts b/src/plugins/data/public/mocks.ts index ba1df89c413587..7307c93139d592 100644 --- a/src/plugins/data/public/mocks.ts +++ b/src/plugins/data/public/mocks.ts @@ -57,6 +57,8 @@ const createStartContract = (): Start => { SearchBar: jest.fn(), }, indexPatterns: ({ + createField: jest.fn(() => {}), + createFieldList: jest.fn(() => []), ensureDefaultIndexPattern: jest.fn(), make: () => ({ fieldsFetcher: { diff --git a/src/plugins/data/public/plugin.ts b/src/plugins/data/public/plugin.ts index d822e96d0a129f..08796216db1e06 100644 --- a/src/plugins/data/public/plugin.ts +++ b/src/plugins/data/public/plugin.ts @@ -160,7 +160,7 @@ export class DataPublicPlugin implements Plugin CreateIndexPatternFieldList; + // Warning: (ae-missing-release-tag) "getKbnTypeNames" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public @@ -829,6 +838,22 @@ export interface IIndexPattern { type?: string; } +// Warning: (ae-missing-release-tag) "IIndexPatternFieldList" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface IIndexPatternFieldList extends Array { + // (undocumented) + add(field: FieldSpec): void; + // (undocumented) + getByName(name: Field['name']): Field | undefined; + // (undocumented) + getByType(type: Field['type']): Field[]; + // (undocumented) + remove(field: IFieldType): void; + // (undocumented) + update(field: FieldSpec): void; +} + // Warning: (ae-missing-release-tag) "IKibanaSearchRequest" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -870,10 +895,8 @@ export class IndexPattern implements IIndexPattern { _fetchFields(): Promise; // (undocumented) fieldFormatMap: any; - // Warning: (ae-forgotten-export) The symbol "IFieldList" needs to be exported by the entry point index.d.ts - // // (undocumented) - fields: IFieldList; + fields: IIndexPatternFieldList; // (undocumented) fieldsFetcher: any; // (undocumented) @@ -988,23 +1011,6 @@ export interface IndexPatternAttributes { typeMeta: string; } -// Warning: (ae-missing-release-tag) "FieldList" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export class IndexPatternFieldList extends Array implements IFieldList { - constructor(indexPattern: IndexPattern, specs?: FieldSpec[], shortDotsEnable?: boolean); - // (undocumented) - add: (field: Record) => void; - // (undocumented) - getByName: (name: string) => Field | undefined; - // (undocumented) - getByType: (type: string) => any[]; - // (undocumented) - remove: (field: IFieldType) => void; - // (undocumented) - update: (field: Record) => void; -} - // Warning: (ae-missing-release-tag) "indexPatterns" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -1352,7 +1358,7 @@ export interface QueryState { // Warning: (ae-missing-release-tag) "QueryStringInput" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const QueryStringInput: React.FC>; +export const QueryStringInput: React.FC>; // @public (undocumented) export type QuerySuggestion = QuerySuggestionBasic | QuerySuggestionField; @@ -1564,8 +1570,8 @@ export const search: { // Warning: (ae-missing-release-tag) "SearchBar" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const SearchBar: React.ComponentClass, "query" | "isLoading" | "filters" | "indexPatterns" | "refreshInterval" | "screenTitle" | "dataTestSubj" | "customSubmitButton" | "showQueryBar" | "showQueryInput" | "showFilterBar" | "showDatePicker" | "showAutoRefreshOnly" | "isRefreshPaused" | "dateRangeFrom" | "dateRangeTo" | "showSaveQuery" | "savedQuery" | "onQueryChange" | "onQuerySubmit" | "onSaved" | "onSavedQueryUpdated" | "onClearSavedQuery" | "onRefresh" | "timeHistory" | "onFiltersUpdated" | "onRefreshChange">, any> & { - WrappedComponent: React.ComponentType & ReactIntl.InjectedIntlProps>; +export const SearchBar: React.ComponentClass, "query" | "isLoading" | "filters" | "indexPatterns" | "refreshInterval" | "customSubmitButton" | "screenTitle" | "dataTestSubj" | "showQueryBar" | "showQueryInput" | "showFilterBar" | "showDatePicker" | "showAutoRefreshOnly" | "isRefreshPaused" | "dateRangeFrom" | "dateRangeTo" | "showSaveQuery" | "savedQuery" | "onQueryChange" | "onQuerySubmit" | "onSaved" | "onSavedQueryUpdated" | "onClearSavedQuery" | "onRefresh" | "timeHistory" | "onFiltersUpdated" | "onRefreshChange">, any> & { + WrappedComponent: React.ComponentType & ReactIntl.InjectedIntlProps>; }; // Warning: (ae-forgotten-export) The symbol "SearchBarOwnProps" needs to be exported by the entry point index.d.ts @@ -1817,20 +1823,20 @@ export type TSearchStrategyProvider = (context: ISearc // src/plugins/data/public/index.ts:238:27 - (ae-forgotten-export) The symbol "flattenHitWrapper" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:238:27 - (ae-forgotten-export) The symbol "getRoutes" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:238:27 - (ae-forgotten-export) The symbol "formatHitProvider" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:377:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:377:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:377:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:377:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:379:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:380:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:389:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:390:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:391:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:395:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:396:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:399:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:400:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:403:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:378:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:378:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:378:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:378:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:380:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:381:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:390:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:391:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:392:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:396:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:397:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:400:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:401:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:404:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts // src/plugins/data/public/query/state_sync/connect_to_query_state.ts:33:33 - (ae-forgotten-export) The symbol "FilterStateStore" needs to be exported by the entry point index.d.ts // src/plugins/data/public/query/state_sync/connect_to_query_state.ts:37:1 - (ae-forgotten-export) The symbol "QueryStateChange" needs to be exported by the entry point index.d.ts // src/plugins/data/public/types.ts:52:5 - (ae-forgotten-export) The symbol "createFiltersFromValueClickAction" needs to be exported by the entry point index.d.ts diff --git a/src/plugins/data/public/search/aggs/agg_config.ts b/src/plugins/data/public/search/aggs/agg_config.ts index 973c69e3d4f5fa..86a2c3e0e82e40 100644 --- a/src/plugins/data/public/search/aggs/agg_config.ts +++ b/src/plugins/data/public/search/aggs/agg_config.ts @@ -19,7 +19,7 @@ import _ from 'lodash'; import { i18n } from '@kbn/i18n'; -import { Assign } from '@kbn/utility-types'; +import { Assign, Ensure } from '@kbn/utility-types'; import { ExpressionAstFunction, ExpressionAstArgument } from 'src/plugins/expressions/public'; import { IAggType } from './agg_type'; import { writeParams } from './agg_params'; @@ -31,17 +31,22 @@ import { FieldFormatsStart } from '../../field_formats'; type State = string | number | boolean | null | undefined | SerializableState; -interface SerializableState { +/** @internal **/ +export interface SerializableState { [key: string]: State | State[]; } -export interface AggConfigSerialized { - type: string; - enabled?: boolean; - id?: string; - params?: SerializableState; - schema?: string; -} +/** @internal **/ +export type AggConfigSerialized = Ensure< + { + type: string; + enabled?: boolean; + id?: string; + params?: SerializableState; + schema?: string; + }, + SerializableState +>; export interface AggConfigDependencies { fieldFormats: FieldFormatsStart; diff --git a/src/plugins/data/public/search/aggs/agg_types.ts b/src/plugins/data/public/search/aggs/agg_types.ts index da07f581c9274c..7c7d7609cc82f7 100644 --- a/src/plugins/data/public/search/aggs/agg_types.ts +++ b/src/plugins/data/public/search/aggs/agg_types.ts @@ -105,6 +105,29 @@ export const getAggTypes = ({ ], }); +/** Buckets: **/ +import { aggFilter } from './buckets/filter_fn'; +import { aggFilters } from './buckets/filters_fn'; +import { aggSignificantTerms } from './buckets/significant_terms_fn'; +import { aggIpRange } from './buckets/ip_range_fn'; +import { aggDateRange } from './buckets/date_range_fn'; +import { aggRange } from './buckets/range_fn'; +import { aggGeoTile } from './buckets/geo_tile_fn'; +import { aggGeoHash } from './buckets/geo_hash_fn'; +import { aggHistogram } from './buckets/histogram_fn'; +import { aggDateHistogram } from './buckets/date_histogram_fn'; import { aggTerms } from './buckets/terms_fn'; -export const getAggTypesFunctions = () => [aggTerms]; +export const getAggTypesFunctions = () => [ + aggFilter, + aggFilters, + aggSignificantTerms, + aggIpRange, + aggDateRange, + aggRange, + aggGeoTile, + aggGeoHash, + aggDateHistogram, + aggHistogram, + aggTerms, +]; diff --git a/src/plugins/data/public/search/aggs/buckets/date_histogram.ts b/src/plugins/data/public/search/aggs/buckets/date_histogram.ts index 3ecdc17cb57f3f..219bb5440c8dae 100644 --- a/src/plugins/data/public/search/aggs/buckets/date_histogram.ts +++ b/src/plugins/data/public/search/aggs/buckets/date_histogram.ts @@ -27,7 +27,7 @@ import { BucketAggType, IBucketAggConfig } from './bucket_agg_type'; import { BUCKET_TYPES } from './bucket_agg_types'; import { createFilterDateHistogram } from './create_filter/date_histogram'; import { intervalOptions } from './_interval_options'; -import { dateHistogramInterval } from '../../../../common'; +import { dateHistogramInterval, TimeRange } from '../../../../common'; import { writeParams } from '../agg_params'; import { isMetricAggType } from '../metrics/metric_agg_type'; @@ -35,6 +35,8 @@ import { FIELD_FORMAT_IDS, KBN_FIELD_TYPES } from '../../../../common'; import { TimefilterContract } from '../../../query'; import { QuerySetup } from '../../../query/query_service'; import { GetInternalStartServicesFn } from '../../../types'; +import { BaseAggParams } from '../types'; +import { ExtendedBounds } from './lib/extended_bounds'; const detectedTimezone = moment.tz.guess(); const tzOffset = moment().format('Z'); @@ -67,6 +69,19 @@ export function isDateHistogramBucketAggConfig(agg: any): agg is IBucketDateHist return Boolean(agg.buckets); } +export interface AggParamsDateHistogram extends BaseAggParams { + field?: string; + timeRange?: TimeRange; + useNormalizedEsInterval?: boolean; + scaleMetricValues?: boolean; + interval?: string; + time_zone?: string; + drop_partials?: boolean; + format?: string; + min_doc_count?: number; + extended_bounds?: ExtendedBounds; +} + export const getDateHistogramBucketAgg = ({ uiSettings, query, @@ -89,6 +104,7 @@ export const getDateHistogramBucketAgg = ({ } const field = agg.getFieldDisplayName(); + return i18n.translate('data.search.aggs.buckets.dateHistogramLabel', { defaultMessage: '{fieldName} per {intervalDescription}', values: { diff --git a/src/plugins/data/public/search/aggs/buckets/date_histogram_fn.test.ts b/src/plugins/data/public/search/aggs/buckets/date_histogram_fn.test.ts new file mode 100644 index 00000000000000..bd3c4f8dd58cfd --- /dev/null +++ b/src/plugins/data/public/search/aggs/buckets/date_histogram_fn.test.ts @@ -0,0 +1,120 @@ +/* + * 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 { functionWrapper } from '../test_helpers'; +import { aggDateHistogram } from './date_histogram_fn'; + +describe('agg_expression_functions', () => { + describe('aggDateHistogram', () => { + const fn = functionWrapper(aggDateHistogram()); + + test('fills in defaults when only required args are provided', () => { + const actual = fn({}); + expect(actual).toMatchInlineSnapshot(` + Object { + "type": "agg_type", + "value": Object { + "enabled": true, + "id": undefined, + "params": Object { + "customLabel": undefined, + "drop_partials": undefined, + "extended_bounds": undefined, + "field": undefined, + "format": undefined, + "interval": undefined, + "json": undefined, + "min_doc_count": undefined, + "scaleMetricValues": undefined, + "timeRange": undefined, + "time_zone": undefined, + "useNormalizedEsInterval": undefined, + }, + "schema": undefined, + "type": "date_histogram", + }, + } + `); + }); + + test('includes optional params when they are provided', () => { + const actual = fn({ + field: 'field', + timeRange: JSON.stringify({ + from: 'from', + to: 'to', + }), + useNormalizedEsInterval: true, + scaleMetricValues: true, + interval: 'interval', + time_zone: 'time_zone', + drop_partials: false, + format: 'format', + min_doc_count: 1, + extended_bounds: JSON.stringify({ + min: 1, + max: 2, + }), + }); + + expect(actual.value).toMatchInlineSnapshot(` + Object { + "enabled": true, + "id": undefined, + "params": Object { + "customLabel": undefined, + "drop_partials": false, + "extended_bounds": Object { + "max": 2, + "min": 1, + }, + "field": "field", + "format": "format", + "interval": "interval", + "json": undefined, + "min_doc_count": 1, + "scaleMetricValues": true, + "timeRange": Object { + "from": "from", + "to": "to", + }, + "time_zone": "time_zone", + "useNormalizedEsInterval": true, + }, + "schema": undefined, + "type": "date_histogram", + } + `); + }); + + test('correctly parses json string argument', () => { + const actual = fn({ + json: '{ "foo": true }', + }); + + expect(actual.value.params.json).toEqual({ foo: true }); + + expect(() => { + fn({ + json: '/// intentionally malformed json ///', + }); + }).toThrowErrorMatchingInlineSnapshot(`"Unable to parse json argument string"`); + }); + }); +}); diff --git a/src/plugins/data/public/search/aggs/buckets/date_histogram_fn.ts b/src/plugins/data/public/search/aggs/buckets/date_histogram_fn.ts new file mode 100644 index 00000000000000..033b44da0880f6 --- /dev/null +++ b/src/plugins/data/public/search/aggs/buckets/date_histogram_fn.ts @@ -0,0 +1,155 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import { Assign } from '@kbn/utility-types'; +import { ExpressionFunctionDefinition } from '../../../../../expressions/public'; +import { AggExpressionType, AggExpressionFunctionArgs, BUCKET_TYPES } from '../'; +import { getParsedValue } from '../utils/get_parsed_value'; + +const fnName = 'aggDateHistogram'; + +type Input = any; +type AggArgs = AggExpressionFunctionArgs; + +type Arguments = Assign; + +type Output = AggExpressionType; +type FunctionDefinition = ExpressionFunctionDefinition; + +export const aggDateHistogram = (): FunctionDefinition => ({ + name: fnName, + help: i18n.translate('data.search.aggs.function.buckets.dateHistogram.help', { + defaultMessage: 'Generates a serialized agg config for a Histogram agg', + }), + type: 'agg_type', + args: { + id: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.dateHistogram.id.help', { + defaultMessage: 'ID for this aggregation', + }), + }, + enabled: { + types: ['boolean'], + default: true, + help: i18n.translate('data.search.aggs.buckets.dateHistogram.enabled.help', { + defaultMessage: 'Specifies whether this aggregation should be enabled', + }), + }, + schema: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.dateHistogram.schema.help', { + defaultMessage: 'Schema to use for this aggregation', + }), + }, + field: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.dateHistogram.field.help', { + defaultMessage: 'Field to use for this aggregation', + }), + }, + useNormalizedEsInterval: { + types: ['boolean'], + help: i18n.translate('data.search.aggs.buckets.dateHistogram.useNormalizedEsInterval.help', { + defaultMessage: 'Specifies whether to use useNormalizedEsInterval for this aggregation', + }), + }, + time_zone: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.dateHistogram.timeZone.help', { + defaultMessage: 'Time zone to use for this aggregation', + }), + }, + format: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.dateHistogram.format.help', { + defaultMessage: 'Format to use for this aggregation', + }), + }, + scaleMetricValues: { + types: ['boolean'], + help: i18n.translate('data.search.aggs.buckets.dateHistogram.scaleMetricValues.help', { + defaultMessage: 'Specifies whether to use scaleMetricValues for this aggregation', + }), + }, + interval: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.dateHistogram.interval.help', { + defaultMessage: 'Interval to use for this aggregation', + }), + }, + timeRange: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.dateHistogram.timeRange.help', { + defaultMessage: 'Time Range to use for this aggregation', + }), + }, + min_doc_count: { + types: ['number'], + help: i18n.translate('data.search.aggs.buckets.dateHistogram.minDocCount.help', { + defaultMessage: 'Minimum document count to use for this aggregation', + }), + }, + drop_partials: { + types: ['boolean'], + help: i18n.translate('data.search.aggs.buckets.dateHistogram.dropPartials.help', { + defaultMessage: 'Specifies whether to use drop_partials for this aggregation', + }), + }, + extended_bounds: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.dateHistogram.extendedBounds.help', { + defaultMessage: + 'With extended_bounds setting, you now can "force" the histogram aggregation to start building buckets on a specific min value and also keep on building buckets up to a max value ', + }), + }, + json: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.dateHistogram.json.help', { + defaultMessage: 'Advanced json to include when the agg is sent to Elasticsearch', + }), + }, + customLabel: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.dateHistogram.customLabel.help', { + defaultMessage: 'Represents a custom label for this aggregation', + }), + }, + }, + fn: (input, args) => { + const { id, enabled, schema, ...rest } = args; + + return { + type: 'agg_type', + value: { + id, + enabled, + schema, + type: BUCKET_TYPES.DATE_HISTOGRAM, + params: { + ...rest, + timeRange: getParsedValue(args, 'timeRange'), + extended_bounds: getParsedValue(args, 'extended_bounds'), + json: getParsedValue(args, 'json'), + }, + }, + }; + }, +}); diff --git a/src/plugins/data/public/search/aggs/buckets/date_range.ts b/src/plugins/data/public/search/aggs/buckets/date_range.ts index 07d927e64a943d..504958854cad4d 100644 --- a/src/plugins/data/public/search/aggs/buckets/date_range.ts +++ b/src/plugins/data/public/search/aggs/buckets/date_range.ts @@ -29,6 +29,7 @@ import { convertDateRangeToString, DateRangeKey } from './lib/date_range'; import { KBN_FIELD_TYPES, FieldFormat, TEXT_CONTEXT_TYPE } from '../../../../common'; import { GetInternalStartServicesFn } from '../../../types'; +import { BaseAggParams } from '../types'; const dateRangeTitle = i18n.translate('data.search.aggs.buckets.dateRangeTitle', { defaultMessage: 'Date Range', @@ -39,6 +40,12 @@ export interface DateRangeBucketAggDependencies { getInternalStartServices: GetInternalStartServicesFn; } +export interface AggParamsDateRange extends BaseAggParams { + field?: string; + ranges?: DateRangeKey[]; + time_zone?: string; +} + export const getDateRangeBucketAgg = ({ uiSettings, getInternalStartServices, diff --git a/src/plugins/data/public/search/aggs/buckets/date_range_fn.test.ts b/src/plugins/data/public/search/aggs/buckets/date_range_fn.test.ts new file mode 100644 index 00000000000000..93bb791874e677 --- /dev/null +++ b/src/plugins/data/public/search/aggs/buckets/date_range_fn.test.ts @@ -0,0 +1,101 @@ +/* + * 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 { functionWrapper } from '../test_helpers'; +import { aggDateRange } from './date_range_fn'; + +describe('agg_expression_functions', () => { + describe('aggDateRange', () => { + const fn = functionWrapper(aggDateRange()); + + test('fills in defaults when only required args are provided', () => { + const actual = fn({}); + expect(actual).toMatchInlineSnapshot(` + Object { + "type": "agg_type", + "value": Object { + "enabled": true, + "id": undefined, + "params": Object { + "customLabel": undefined, + "field": undefined, + "json": undefined, + "ranges": undefined, + "time_zone": undefined, + }, + "schema": undefined, + "type": "date_range", + }, + } + `); + }); + + test('includes optional params when they are provided', () => { + const actual = fn({ + field: 'date_field', + time_zone: 'UTC +3', + ranges: JSON.stringify([ + { from: 'now-1w/w', to: 'now' }, + { from: 1588163532470, to: 1588163532481 }, + ]), + }); + + expect(actual.value).toMatchInlineSnapshot(` + Object { + "enabled": true, + "id": undefined, + "params": Object { + "customLabel": undefined, + "field": "date_field", + "json": undefined, + "ranges": Array [ + Object { + "from": "now-1w/w", + "to": "now", + }, + Object { + "from": 1588163532470, + "to": 1588163532481, + }, + ], + "time_zone": "UTC +3", + }, + "schema": undefined, + "type": "date_range", + } + `); + }); + + test('correctly parses json string argument', () => { + const actual = fn({ + field: 'date_field', + json: '{ "foo": true }', + }); + + expect(actual.value.params.json).toEqual({ foo: true }); + + expect(() => { + fn({ + field: 'date_field', + json: '/// intentionally malformed json ///', + }); + }).toThrowErrorMatchingInlineSnapshot(`"Unable to parse json argument string"`); + }); + }); +}); diff --git a/src/plugins/data/public/search/aggs/buckets/date_range_fn.ts b/src/plugins/data/public/search/aggs/buckets/date_range_fn.ts new file mode 100644 index 00000000000000..1fe42ce63d8157 --- /dev/null +++ b/src/plugins/data/public/search/aggs/buckets/date_range_fn.ts @@ -0,0 +1,111 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import { Assign } from '@kbn/utility-types'; +import { ExpressionFunctionDefinition } from '../../../../../expressions/public'; +import { AggExpressionType, AggExpressionFunctionArgs, BUCKET_TYPES } from '../'; +import { getParsedValue } from '../utils/get_parsed_value'; + +const fnName = 'aggDateRange'; + +type Input = any; +type AggArgs = AggExpressionFunctionArgs; + +type Arguments = Assign; + +type Output = AggExpressionType; +type FunctionDefinition = ExpressionFunctionDefinition; + +export const aggDateRange = (): FunctionDefinition => ({ + name: fnName, + help: i18n.translate('data.search.aggs.function.buckets.dateRange.help', { + defaultMessage: 'Generates a serialized agg config for a Date Range agg', + }), + type: 'agg_type', + args: { + id: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.dateRange.id.help', { + defaultMessage: 'ID for this aggregation', + }), + }, + enabled: { + types: ['boolean'], + default: true, + help: i18n.translate('data.search.aggs.buckets.dateRange.enabled.help', { + defaultMessage: 'Specifies whether this aggregation should be enabled', + }), + }, + schema: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.dateRange.schema.help', { + defaultMessage: 'Schema to use for this aggregation', + }), + }, + field: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.dateRange.field.help', { + defaultMessage: 'Field to use for this aggregation', + }), + }, + ranges: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.dateRange.ranges.help', { + defaultMessage: 'Serialized ranges to use for this aggregation.', + }), + }, + time_zone: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.dateRange.timeZone.help', { + defaultMessage: 'Time zone to use for this aggregation.', + }), + }, + json: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.dateRange.json.help', { + defaultMessage: 'Advanced json to include when the agg is sent to Elasticsearch', + }), + }, + customLabel: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.dateRange.customLabel.help', { + defaultMessage: 'Represents a custom label for this aggregation', + }), + }, + }, + fn: (input, args) => { + const { id, enabled, schema, ...rest } = args; + + return { + type: 'agg_type', + value: { + id, + enabled, + schema, + type: BUCKET_TYPES.DATE_RANGE, + params: { + ...rest, + json: getParsedValue(args, 'json'), + ranges: getParsedValue(args, 'ranges'), + }, + }, + }; + }, +}); diff --git a/src/plugins/data/public/search/aggs/buckets/filter.ts b/src/plugins/data/public/search/aggs/buckets/filter.ts index accbdf4dd783d9..69157edad4f680 100644 --- a/src/plugins/data/public/search/aggs/buckets/filter.ts +++ b/src/plugins/data/public/search/aggs/buckets/filter.ts @@ -21,6 +21,8 @@ import { i18n } from '@kbn/i18n'; import { BucketAggType } from './bucket_agg_type'; import { BUCKET_TYPES } from './bucket_agg_types'; import { GetInternalStartServicesFn } from '../../../types'; +import { GeoBoundingBox } from './lib/geo_point'; +import { BaseAggParams } from '../types'; const filterTitle = i18n.translate('data.search.aggs.buckets.filterTitle', { defaultMessage: 'Filter', @@ -30,6 +32,10 @@ export interface FilterBucketAggDependencies { getInternalStartServices: GetInternalStartServicesFn; } +export interface AggParamsFilter extends BaseAggParams { + geo_bounding_box?: GeoBoundingBox; +} + export const getFilterBucketAgg = ({ getInternalStartServices }: FilterBucketAggDependencies) => new BucketAggType( { diff --git a/src/plugins/data/public/search/aggs/buckets/filter_fn.test.ts b/src/plugins/data/public/search/aggs/buckets/filter_fn.test.ts new file mode 100644 index 00000000000000..c820a73b0a8944 --- /dev/null +++ b/src/plugins/data/public/search/aggs/buckets/filter_fn.test.ts @@ -0,0 +1,85 @@ +/* + * 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 { functionWrapper } from '../test_helpers'; +import { aggFilter } from './filter_fn'; + +describe('agg_expression_functions', () => { + describe('aggFilter', () => { + const fn = functionWrapper(aggFilter()); + + test('fills in defaults when only required args are provided', () => { + const actual = fn({}); + expect(actual).toMatchInlineSnapshot(` + Object { + "type": "agg_type", + "value": Object { + "enabled": true, + "id": undefined, + "params": Object { + "customLabel": undefined, + "geo_bounding_box": undefined, + "json": undefined, + }, + "schema": undefined, + "type": "filter", + }, + } + `); + }); + + test('includes optional params when they are provided', () => { + const actual = fn({ + geo_bounding_box: JSON.stringify({ + wkt: 'BBOX (-74.1, -71.12, 40.73, 40.01)', + }), + }); + + expect(actual.value).toMatchInlineSnapshot(` + Object { + "enabled": true, + "id": undefined, + "params": Object { + "customLabel": undefined, + "geo_bounding_box": Object { + "wkt": "BBOX (-74.1, -71.12, 40.73, 40.01)", + }, + "json": undefined, + }, + "schema": undefined, + "type": "filter", + } + `); + }); + + test('correctly parses json string argument', () => { + const actual = fn({ + json: '{ "foo": true }', + }); + + expect(actual.value.params.json).toEqual({ foo: true }); + + expect(() => { + fn({ + json: '/// intentionally malformed json ///', + }); + }).toThrowErrorMatchingInlineSnapshot(`"Unable to parse json argument string"`); + }); + }); +}); diff --git a/src/plugins/data/public/search/aggs/buckets/filter_fn.ts b/src/plugins/data/public/search/aggs/buckets/filter_fn.ts new file mode 100644 index 00000000000000..4a7180fc86c714 --- /dev/null +++ b/src/plugins/data/public/search/aggs/buckets/filter_fn.ts @@ -0,0 +1,99 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import { Assign } from '@kbn/utility-types'; +import { ExpressionFunctionDefinition } from '../../../../../expressions/public'; +import { AggExpressionType, AggExpressionFunctionArgs, BUCKET_TYPES } from '../'; +import { getParsedValue } from '../utils/get_parsed_value'; + +const fnName = 'aggFilter'; + +type Input = any; +type AggArgs = AggExpressionFunctionArgs; + +type Arguments = Assign; + +type Output = AggExpressionType; +type FunctionDefinition = ExpressionFunctionDefinition; + +export const aggFilter = (): FunctionDefinition => ({ + name: fnName, + help: i18n.translate('data.search.aggs.function.buckets.filter.help', { + defaultMessage: 'Generates a serialized agg config for a Filter agg', + }), + type: 'agg_type', + args: { + id: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.filter.id.help', { + defaultMessage: 'ID for this aggregation', + }), + }, + enabled: { + types: ['boolean'], + default: true, + help: i18n.translate('data.search.aggs.buckets.filter.enabled.help', { + defaultMessage: 'Specifies whether this aggregation should be enabled', + }), + }, + schema: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.filter.schema.help', { + defaultMessage: 'Schema to use for this aggregation', + }), + }, + geo_bounding_box: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.filter.geoBoundingBox.help', { + defaultMessage: 'Filter results based on a point location within a bounding box', + }), + }, + json: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.filter.json.help', { + defaultMessage: 'Advanced json to include when the agg is sent to Elasticsearch', + }), + }, + customLabel: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.filter.customLabel.help', { + defaultMessage: 'Represents a custom label for this aggregation', + }), + }, + }, + fn: (input, args) => { + const { id, enabled, schema, ...rest } = args; + + return { + type: 'agg_type', + value: { + id, + enabled, + schema, + type: BUCKET_TYPES.FILTER, + params: { + ...rest, + json: getParsedValue(args, 'json'), + geo_bounding_box: getParsedValue(args, 'geo_bounding_box'), + }, + }, + }; + }, +}); diff --git a/src/plugins/data/public/search/aggs/buckets/filters.ts b/src/plugins/data/public/search/aggs/buckets/filters.ts index fe013928bba65a..c993c0fc138009 100644 --- a/src/plugins/data/public/search/aggs/buckets/filters.ts +++ b/src/plugins/data/public/search/aggs/buckets/filters.ts @@ -29,6 +29,7 @@ import { Storage } from '../../../../../../plugins/kibana_utils/public'; import { getEsQueryConfig, buildEsQuery, Query } from '../../../../common'; import { getQueryLog } from '../../../query'; import { GetInternalStartServicesFn } from '../../../types'; +import { BaseAggParams } from '../types'; const filtersTitle = i18n.translate('data.search.aggs.buckets.filtersTitle', { defaultMessage: 'Filters', @@ -47,6 +48,13 @@ export interface FiltersBucketAggDependencies { getInternalStartServices: GetInternalStartServicesFn; } +export interface AggParamsFilters extends BaseAggParams { + filters?: Array<{ + input: Query; + label: string; + }>; +} + export const getFiltersBucketAgg = ({ uiSettings, getInternalStartServices, diff --git a/src/plugins/data/public/search/aggs/buckets/filters_fn.test.ts b/src/plugins/data/public/search/aggs/buckets/filters_fn.test.ts new file mode 100644 index 00000000000000..99c4f7d8c2b659 --- /dev/null +++ b/src/plugins/data/public/search/aggs/buckets/filters_fn.test.ts @@ -0,0 +1,91 @@ +/* + * 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 { functionWrapper } from '../test_helpers'; +import { aggFilters } from './filters_fn'; + +describe('agg_expression_functions', () => { + describe('aggFilters', () => { + const fn = functionWrapper(aggFilters()); + + test('fills in defaults when only required args are provided', () => { + const actual = fn({}); + expect(actual).toMatchInlineSnapshot(` + Object { + "type": "agg_type", + "value": Object { + "enabled": true, + "id": undefined, + "params": Object { + "filters": undefined, + "json": undefined, + }, + "schema": undefined, + "type": "filters", + }, + } + `); + }); + + test('includes optional params when they are provided', () => { + const actual = fn({ + filters: JSON.stringify([ + { + query: 'query', + language: 'lucene', + label: 'test', + }, + ]), + }); + + expect(actual.value).toMatchInlineSnapshot(` + Object { + "enabled": true, + "id": undefined, + "params": Object { + "filters": Array [ + Object { + "label": "test", + "language": "lucene", + "query": "query", + }, + ], + "json": undefined, + }, + "schema": undefined, + "type": "filters", + } + `); + }); + + test('correctly parses json string argument', () => { + const actual = fn({ + json: '{ "foo": true }', + }); + + expect(actual.value.params.json).toEqual({ foo: true }); + + expect(() => { + fn({ + json: '/// intentionally malformed json ///', + }); + }).toThrowErrorMatchingInlineSnapshot(`"Unable to parse json argument string"`); + }); + }); +}); diff --git a/src/plugins/data/public/search/aggs/buckets/filters_fn.ts b/src/plugins/data/public/search/aggs/buckets/filters_fn.ts new file mode 100644 index 00000000000000..6ffd5369d70876 --- /dev/null +++ b/src/plugins/data/public/search/aggs/buckets/filters_fn.ts @@ -0,0 +1,93 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import { Assign } from '@kbn/utility-types'; +import { ExpressionFunctionDefinition } from '../../../../../expressions/public'; +import { AggExpressionType, AggExpressionFunctionArgs, BUCKET_TYPES } from '../'; +import { getParsedValue } from '../utils/get_parsed_value'; + +const fnName = 'aggFilters'; + +type Input = any; +type AggArgs = AggExpressionFunctionArgs; + +type Arguments = Assign; + +type Output = AggExpressionType; +type FunctionDefinition = ExpressionFunctionDefinition; + +export const aggFilters = (): FunctionDefinition => ({ + name: fnName, + help: i18n.translate('data.search.aggs.function.buckets.filters.help', { + defaultMessage: 'Generates a serialized agg config for a Filter agg', + }), + type: 'agg_type', + args: { + id: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.filters.id.help', { + defaultMessage: 'ID for this aggregation', + }), + }, + enabled: { + types: ['boolean'], + default: true, + help: i18n.translate('data.search.aggs.buckets.filters.enabled.help', { + defaultMessage: 'Specifies whether this aggregation should be enabled', + }), + }, + schema: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.filters.schema.help', { + defaultMessage: 'Schema to use for this aggregation', + }), + }, + filters: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.filters.filters.help', { + defaultMessage: 'Filters to use for this aggregation', + }), + }, + json: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.filters.json.help', { + defaultMessage: 'Advanced json to include when the agg is sent to Elasticsearch', + }), + }, + }, + fn: (input, args) => { + const { id, enabled, schema, ...rest } = args; + + return { + type: 'agg_type', + value: { + id, + enabled, + schema, + type: BUCKET_TYPES.FILTERS, + params: { + ...rest, + filters: getParsedValue(args, 'filters'), + json: getParsedValue(args, 'json'), + }, + }, + }; + }, +}); diff --git a/src/plugins/data/public/search/aggs/buckets/geo_hash.ts b/src/plugins/data/public/search/aggs/buckets/geo_hash.ts index eab10edad60f63..be339de5d7faea 100644 --- a/src/plugins/data/public/search/aggs/buckets/geo_hash.ts +++ b/src/plugins/data/public/search/aggs/buckets/geo_hash.ts @@ -22,6 +22,8 @@ import { BucketAggType, IBucketAggConfig } from './bucket_agg_type'; import { KBN_FIELD_TYPES } from '../../../../common'; import { BUCKET_TYPES } from './bucket_agg_types'; import { GetInternalStartServicesFn } from '../../../types'; +import { GeoBoundingBox } from './lib/geo_point'; +import { BaseAggParams } from '../types'; const defaultBoundingBox = { top_left: { lat: 1, lon: 1 }, @@ -38,6 +40,15 @@ export interface GeoHashBucketAggDependencies { getInternalStartServices: GetInternalStartServicesFn; } +export interface AggParamsGeoHash extends BaseAggParams { + field: string; + autoPrecision?: boolean; + precision?: number; + useGeocentroid?: boolean; + isFilteredByCollar?: boolean; + boundingBox?: GeoBoundingBox; +} + export const getGeoHashBucketAgg = ({ getInternalStartServices }: GeoHashBucketAggDependencies) => new BucketAggType( { diff --git a/src/plugins/data/public/search/aggs/buckets/geo_hash_fn.test.ts b/src/plugins/data/public/search/aggs/buckets/geo_hash_fn.test.ts new file mode 100644 index 00000000000000..07ab8e66f1defe --- /dev/null +++ b/src/plugins/data/public/search/aggs/buckets/geo_hash_fn.test.ts @@ -0,0 +1,112 @@ +/* + * 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 { functionWrapper } from '../test_helpers'; +import { aggGeoHash } from './geo_hash_fn'; + +describe('agg_expression_functions', () => { + describe('aggGeoHash', () => { + const fn = functionWrapper(aggGeoHash()); + + test('fills in defaults when only required args are provided', () => { + const actual = fn({ + field: 'geo_field', + }); + expect(actual).toMatchInlineSnapshot(` + Object { + "type": "agg_type", + "value": Object { + "enabled": true, + "id": undefined, + "params": Object { + "autoPrecision": undefined, + "boundingBox": undefined, + "customLabel": undefined, + "field": "geo_field", + "isFilteredByCollar": undefined, + "json": undefined, + "precision": undefined, + "useGeocentroid": undefined, + }, + "schema": undefined, + "type": "geohash_grid", + }, + } + `); + }); + + test('includes optional params when they are provided', () => { + const actual = fn({ + field: 'geo_field', + autoPrecision: false, + precision: 10, + useGeocentroid: true, + isFilteredByCollar: false, + boundingBox: JSON.stringify({ + top_left: [-74.1, 40.73], + bottom_right: [-71.12, 40.01], + }), + }); + + expect(actual.value).toMatchInlineSnapshot(` + Object { + "enabled": true, + "id": undefined, + "params": Object { + "autoPrecision": false, + "boundingBox": Object { + "bottom_right": Array [ + -71.12, + 40.01, + ], + "top_left": Array [ + -74.1, + 40.73, + ], + }, + "customLabel": undefined, + "field": "geo_field", + "isFilteredByCollar": false, + "json": undefined, + "precision": 10, + "useGeocentroid": true, + }, + "schema": undefined, + "type": "geohash_grid", + } + `); + }); + + test('correctly parses json string argument', () => { + const actual = fn({ + field: 'geo_field', + json: '{ "foo": true }', + }); + + expect(actual.value.params.json).toEqual({ foo: true }); + + expect(() => { + fn({ + field: 'geo_field', + json: '/// intentionally malformed json ///', + }); + }).toThrowErrorMatchingInlineSnapshot(`"Unable to parse json argument string"`); + }); + }); +}); diff --git a/src/plugins/data/public/search/aggs/buckets/geo_hash_fn.ts b/src/plugins/data/public/search/aggs/buckets/geo_hash_fn.ts new file mode 100644 index 00000000000000..bbfa8575d486ce --- /dev/null +++ b/src/plugins/data/public/search/aggs/buckets/geo_hash_fn.ts @@ -0,0 +1,129 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import { Assign } from '@kbn/utility-types'; +import { ExpressionFunctionDefinition } from '../../../../../expressions/public'; +import { AggExpressionType, AggExpressionFunctionArgs, BUCKET_TYPES } from '../'; +import { getParsedValue } from '../utils/get_parsed_value'; + +const fnName = 'aggGeoHash'; + +type Input = any; +type AggArgs = AggExpressionFunctionArgs; + +type Arguments = Assign; +type Output = AggExpressionType; +type FunctionDefinition = ExpressionFunctionDefinition; + +export const aggGeoHash = (): FunctionDefinition => ({ + name: fnName, + help: i18n.translate('data.search.aggs.function.buckets.geoHash.help', { + defaultMessage: 'Generates a serialized agg config for a Geo Hash agg', + }), + type: 'agg_type', + args: { + id: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.geoHash.id.help', { + defaultMessage: 'ID for this aggregation', + }), + }, + enabled: { + types: ['boolean'], + default: true, + help: i18n.translate('data.search.aggs.buckets.geoHash.enabled.help', { + defaultMessage: 'Specifies whether this aggregation should be enabled', + }), + }, + schema: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.geoHash.schema.help', { + defaultMessage: 'Schema to use for this aggregation', + }), + }, + field: { + types: ['string'], + required: true, + help: i18n.translate('data.search.aggs.buckets.geoHash.field.help', { + defaultMessage: 'Field to use for this aggregation', + }), + }, + useGeocentroid: { + types: ['boolean'], + help: i18n.translate('data.search.aggs.buckets.geoHash.useGeocentroid.help', { + defaultMessage: 'Specifies whether to use geocentroid for this aggregation', + }), + }, + autoPrecision: { + types: ['boolean'], + help: i18n.translate('data.search.aggs.buckets.geoHash.autoPrecision.help', { + defaultMessage: 'Specifies whether to use auto precision for this aggregation', + }), + }, + isFilteredByCollar: { + types: ['boolean'], + help: i18n.translate('data.search.aggs.buckets.geoHash.isFilteredByCollar.help', { + defaultMessage: 'Specifies whether to filter by collar', + }), + }, + boundingBox: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.geoHash.boundingBox.help', { + defaultMessage: 'Filter results based on a point location within a bounding box', + }), + }, + precision: { + types: ['number'], + help: i18n.translate('data.search.aggs.buckets.geoHash.precision.help', { + defaultMessage: 'Precision to use for this aggregation.', + }), + }, + json: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.geoHash.json.help', { + defaultMessage: 'Advanced json to include when the agg is sent to Elasticsearch', + }), + }, + customLabel: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.geoHash.customLabel.help', { + defaultMessage: 'Represents a custom label for this aggregation', + }), + }, + }, + fn: (input, args) => { + const { id, enabled, schema, ...rest } = args; + + return { + type: 'agg_type', + value: { + id, + enabled, + schema, + type: BUCKET_TYPES.GEOHASH_GRID, + params: { + ...rest, + boundingBox: getParsedValue(args, 'boundingBox'), + json: getParsedValue(args, 'json'), + }, + }, + }; + }, +}); diff --git a/src/plugins/data/public/search/aggs/buckets/geo_tile.ts b/src/plugins/data/public/search/aggs/buckets/geo_tile.ts index c981e8400f9a12..1212bba23a93aa 100644 --- a/src/plugins/data/public/search/aggs/buckets/geo_tile.ts +++ b/src/plugins/data/public/search/aggs/buckets/geo_tile.ts @@ -25,6 +25,7 @@ import { BUCKET_TYPES } from './bucket_agg_types'; import { KBN_FIELD_TYPES } from '../../../../common'; import { METRIC_TYPES } from '../metrics/metric_agg_types'; import { GetInternalStartServicesFn } from '../../../types'; +import { BaseAggParams } from '../types'; export interface GeoTitleBucketAggDependencies { getInternalStartServices: GetInternalStartServicesFn; @@ -34,6 +35,12 @@ const geotileGridTitle = i18n.translate('data.search.aggs.buckets.geotileGridTit defaultMessage: 'Geotile', }); +export interface AggParamsGeoTile extends BaseAggParams { + field: string; + useGeocentroid?: boolean; + precision?: number; +} + export const getGeoTitleBucketAgg = ({ getInternalStartServices }: GeoTitleBucketAggDependencies) => new BucketAggType( { diff --git a/src/plugins/data/public/search/aggs/buckets/geo_tile_fn.test.ts b/src/plugins/data/public/search/aggs/buckets/geo_tile_fn.test.ts new file mode 100644 index 00000000000000..bfaf47ede87343 --- /dev/null +++ b/src/plugins/data/public/search/aggs/buckets/geo_tile_fn.test.ts @@ -0,0 +1,91 @@ +/* + * 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 { functionWrapper } from '../test_helpers'; +import { aggGeoTile } from './geo_tile_fn'; + +describe('agg_expression_functions', () => { + describe('aggGeoTile', () => { + const fn = functionWrapper(aggGeoTile()); + + test('fills in defaults when only required args are provided', () => { + const actual = fn({ + field: 'geo_field', + }); + expect(actual).toMatchInlineSnapshot(` + Object { + "type": "agg_type", + "value": Object { + "enabled": true, + "id": undefined, + "params": Object { + "customLabel": undefined, + "field": "geo_field", + "json": undefined, + "precision": undefined, + "useGeocentroid": undefined, + }, + "schema": undefined, + "type": "geotile_grid", + }, + } + `); + }); + + test('includes optional params when they are provided', () => { + const actual = fn({ + field: 'geo_field', + useGeocentroid: false, + precision: 10, + }); + + expect(actual.value).toMatchInlineSnapshot(` + Object { + "enabled": true, + "id": undefined, + "params": Object { + "customLabel": undefined, + "field": "geo_field", + "json": undefined, + "precision": 10, + "useGeocentroid": false, + }, + "schema": undefined, + "type": "geotile_grid", + } + `); + }); + + test('correctly parses json string argument', () => { + const actual = fn({ + field: 'geo_field', + json: '{ "foo": true }', + }); + + expect(actual.value.params.json).toEqual({ foo: true }); + + expect(() => { + fn({ + field: 'geo_field', + json: '/// intentionally malformed json ///', + }); + }).toThrowErrorMatchingInlineSnapshot(`"Unable to parse json argument string"`); + }); + }); +}); diff --git a/src/plugins/data/public/search/aggs/buckets/geo_tile_fn.ts b/src/plugins/data/public/search/aggs/buckets/geo_tile_fn.ts new file mode 100644 index 00000000000000..9c33ef45762af4 --- /dev/null +++ b/src/plugins/data/public/search/aggs/buckets/geo_tile_fn.ts @@ -0,0 +1,108 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import { ExpressionFunctionDefinition } from '../../../../../expressions/public'; +import { AggExpressionType, AggExpressionFunctionArgs, BUCKET_TYPES } from '../'; +import { getParsedValue } from '../utils/get_parsed_value'; + +const fnName = 'aggGeoTile'; + +type Input = any; +type AggArgs = AggExpressionFunctionArgs; + +type Output = AggExpressionType; +type FunctionDefinition = ExpressionFunctionDefinition; + +export const aggGeoTile = (): FunctionDefinition => ({ + name: fnName, + help: i18n.translate('data.search.aggs.function.buckets.geoTile.help', { + defaultMessage: 'Generates a serialized agg config for a Geo Tile agg', + }), + type: 'agg_type', + args: { + id: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.geoTile.id.help', { + defaultMessage: 'ID for this aggregation', + }), + }, + enabled: { + types: ['boolean'], + default: true, + help: i18n.translate('data.search.aggs.buckets.geoTile.enabled.help', { + defaultMessage: 'Specifies whether this aggregation should be enabled', + }), + }, + schema: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.geoTile.schema.help', { + defaultMessage: 'Schema to use for this aggregation', + }), + }, + field: { + types: ['string'], + required: true, + help: i18n.translate('data.search.aggs.buckets.geoTile.field.help', { + defaultMessage: 'Field to use for this aggregation', + }), + }, + useGeocentroid: { + types: ['boolean'], + help: i18n.translate('data.search.aggs.buckets.geoTile.useGeocentroid.help', { + defaultMessage: 'Specifies whether to use geocentroid for this aggregation', + }), + }, + precision: { + types: ['number'], + help: i18n.translate('data.search.aggs.buckets.geoTile.precision.help', { + defaultMessage: 'Precision to use for this aggregation.', + }), + }, + json: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.geoTile.json.help', { + defaultMessage: 'Advanced json to include when the agg is sent to Elasticsearch', + }), + }, + customLabel: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.geoTile.customLabel.help', { + defaultMessage: 'Represents a custom label for this aggregation', + }), + }, + }, + fn: (input, args) => { + const { id, enabled, schema, ...rest } = args; + + return { + type: 'agg_type', + value: { + id, + enabled, + schema, + type: BUCKET_TYPES.GEOTILE_GRID, + params: { + ...rest, + json: getParsedValue(args, 'json'), + }, + }, + }; + }, +}); diff --git a/src/plugins/data/public/search/aggs/buckets/histogram.ts b/src/plugins/data/public/search/aggs/buckets/histogram.ts index f8e8720d24ea97..d04df4f8aac6ba 100644 --- a/src/plugins/data/public/search/aggs/buckets/histogram.ts +++ b/src/plugins/data/public/search/aggs/buckets/histogram.ts @@ -26,6 +26,8 @@ import { createFilterHistogram } from './create_filter/histogram'; import { BUCKET_TYPES } from './bucket_agg_types'; import { KBN_FIELD_TYPES } from '../../../../common'; import { GetInternalStartServicesFn } from '../../../types'; +import { BaseAggParams } from '../types'; +import { ExtendedBounds } from './lib/extended_bounds'; export interface AutoBounds { min: number; @@ -42,6 +44,15 @@ export interface IBucketHistogramAggConfig extends IBucketAggConfig { getAutoBounds: () => AutoBounds; } +export interface AggParamsHistogram extends BaseAggParams { + field: string; + interval: string; + intervalBase?: number; + min_doc_count?: boolean; + has_extended_bounds?: boolean; + extended_bounds?: ExtendedBounds; +} + export const getHistogramBucketAgg = ({ uiSettings, getInternalStartServices, diff --git a/src/plugins/data/public/search/aggs/buckets/histogram_fn.test.ts b/src/plugins/data/public/search/aggs/buckets/histogram_fn.test.ts new file mode 100644 index 00000000000000..34b6fa1a6dcd6d --- /dev/null +++ b/src/plugins/data/public/search/aggs/buckets/histogram_fn.test.ts @@ -0,0 +1,109 @@ +/* + * 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 { functionWrapper } from '../test_helpers'; +import { aggHistogram } from './histogram_fn'; + +describe('agg_expression_functions', () => { + describe('aggHistogram', () => { + const fn = functionWrapper(aggHistogram()); + + test('fills in defaults when only required args are provided', () => { + const actual = fn({ + field: 'field', + interval: '10', + }); + expect(actual).toMatchInlineSnapshot(` + Object { + "type": "agg_type", + "value": Object { + "enabled": true, + "id": undefined, + "params": Object { + "customLabel": undefined, + "extended_bounds": undefined, + "field": "field", + "has_extended_bounds": undefined, + "interval": "10", + "intervalBase": undefined, + "json": undefined, + "min_doc_count": undefined, + }, + "schema": undefined, + "type": "histogram", + }, + } + `); + }); + + test('includes optional params when they are provided', () => { + const actual = fn({ + field: 'field', + interval: '10', + intervalBase: 1, + min_doc_count: false, + has_extended_bounds: false, + extended_bounds: JSON.stringify({ + min: 1, + max: 2, + }), + }); + + expect(actual.value).toMatchInlineSnapshot(` + Object { + "enabled": true, + "id": undefined, + "params": Object { + "customLabel": undefined, + "extended_bounds": Object { + "max": 2, + "min": 1, + }, + "field": "field", + "has_extended_bounds": false, + "interval": "10", + "intervalBase": 1, + "json": undefined, + "min_doc_count": false, + }, + "schema": undefined, + "type": "histogram", + } + `); + }); + + test('correctly parses json string argument', () => { + const actual = fn({ + field: 'field', + interval: '10', + json: '{ "foo": true }', + }); + + expect(actual.value.params.json).toEqual({ foo: true }); + + expect(() => { + fn({ + field: 'field', + interval: '10', + json: '/// intentionally malformed json ///', + }); + }).toThrowErrorMatchingInlineSnapshot(`"Unable to parse json argument string"`); + }); + }); +}); diff --git a/src/plugins/data/public/search/aggs/buckets/histogram_fn.ts b/src/plugins/data/public/search/aggs/buckets/histogram_fn.ts new file mode 100644 index 00000000000000..1e5a5a72c0ecbd --- /dev/null +++ b/src/plugins/data/public/search/aggs/buckets/histogram_fn.ts @@ -0,0 +1,132 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import { Assign } from '@kbn/utility-types'; +import { ExpressionFunctionDefinition } from '../../../../../expressions/public'; +import { AggExpressionType, AggExpressionFunctionArgs, BUCKET_TYPES } from '../'; +import { getParsedValue } from '../utils/get_parsed_value'; + +const fnName = 'aggHistogram'; + +type Input = any; +type AggArgs = AggExpressionFunctionArgs; + +type Arguments = Assign; + +type Output = AggExpressionType; +type FunctionDefinition = ExpressionFunctionDefinition; + +export const aggHistogram = (): FunctionDefinition => ({ + name: fnName, + help: i18n.translate('data.search.aggs.function.buckets.histogram.help', { + defaultMessage: 'Generates a serialized agg config for a Histogram agg', + }), + type: 'agg_type', + args: { + id: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.histogram.id.help', { + defaultMessage: 'ID for this aggregation', + }), + }, + enabled: { + types: ['boolean'], + default: true, + help: i18n.translate('data.search.aggs.buckets.histogram.enabled.help', { + defaultMessage: 'Specifies whether this aggregation should be enabled', + }), + }, + schema: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.histogram.schema.help', { + defaultMessage: 'Schema to use for this aggregation', + }), + }, + field: { + types: ['string'], + required: true, + help: i18n.translate('data.search.aggs.buckets.histogram.field.help', { + defaultMessage: 'Field to use for this aggregation', + }), + }, + interval: { + types: ['string'], + required: true, + help: i18n.translate('data.search.aggs.buckets.histogram.interval.help', { + defaultMessage: 'Interval to use for this aggregation', + }), + }, + intervalBase: { + types: ['number'], + help: i18n.translate('data.search.aggs.buckets.histogram.intervalBase.help', { + defaultMessage: 'IntervalBase to use for this aggregation', + }), + }, + min_doc_count: { + types: ['boolean'], + help: i18n.translate('data.search.aggs.buckets.histogram.minDocCount.help', { + defaultMessage: 'Specifies whether to use min_doc_count for this aggregation', + }), + }, + has_extended_bounds: { + types: ['boolean'], + help: i18n.translate('data.search.aggs.buckets.histogram.hasExtendedBounds.help', { + defaultMessage: 'Specifies whether to use has_extended_bounds for this aggregation', + }), + }, + extended_bounds: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.histogram.extendedBounds.help', { + defaultMessage: + 'With extended_bounds setting, you now can "force" the histogram aggregation to start building buckets on a specific min value and also keep on building buckets up to a max value ', + }), + }, + json: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.histogram.json.help', { + defaultMessage: 'Advanced json to include when the agg is sent to Elasticsearch', + }), + }, + customLabel: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.histogram.customLabel.help', { + defaultMessage: 'Represents a custom label for this aggregation', + }), + }, + }, + fn: (input, args) => { + const { id, enabled, schema, ...rest } = args; + + return { + type: 'agg_type', + value: { + id, + enabled, + schema, + type: BUCKET_TYPES.HISTOGRAM, + params: { + ...rest, + extended_bounds: getParsedValue(args, 'extended_bounds'), + json: getParsedValue(args, 'json'), + }, + }, + }; + }, +}); diff --git a/src/plugins/data/public/search/aggs/buckets/index.ts b/src/plugins/data/public/search/aggs/buckets/index.ts index 3a402b1498a775..7036cc7785db7f 100644 --- a/src/plugins/data/public/search/aggs/buckets/index.ts +++ b/src/plugins/data/public/search/aggs/buckets/index.ts @@ -19,11 +19,18 @@ export * from './_interval_options'; export * from './bucket_agg_types'; +export * from './histogram'; export * from './date_histogram'; export * from './date_range'; +export * from './range'; +export * from './filter'; +export * from './filters'; +export * from './geo_tile'; +export * from './geo_hash'; export * from './ip_range'; export * from './lib/cidr_mask'; export * from './lib/date_range'; export * from './lib/ip_range'; export * from './migrate_include_exclude_format'; +export * from './significant_terms'; export * from './terms'; diff --git a/src/plugins/data/public/search/aggs/buckets/ip_range.ts b/src/plugins/data/public/search/aggs/buckets/ip_range.ts index bde347d6e673d1..029fd864154bea 100644 --- a/src/plugins/data/public/search/aggs/buckets/ip_range.ts +++ b/src/plugins/data/public/search/aggs/buckets/ip_range.ts @@ -23,18 +23,38 @@ import { BucketAggType } from './bucket_agg_type'; import { BUCKET_TYPES } from './bucket_agg_types'; import { createFilterIpRange } from './create_filter/ip_range'; -import { IpRangeKey, convertIPRangeToString } from './lib/ip_range'; +import { + convertIPRangeToString, + IpRangeKey, + RangeIpRangeAggKey, + CidrMaskIpRangeAggKey, +} from './lib/ip_range'; import { KBN_FIELD_TYPES, FieldFormat, TEXT_CONTEXT_TYPE } from '../../../../common'; import { GetInternalStartServicesFn } from '../../../types'; +import { BaseAggParams } from '../types'; const ipRangeTitle = i18n.translate('data.search.aggs.buckets.ipRangeTitle', { defaultMessage: 'IPv4 Range', }); +export enum IP_RANGE_TYPES { + FROM_TO = 'fromTo', + MASK = 'mask', +} + export interface IpRangeBucketAggDependencies { getInternalStartServices: GetInternalStartServicesFn; } +export interface AggParamsIpRange extends BaseAggParams { + field: string; + ipRangeType?: IP_RANGE_TYPES; + ranges?: Partial<{ + [IP_RANGE_TYPES.FROM_TO]: RangeIpRangeAggKey[]; + [IP_RANGE_TYPES.MASK]: CidrMaskIpRangeAggKey[]; + }>; +} + export const getIpRangeBucketAgg = ({ getInternalStartServices }: IpRangeBucketAggDependencies) => new BucketAggType( { @@ -42,7 +62,7 @@ export const getIpRangeBucketAgg = ({ getInternalStartServices }: IpRangeBucketA title: ipRangeTitle, createFilter: createFilterIpRange, getKey(bucket, key, agg): IpRangeKey { - if (agg.params.ipRangeType === 'mask') { + if (agg.params.ipRangeType === IP_RANGE_TYPES.MASK) { return { type: 'mask', mask: key }; } return { type: 'range', from: bucket.from, to: bucket.to }; @@ -74,7 +94,7 @@ export const getIpRangeBucketAgg = ({ getInternalStartServices }: IpRangeBucketA }, { name: 'ipRangeType', - default: 'fromTo', + default: IP_RANGE_TYPES.FROM_TO, write: noop, }, { @@ -90,7 +110,7 @@ export const getIpRangeBucketAgg = ({ getInternalStartServices }: IpRangeBucketA const ipRangeType = aggConfig.params.ipRangeType; let ranges = aggConfig.params.ranges[ipRangeType]; - if (ipRangeType === 'fromTo') { + if (ipRangeType === IP_RANGE_TYPES.FROM_TO) { ranges = map(ranges, (range: any) => omit(range, isNull)); } diff --git a/src/plugins/data/public/search/aggs/buckets/ip_range_fn.test.ts b/src/plugins/data/public/search/aggs/buckets/ip_range_fn.test.ts new file mode 100644 index 00000000000000..5940345b258904 --- /dev/null +++ b/src/plugins/data/public/search/aggs/buckets/ip_range_fn.test.ts @@ -0,0 +1,102 @@ +/* + * 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 { functionWrapper } from '../test_helpers'; +import { IP_RANGE_TYPES } from './ip_range'; +import { aggIpRange } from './ip_range_fn'; + +describe('agg_expression_functions', () => { + describe('aggIpRange', () => { + const fn = functionWrapper(aggIpRange()); + + test('fills in defaults when only required args are provided', () => { + const actual = fn({ + field: 'ip_field', + }); + expect(actual).toMatchInlineSnapshot(` + Object { + "type": "agg_type", + "value": Object { + "enabled": true, + "id": undefined, + "params": Object { + "customLabel": undefined, + "field": "ip_field", + "ipRangeType": undefined, + "json": undefined, + "ranges": undefined, + }, + "schema": undefined, + "type": "ip_range", + }, + } + `); + }); + + test('includes optional params when they are provided', () => { + const actual = fn({ + field: 'ip_field', + ipRangeType: IP_RANGE_TYPES.MASK, + ranges: JSON.stringify({ + mask: [{ mask: '10.0.0.0/25' }], + }), + }); + + expect(actual.value).toMatchInlineSnapshot(` + Object { + "enabled": true, + "id": undefined, + "params": Object { + "customLabel": undefined, + "field": "ip_field", + "ipRangeType": "mask", + "json": undefined, + "ranges": Object { + "mask": Array [ + Object { + "mask": "10.0.0.0/25", + }, + ], + }, + }, + "schema": undefined, + "type": "ip_range", + } + `); + }); + + test('correctly parses json string argument', () => { + const actual = fn({ + field: 'ip_field', + ipRangeType: IP_RANGE_TYPES.MASK, + json: '{ "foo": true }', + }); + + expect(actual.value.params.json).toEqual({ foo: true }); + + expect(() => { + fn({ + field: 'ip_field', + ipRangeType: IP_RANGE_TYPES.FROM_TO, + json: '/// intentionally malformed json ///', + }); + }).toThrowErrorMatchingInlineSnapshot(`"Unable to parse json argument string"`); + }); + }); +}); diff --git a/src/plugins/data/public/search/aggs/buckets/ip_range_fn.ts b/src/plugins/data/public/search/aggs/buckets/ip_range_fn.ts new file mode 100644 index 00000000000000..554a8708d91642 --- /dev/null +++ b/src/plugins/data/public/search/aggs/buckets/ip_range_fn.ts @@ -0,0 +1,114 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import { Assign } from '@kbn/utility-types'; +import { ExpressionFunctionDefinition } from '../../../../../expressions/public'; +import { AggExpressionType, AggExpressionFunctionArgs, BUCKET_TYPES } from '../'; +import { getParsedValue } from '../utils/get_parsed_value'; + +const fnName = 'aggIpRange'; + +type Input = any; +type AggArgs = AggExpressionFunctionArgs; + +type Arguments = Assign; + +type Output = AggExpressionType; +type FunctionDefinition = ExpressionFunctionDefinition; + +export const aggIpRange = (): FunctionDefinition => ({ + name: fnName, + help: i18n.translate('data.search.aggs.function.buckets.ipRange.help', { + defaultMessage: 'Generates a serialized agg config for a Ip Range agg', + }), + type: 'agg_type', + args: { + id: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.ipRange.id.help', { + defaultMessage: 'ID for this aggregation', + }), + }, + enabled: { + types: ['boolean'], + default: true, + help: i18n.translate('data.search.aggs.buckets.ipRange.enabled.help', { + defaultMessage: 'Specifies whether this aggregation should be enabled', + }), + }, + schema: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.ipRange.schema.help', { + defaultMessage: 'Schema to use for this aggregation', + }), + }, + field: { + types: ['string'], + required: true, + help: i18n.translate('data.search.aggs.buckets.ipRange.field.help', { + defaultMessage: 'Field to use for this aggregation', + }), + }, + ipRangeType: { + types: ['string'], + options: ['mask', 'fromTo'], + help: i18n.translate('data.search.aggs.buckets.ipRange.ipRangeType.help', { + defaultMessage: + 'IP range type to use for this aggregation. Takes one of the following values: mask, fromTo.', + }), + }, + ranges: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.ipRange.ranges.help', { + defaultMessage: 'Serialized ranges to use for this aggregation.', + }), + }, + json: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.ipRange.json.help', { + defaultMessage: 'Advanced json to include when the agg is sent to Elasticsearch', + }), + }, + customLabel: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.ipRange.customLabel.help', { + defaultMessage: 'Represents a custom label for this aggregation', + }), + }, + }, + fn: (input, args) => { + const { id, enabled, schema, ...rest } = args; + + return { + type: 'agg_type', + value: { + id, + enabled, + schema, + type: BUCKET_TYPES.IP_RANGE, + params: { + ...rest, + json: getParsedValue(args, 'json'), + ranges: getParsedValue(args, 'ranges'), + }, + }, + }; + }, +}); diff --git a/src/plugins/data/public/search/aggs/buckets/lib/date_range.ts b/src/plugins/data/public/search/aggs/buckets/lib/date_range.ts index 6eb9fe8414ec8f..d52bdff993a2b1 100644 --- a/src/plugins/data/public/search/aggs/buckets/lib/date_range.ts +++ b/src/plugins/data/public/search/aggs/buckets/lib/date_range.ts @@ -18,8 +18,8 @@ */ export interface DateRangeKey { - from: number; - to: number; + from: number | string; + to: number | string; } export function convertDateRangeToString({ from, to }: DateRangeKey, format: (val: any) => string) { diff --git a/src/plugins/discover/public/helpers/index.ts b/src/plugins/data/public/search/aggs/buckets/lib/extended_bounds.ts similarity index 92% rename from src/plugins/discover/public/helpers/index.ts rename to src/plugins/data/public/search/aggs/buckets/lib/extended_bounds.ts index 7196c96989e97d..7a249a9daca91f 100644 --- a/src/plugins/discover/public/helpers/index.ts +++ b/src/plugins/data/public/search/aggs/buckets/lib/extended_bounds.ts @@ -17,4 +17,7 @@ * under the License. */ -export { shortenDottedString } from './shorten_dotted_string'; +export interface ExtendedBounds { + min: number; + max: number; +} diff --git a/src/plugins/data/public/search/aggs/buckets/lib/geo_point.ts b/src/plugins/data/public/search/aggs/buckets/lib/geo_point.ts new file mode 100644 index 00000000000000..8ff4493e286cf2 --- /dev/null +++ b/src/plugins/data/public/search/aggs/buckets/lib/geo_point.ts @@ -0,0 +1,86 @@ +/* + * 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. + */ + +type GeoPoint = + | { + lat: number; + lon: number; + } + | string + | [number, number]; + +interface GeoBox { + top: number; + left: number; + bottom: number; + right: number; +} + +/** GeoBoundingBox Accepted Formats: + * Lat Lon As Properties: + * "top_left" : { + * "lat" : 40.73, "lon" : -74.1 + * }, + * "bottom_right" : { + * "lat" : 40.01, "lon" : -71.12 + * } + * + * Lat Lon As Array: + * { + * "top_left" : [-74.1, 40.73], + * "bottom_right" : [-71.12, 40.01] + * } + * + * Lat Lon As String: + * { + * "top_left" : "40.73, -74.1", + * "bottom_right" : "40.01, -71.12" + * } + * + * Bounding Box as Well-Known Text (WKT): + * { + * "wkt" : "BBOX (-74.1, -71.12, 40.73, 40.01)" + * } + * + * Geohash: + * { + * "top_right" : "dr5r9ydj2y73", + * "bottom_left" : "drj7teegpus6" + * } + * + * Vertices: + * { + * "top" : 40.73, + * "left" : -74.1, + * "bottom" : 40.01, + * "right" : -71.12 + * } + * + * **/ +export type GeoBoundingBox = + | Partial<{ + top_left: GeoPoint; + top_right: GeoPoint; + bottom_right: GeoPoint; + bottom_left: GeoPoint; + }> + | { + wkt: string; + } + | GeoBox; diff --git a/src/plugins/data/public/search/aggs/buckets/lib/ip_range.ts b/src/plugins/data/public/search/aggs/buckets/lib/ip_range.ts index be1ac28934c7cd..57e5337d4c3654 100644 --- a/src/plugins/data/public/search/aggs/buckets/lib/ip_range.ts +++ b/src/plugins/data/public/search/aggs/buckets/lib/ip_range.ts @@ -17,9 +17,18 @@ * under the License. */ -export type IpRangeKey = - | { type: 'mask'; mask: string } - | { type: 'range'; from: string; to: string }; +export interface CidrMaskIpRangeAggKey { + type: 'mask'; + mask: string; +} + +export interface RangeIpRangeAggKey { + type: 'range'; + from: string; + to: string; +} + +export type IpRangeKey = CidrMaskIpRangeAggKey | RangeIpRangeAggKey; export const convertIPRangeToString = (range: IpRangeKey, format: (val: any) => string) => { if (range.type === 'mask') { diff --git a/src/plugins/data/public/search/aggs/buckets/range.ts b/src/plugins/data/public/search/aggs/buckets/range.ts index 2c1303814a88ab..02aad3bd5fed12 100644 --- a/src/plugins/data/public/search/aggs/buckets/range.ts +++ b/src/plugins/data/public/search/aggs/buckets/range.ts @@ -24,6 +24,7 @@ import { RangeKey } from './range_key'; import { createFilterRange } from './create_filter/range'; import { BUCKET_TYPES } from './bucket_agg_types'; import { GetInternalStartServicesFn } from '../../../types'; +import { BaseAggParams } from '../types'; const keyCaches = new WeakMap(); const formats = new WeakMap(); @@ -36,6 +37,14 @@ export interface RangeBucketAggDependencies { getInternalStartServices: GetInternalStartServicesFn; } +export interface AggParamsRange extends BaseAggParams { + field: string; + ranges?: Array<{ + from: number; + to: number; + }>; +} + export const getRangeBucketAgg = ({ getInternalStartServices }: RangeBucketAggDependencies) => new BucketAggType( { diff --git a/src/plugins/data/public/search/aggs/buckets/range_fn.test.ts b/src/plugins/data/public/search/aggs/buckets/range_fn.test.ts new file mode 100644 index 00000000000000..93ae4490196a82 --- /dev/null +++ b/src/plugins/data/public/search/aggs/buckets/range_fn.test.ts @@ -0,0 +1,100 @@ +/* + * 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 { functionWrapper } from '../test_helpers'; +import { aggRange } from './range_fn'; + +describe('agg_expression_functions', () => { + describe('aggRange', () => { + const fn = functionWrapper(aggRange()); + + test('fills in defaults when only required args are provided', () => { + const actual = fn({ + field: 'number_field', + }); + expect(actual).toMatchInlineSnapshot(` + Object { + "type": "agg_type", + "value": Object { + "enabled": true, + "id": undefined, + "params": Object { + "customLabel": undefined, + "field": "number_field", + "json": undefined, + "ranges": undefined, + }, + "schema": undefined, + "type": "range", + }, + } + `); + }); + + test('includes optional params when they are provided', () => { + const actual = fn({ + field: 'number_field', + ranges: JSON.stringify([ + { from: 1, to: 2 }, + { from: 5, to: 100 }, + ]), + }); + + expect(actual.value).toMatchInlineSnapshot(` + Object { + "enabled": true, + "id": undefined, + "params": Object { + "customLabel": undefined, + "field": "number_field", + "json": undefined, + "ranges": Array [ + Object { + "from": 1, + "to": 2, + }, + Object { + "from": 5, + "to": 100, + }, + ], + }, + "schema": undefined, + "type": "range", + } + `); + }); + + test('correctly parses json string argument', () => { + const actual = fn({ + field: 'number_field', + json: '{ "foo": true }', + }); + + expect(actual.value.params.json).toEqual({ foo: true }); + + expect(() => { + fn({ + field: 'number_field', + json: '/// intentionally malformed json ///', + }); + }).toThrowErrorMatchingInlineSnapshot(`"Unable to parse json argument string"`); + }); + }); +}); diff --git a/src/plugins/data/public/search/aggs/buckets/range_fn.ts b/src/plugins/data/public/search/aggs/buckets/range_fn.ts new file mode 100644 index 00000000000000..48686e7061de96 --- /dev/null +++ b/src/plugins/data/public/search/aggs/buckets/range_fn.ts @@ -0,0 +1,106 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import { Assign } from '@kbn/utility-types'; +import { ExpressionFunctionDefinition } from '../../../../../expressions/public'; +import { AggExpressionType, AggExpressionFunctionArgs, BUCKET_TYPES } from '../'; +import { getParsedValue } from '../utils/get_parsed_value'; + +const fnName = 'aggRange'; + +type Input = any; +type AggArgs = AggExpressionFunctionArgs; + +type Arguments = Assign; + +type Output = AggExpressionType; +type FunctionDefinition = ExpressionFunctionDefinition; + +export const aggRange = (): FunctionDefinition => ({ + name: fnName, + help: i18n.translate('data.search.aggs.function.buckets.range.help', { + defaultMessage: 'Generates a serialized agg config for a Range agg', + }), + type: 'agg_type', + args: { + id: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.range.id.help', { + defaultMessage: 'ID for this aggregation', + }), + }, + enabled: { + types: ['boolean'], + default: true, + help: i18n.translate('data.search.aggs.buckets.range.enabled.help', { + defaultMessage: 'Specifies whether this aggregation should be enabled', + }), + }, + schema: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.range.schema.help', { + defaultMessage: 'Schema to use for this aggregation', + }), + }, + field: { + types: ['string'], + required: true, + help: i18n.translate('data.search.aggs.buckets.range.field.help', { + defaultMessage: 'Field to use for this aggregation', + }), + }, + ranges: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.range.ranges.help', { + defaultMessage: 'Serialized ranges to use for this aggregation.', + }), + }, + json: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.range.json.help', { + defaultMessage: 'Advanced json to include when the agg is sent to Elasticsearch', + }), + }, + customLabel: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.range.customLabel.help', { + defaultMessage: 'Represents a custom label for this aggregation', + }), + }, + }, + fn: (input, args) => { + const { id, enabled, schema, ...rest } = args; + + return { + type: 'agg_type', + value: { + id, + enabled, + schema, + type: BUCKET_TYPES.RANGE, + params: { + ...rest, + json: getParsedValue(args, 'json'), + ranges: getParsedValue(args, 'ranges'), + }, + }, + }; + }, +}); diff --git a/src/plugins/data/public/search/aggs/buckets/significant_terms.ts b/src/plugins/data/public/search/aggs/buckets/significant_terms.ts index 49d797f3afbc9a..e6afc56dfd31c0 100644 --- a/src/plugins/data/public/search/aggs/buckets/significant_terms.ts +++ b/src/plugins/data/public/search/aggs/buckets/significant_terms.ts @@ -24,6 +24,7 @@ import { isStringType, migrateIncludeExcludeFormat } from './migrate_include_exc import { BUCKET_TYPES } from './bucket_agg_types'; import { KBN_FIELD_TYPES } from '../../../../common'; import { GetInternalStartServicesFn } from '../../../types'; +import { BaseAggParams } from '../types'; const significantTermsTitle = i18n.translate('data.search.aggs.buckets.significantTermsTitle', { defaultMessage: 'Significant Terms', @@ -33,6 +34,13 @@ export interface SignificantTermsBucketAggDependencies { getInternalStartServices: GetInternalStartServicesFn; } +export interface AggParamsSignificantTerms extends BaseAggParams { + field: string; + size?: number; + exclude?: string; + include?: string; +} + export const getSignificantTermsBucketAgg = ({ getInternalStartServices, }: SignificantTermsBucketAggDependencies) => diff --git a/src/plugins/data/public/search/aggs/buckets/significant_terms_fn.test.ts b/src/plugins/data/public/search/aggs/buckets/significant_terms_fn.test.ts new file mode 100644 index 00000000000000..71be4e9cfa9ac9 --- /dev/null +++ b/src/plugins/data/public/search/aggs/buckets/significant_terms_fn.test.ts @@ -0,0 +1,96 @@ +/* + * 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 { functionWrapper } from '../test_helpers'; +import { aggSignificantTerms } from './significant_terms_fn'; + +describe('agg_expression_functions', () => { + describe('aggSignificantTerms', () => { + const fn = functionWrapper(aggSignificantTerms()); + + test('fills in defaults when only required args are provided', () => { + const actual = fn({ + field: 'machine.os.keyword', + }); + expect(actual).toMatchInlineSnapshot(` + Object { + "type": "agg_type", + "value": Object { + "enabled": true, + "id": undefined, + "params": Object { + "customLabel": undefined, + "exclude": undefined, + "field": "machine.os.keyword", + "include": undefined, + "json": undefined, + "size": undefined, + }, + "schema": undefined, + "type": "significant_terms", + }, + } + `); + }); + + test('includes optional params when they are provided', () => { + const actual = fn({ + id: '1', + enabled: false, + schema: 'whatever', + field: 'machine.os.keyword', + size: 6, + include: 'win', + exclude: 'ios', + }); + + expect(actual.value).toMatchInlineSnapshot(` + Object { + "enabled": false, + "id": "1", + "params": Object { + "customLabel": undefined, + "exclude": "ios", + "field": "machine.os.keyword", + "include": "win", + "json": undefined, + "size": 6, + }, + "schema": "whatever", + "type": "significant_terms", + } + `); + }); + + test('correctly parses json string argument', () => { + const actual = fn({ + field: 'machine.os.keyword', + json: '{ "foo": true }', + }); + + expect(actual.value.params.json).toEqual({ foo: true }); + expect(() => { + fn({ + field: 'machine.os.keyword', + json: '/// intentionally malformed json ///', + }); + }).toThrowErrorMatchingInlineSnapshot(`"Unable to parse json argument string"`); + }); + }); +}); diff --git a/src/plugins/data/public/search/aggs/buckets/significant_terms_fn.ts b/src/plugins/data/public/search/aggs/buckets/significant_terms_fn.ts new file mode 100644 index 00000000000000..83583070bddfed --- /dev/null +++ b/src/plugins/data/public/search/aggs/buckets/significant_terms_fn.ts @@ -0,0 +1,116 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import { ExpressionFunctionDefinition } from '../../../../../expressions/public'; +import { AggExpressionType, AggExpressionFunctionArgs, BUCKET_TYPES } from '../'; +import { getParsedValue } from '../utils/get_parsed_value'; + +const fnName = 'aggSignificantTerms'; + +type Input = any; +type AggArgs = AggExpressionFunctionArgs; + +type Arguments = AggArgs; + +type Output = AggExpressionType; +type FunctionDefinition = ExpressionFunctionDefinition; + +export const aggSignificantTerms = (): FunctionDefinition => ({ + name: fnName, + help: i18n.translate('data.search.aggs.function.buckets.significantTerms.help', { + defaultMessage: 'Generates a serialized agg config for a Significant Terms agg', + }), + type: 'agg_type', + args: { + id: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.significantTerms.id.help', { + defaultMessage: 'ID for this aggregation', + }), + }, + enabled: { + types: ['boolean'], + default: true, + help: i18n.translate('data.search.aggs.buckets.significantTerms.enabled.help', { + defaultMessage: 'Specifies whether this aggregation should be enabled', + }), + }, + schema: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.significantTerms.schema.help', { + defaultMessage: 'Schema to use for this aggregation', + }), + }, + field: { + types: ['string'], + required: true, + help: i18n.translate('data.search.aggs.buckets.significantTerms.field.help', { + defaultMessage: 'Field to use for this aggregation', + }), + }, + size: { + types: ['number'], + help: i18n.translate('data.search.aggs.buckets.significantTerms.size.help', { + defaultMessage: 'Max number of buckets to retrieve', + }), + }, + exclude: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.significantTerms.exclude.help', { + defaultMessage: 'Specific bucket values to exclude from results', + }), + }, + include: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.significantTerms.include.help', { + defaultMessage: 'Specific bucket values to include in results', + }), + }, + json: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.significantTerms.json.help', { + defaultMessage: 'Advanced json to include when the agg is sent to Elasticsearch', + }), + }, + customLabel: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.significantTerms.customLabel.help', { + defaultMessage: 'Represents a custom label for this aggregation', + }), + }, + }, + fn: (input, args) => { + const { id, enabled, schema, ...rest } = args; + + return { + type: 'agg_type', + value: { + id, + enabled, + schema, + type: BUCKET_TYPES.SIGNIFICANT_TERMS, + params: { + ...rest, + json: getParsedValue(args, 'json'), + }, + }, + }; + }, +}); diff --git a/src/plugins/data/public/search/aggs/buckets/terms.ts b/src/plugins/data/public/search/aggs/buckets/terms.ts index a12a1d7ac2d3d5..1bfc508dc38716 100644 --- a/src/plugins/data/public/search/aggs/buckets/terms.ts +++ b/src/plugins/data/public/search/aggs/buckets/terms.ts @@ -26,7 +26,7 @@ import { isStringOrNumberType, migrateIncludeExcludeFormat, } from './migrate_include_exclude_format'; -import { AggConfigSerialized, IAggConfigs } from '../types'; +import { AggConfigSerialized, BaseAggParams, IAggConfigs } from '../types'; import { Adapters } from '../../../../../inspector/public'; import { ISearchSource } from '../../search_source'; @@ -63,11 +63,11 @@ export interface TermsBucketAggDependencies { getInternalStartServices: GetInternalStartServicesFn; } -export interface AggParamsTerms { +export interface AggParamsTerms extends BaseAggParams { field: string; - order: 'asc' | 'desc'; orderBy: string; orderAgg?: AggConfigSerialized; + order?: 'asc' | 'desc'; size?: number; missingBucket?: boolean; missingBucketLabel?: string; @@ -76,7 +76,6 @@ export interface AggParamsTerms { // advanced exclude?: string; include?: string; - json?: string; } export const getTermsBucketAgg = ({ getInternalStartServices }: TermsBucketAggDependencies) => diff --git a/src/plugins/data/public/search/aggs/buckets/terms_fn.test.ts b/src/plugins/data/public/search/aggs/buckets/terms_fn.test.ts index f55f1de7960138..1384a9f17e4b69 100644 --- a/src/plugins/data/public/search/aggs/buckets/terms_fn.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/terms_fn.test.ts @@ -27,7 +27,6 @@ describe('agg_expression_functions', () => { test('fills in defaults when only required args are provided', () => { const actual = fn({ field: 'machine.os.keyword', - order: 'asc', orderBy: '1', }); expect(actual).toMatchInlineSnapshot(` @@ -37,18 +36,19 @@ describe('agg_expression_functions', () => { "enabled": true, "id": undefined, "params": Object { + "customLabel": undefined, "exclude": undefined, "field": "machine.os.keyword", "include": undefined, "json": undefined, - "missingBucket": false, - "missingBucketLabel": "Missing", - "order": "asc", + "missingBucket": undefined, + "missingBucketLabel": undefined, + "order": undefined, "orderAgg": undefined, "orderBy": "1", - "otherBucket": false, - "otherBucketLabel": "Other", - "size": 5, + "otherBucket": undefined, + "otherBucketLabel": undefined, + "size": undefined, }, "schema": undefined, "type": "terms", @@ -70,6 +70,7 @@ describe('agg_expression_functions', () => { missingBucketLabel: 'missing', otherBucket: true, otherBucketLabel: 'other', + include: 'win', exclude: 'ios', }); @@ -78,9 +79,10 @@ describe('agg_expression_functions', () => { "enabled": false, "id": "1", "params": Object { + "customLabel": undefined, "exclude": "ios", "field": "machine.os.keyword", - "include": undefined, + "include": "win", "json": undefined, "missingBucket": true, "missingBucketLabel": "missing", @@ -107,37 +109,39 @@ describe('agg_expression_functions', () => { expect(actual.value.params).toMatchInlineSnapshot(` Object { + "customLabel": undefined, "exclude": undefined, "field": "machine.os.keyword", "include": undefined, "json": undefined, - "missingBucket": false, - "missingBucketLabel": "Missing", + "missingBucket": undefined, + "missingBucketLabel": undefined, "order": "asc", "orderAgg": Object { "enabled": true, "id": undefined, "params": Object { + "customLabel": undefined, "exclude": undefined, "field": "name", "include": undefined, "json": undefined, - "missingBucket": false, - "missingBucketLabel": "Missing", + "missingBucket": undefined, + "missingBucketLabel": undefined, "order": "asc", "orderAgg": undefined, "orderBy": "1", - "otherBucket": false, - "otherBucketLabel": "Other", - "size": 5, + "otherBucket": undefined, + "otherBucketLabel": undefined, + "size": undefined, }, "schema": undefined, "type": "terms", }, "orderBy": "1", - "otherBucket": false, - "otherBucketLabel": "Other", - "size": 5, + "otherBucket": undefined, + "otherBucketLabel": undefined, + "size": undefined, } `); }); diff --git a/src/plugins/data/public/search/aggs/buckets/terms_fn.ts b/src/plugins/data/public/search/aggs/buckets/terms_fn.ts index 7980bfabe79fb1..49520863fe1cca 100644 --- a/src/plugins/data/public/search/aggs/buckets/terms_fn.ts +++ b/src/plugins/data/public/search/aggs/buckets/terms_fn.ts @@ -20,27 +20,25 @@ import { i18n } from '@kbn/i18n'; import { Assign } from '@kbn/utility-types'; import { ExpressionFunctionDefinition } from '../../../../../expressions/public'; -import { AggExpressionType, AggExpressionFunctionArgs } from '../'; +import { AggExpressionType, AggExpressionFunctionArgs, BUCKET_TYPES } from '../'; +import { getParsedValue } from '../utils/get_parsed_value'; -const aggName = 'terms'; const fnName = 'aggTerms'; type Input = any; -type AggArgs = AggExpressionFunctionArgs; +type AggArgs = AggExpressionFunctionArgs; + // Since the orderAgg param is an agg nested in a subexpression, we need to // overwrite the param type to expect a value of type AggExpressionType. -type Arguments = AggArgs & - Assign< - AggArgs, - { orderAgg?: AggArgs['orderAgg'] extends undefined ? undefined : AggExpressionType } - >; +type Arguments = Assign; + type Output = AggExpressionType; type FunctionDefinition = ExpressionFunctionDefinition; export const aggTerms = (): FunctionDefinition => ({ name: fnName, help: i18n.translate('data.search.aggs.function.buckets.terms.help', { - defaultMessage: 'Generates a serialized agg config for a terms agg', + defaultMessage: 'Generates a serialized agg config for a Terms agg', }), type: 'agg_type', args: { @@ -72,7 +70,7 @@ export const aggTerms = (): FunctionDefinition => ({ }, order: { types: ['string'], - required: true, + options: ['asc', 'desc'], help: i18n.translate('data.search.aggs.buckets.terms.order.help', { defaultMessage: 'Order in which to return the results: asc or desc', }), @@ -91,41 +89,30 @@ export const aggTerms = (): FunctionDefinition => ({ }, size: { types: ['number'], - default: 5, help: i18n.translate('data.search.aggs.buckets.terms.size.help', { defaultMessage: 'Max number of buckets to retrieve', }), }, missingBucket: { types: ['boolean'], - default: false, help: i18n.translate('data.search.aggs.buckets.terms.missingBucket.help', { defaultMessage: 'When set to true, groups together any buckets with missing fields', }), }, missingBucketLabel: { types: ['string'], - default: i18n.translate('data.search.aggs.buckets.terms.missingBucketLabel', { - defaultMessage: 'Missing', - description: `Default label used in charts when documents are missing a field. - Visible when you create a chart with a terms aggregation and enable "Show missing values"`, - }), help: i18n.translate('data.search.aggs.buckets.terms.missingBucketLabel.help', { defaultMessage: 'Default label used in charts when documents are missing a field.', }), }, otherBucket: { types: ['boolean'], - default: false, help: i18n.translate('data.search.aggs.buckets.terms.otherBucket.help', { defaultMessage: 'When set to true, groups together any buckets beyond the allowed size', }), }, otherBucketLabel: { types: ['string'], - default: i18n.translate('data.search.aggs.buckets.terms.otherBucketLabel', { - defaultMessage: 'Other', - }), help: i18n.translate('data.search.aggs.buckets.terms.otherBucketLabel.help', { defaultMessage: 'Default label used in charts for documents in the Other bucket', }), @@ -148,32 +135,27 @@ export const aggTerms = (): FunctionDefinition => ({ defaultMessage: 'Advanced json to include when the agg is sent to Elasticsearch', }), }, + customLabel: { + types: ['string'], + help: i18n.translate('data.search.aggs.buckets.terms.customLabel.help', { + defaultMessage: 'Represents a custom label for this aggregation', + }), + }, }, fn: (input, args) => { const { id, enabled, schema, ...rest } = args; - let json; - try { - json = args.json ? JSON.parse(args.json) : undefined; - } catch (e) { - throw new Error('Unable to parse json argument string'); - } - - // Need to spread this object to work around TS bug: - // https://github.com/microsoft/TypeScript/issues/15300#issuecomment-436793742 - const orderAgg = args.orderAgg?.value ? { ...args.orderAgg.value } : undefined; - return { type: 'agg_type', value: { id, enabled, schema, - type: aggName, + type: BUCKET_TYPES.TERMS, params: { ...rest, - orderAgg, - json, + orderAgg: args.orderAgg?.value, + json: getParsedValue(args, 'json'), }, }, }; diff --git a/src/plugins/data/public/search/aggs/types.ts b/src/plugins/data/public/search/aggs/types.ts index 1c5b5b458ce907..8ad264f59cc27a 100644 --- a/src/plugins/data/public/search/aggs/types.ts +++ b/src/plugins/data/public/search/aggs/types.ts @@ -21,11 +21,22 @@ import { IndexPattern } from '../../index_patterns'; import { AggConfigSerialized, AggConfigs, + AggParamsRange, + AggParamsIpRange, + AggParamsDateRange, + AggParamsFilter, + AggParamsFilters, + AggParamsSignificantTerms, + AggParamsGeoTile, + AggParamsGeoHash, AggParamsTerms, + AggParamsHistogram, + AggParamsDateHistogram, AggTypesRegistrySetup, AggTypesRegistryStart, CreateAggConfigParams, getCalculateAutoTimeExpression, + BUCKET_TYPES, } from './'; export { IAggConfig, AggConfigSerialized } from './agg_config'; @@ -55,6 +66,12 @@ export interface SearchAggsStart { types: AggTypesRegistryStart; } +/** @internal */ +export interface BaseAggParams { + json?: string; + customLabel?: string; +} + /** @internal */ export interface AggExpressionType { type: 'agg_type'; @@ -74,5 +91,15 @@ export type AggExpressionFunctionArgs< * @internal */ export interface AggParamsMapping { - terms: AggParamsTerms; + [BUCKET_TYPES.RANGE]: AggParamsRange; + [BUCKET_TYPES.IP_RANGE]: AggParamsIpRange; + [BUCKET_TYPES.DATE_RANGE]: AggParamsDateRange; + [BUCKET_TYPES.FILTER]: AggParamsFilter; + [BUCKET_TYPES.FILTERS]: AggParamsFilters; + [BUCKET_TYPES.SIGNIFICANT_TERMS]: AggParamsSignificantTerms; + [BUCKET_TYPES.GEOTILE_GRID]: AggParamsGeoTile; + [BUCKET_TYPES.GEOHASH_GRID]: AggParamsGeoHash; + [BUCKET_TYPES.HISTOGRAM]: AggParamsHistogram; + [BUCKET_TYPES.DATE_HISTOGRAM]: AggParamsDateHistogram; + [BUCKET_TYPES.TERMS]: AggParamsTerms; } diff --git a/src/legacy/core_plugins/kibana/public/discover/index.ts b/src/plugins/data/public/search/aggs/utils/get_parsed_value.ts similarity index 59% rename from src/legacy/core_plugins/kibana/public/discover/index.ts rename to src/plugins/data/public/search/aggs/utils/get_parsed_value.ts index b449b70418d020..48e752369d1d3c 100644 --- a/src/legacy/core_plugins/kibana/public/discover/index.ts +++ b/src/plugins/data/public/search/aggs/utils/get_parsed_value.ts @@ -17,10 +17,18 @@ * under the License. */ -import { PluginInitializerContext } from 'kibana/public'; -import { DiscoverPlugin } from './plugin'; - -// Core will be looking for this when loading our plugin in the new platform -export const plugin = (context: PluginInitializerContext) => { - return new DiscoverPlugin(); +/** + * This method parses a JSON string and constructs the Object or object described by the string. + * If the given string is not valid JSON, you will get a syntax error. + * @param data { Object } - an object that contains the required for parsing field + * @param key { string} - name of the field to be parsed + * + * @internal + */ +export const getParsedValue = (data: any, key: string) => { + try { + return data[key] ? JSON.parse(data[key]) : undefined; + } catch (e) { + throw new Error(`Unable to parse ${key} argument string`); + } }; diff --git a/src/plugins/dev_tools/public/application.tsx b/src/plugins/dev_tools/public/application.tsx index 2c21b451cb9f70..5ec5b8036e4fb4 100644 --- a/src/plugins/dev_tools/public/application.tsx +++ b/src/plugins/dev_tools/public/application.tsx @@ -16,7 +16,6 @@ * specific language governing permissions and limitations * under the License. */ - import { I18nProvider } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { EuiTab, EuiTabs, EuiToolTip } from '@elastic/eui'; @@ -26,17 +25,17 @@ import ReactDOM from 'react-dom'; import { useEffect, useRef } from 'react'; import { AppMountContext, AppMountDeprecated } from 'kibana/public'; -import { DevTool } from './plugin'; +import { DevToolApp } from './dev_tool'; interface DevToolsWrapperProps { - devTools: readonly DevTool[]; - activeDevTool: DevTool; + devTools: readonly DevToolApp[]; + activeDevTool: DevToolApp; appMountContext: AppMountContext; updateRoute: (newRoute: string) => void; } interface MountedDevToolDescriptor { - devTool: DevTool; + devTool: DevToolApp; mountpoint: HTMLElement; unmountHandler: () => void; } @@ -64,10 +63,10 @@ function DevToolsWrapper({ {devTools.map(currentDevTool => ( { - if (!currentDevTool.disabled) { + if (!currentDevTool.isDisabled()) { updateRoute(`/dev_tools/${currentDevTool.id}`); } }} @@ -151,7 +150,7 @@ export function renderApp( element: HTMLElement, appMountContext: AppMountContext, basePath: string, - devTools: readonly DevTool[] + devTools: readonly DevToolApp[] ) { if (redirectOnMissingCapabilities(appMountContext)) { return () => {}; @@ -162,21 +161,24 @@ export function renderApp( - {devTools.map(devTool => ( - ( - - )} - /> - ))} + {devTools + // Only create routes for devtools that are not disabled + .filter(devTool => !devTool.isDisabled()) + .map(devTool => ( + ( + + )} + /> + ))} diff --git a/src/plugins/dev_tools/public/dev_tool.ts b/src/plugins/dev_tools/public/dev_tool.ts new file mode 100644 index 00000000000000..943cca286a722f --- /dev/null +++ b/src/plugins/dev_tools/public/dev_tool.ts @@ -0,0 +1,106 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { App } from 'kibana/public'; + +/** + * Descriptor for a dev tool. A dev tool works similar to an application + * registered in the core application service. + */ +export type CreateDevToolArgs = Omit & { + disabled?: boolean; +}; + +export class DevToolApp { + /** + * The id of the dev tools. This will become part of the URL path + * (`dev_tools/${devTool.id}`. It has to be unique among registered + * dev tools. + */ + public readonly id: string; + /** + * The human readable name of the dev tool. Should be internationalized. + * This will be used as a label in the tab above the actual tool. + */ + public readonly title: string; + public readonly mount: App['mount']; + + /** + * Flag indicating to disable the tab of this dev tool. Navigating to a + * disabled dev tool will be treated as the navigation to an unknown route + * (redirect to the console). + */ + private disabled: boolean; + + /** + * Optional tooltip content of the tab. + */ + public readonly tooltipContent?: string; + /** + * Flag indicating whether the dev tool will do routing within the `dev_tools/${devTool.id}/` + * prefix. If it is set to true, the dev tool is responsible to redirect + * the user when navigating to unknown URLs within the prefix. If set + * to false only the root URL of the dev tool will be recognized as valid. + */ + public readonly enableRouting: boolean; + /** + * Number used to order the tabs. + */ + public readonly order: number; + + constructor( + id: string, + title: string, + mount: App['mount'], + enableRouting: boolean, + order: number, + toolTipContent = '', + disabled = false + ) { + this.id = id; + this.title = title; + this.mount = mount; + this.enableRouting = enableRouting; + this.order = order; + this.tooltipContent = toolTipContent; + this.disabled = disabled; + } + + public enable() { + this.disabled = false; + } + + public disable() { + this.disabled = true; + } + + public isDisabled(): boolean { + return this.disabled; + } +} + +export const createDevToolApp = ({ + id, + title, + mount, + enableRouting, + order, + tooltipContent, + disabled, +}: CreateDevToolArgs) => + new DevToolApp(id, title, mount, enableRouting, order, tooltipContent, disabled); diff --git a/src/plugins/dev_tools/public/plugin.ts b/src/plugins/dev_tools/public/plugin.ts index df61271baf879a..bedf92818315ae 100644 --- a/src/plugins/dev_tools/public/plugin.ts +++ b/src/plugins/dev_tools/public/plugin.ts @@ -17,9 +17,10 @@ * under the License. */ -import { App, CoreSetup, Plugin } from 'kibana/public'; +import { CoreSetup, Plugin } from 'kibana/public'; import { sortBy } from 'lodash'; import { KibanaLegacySetup } from '../../kibana_legacy/public'; +import { CreateDevToolArgs, DevToolApp, createDevToolApp } from './dev_tool'; import './index.scss'; @@ -34,7 +35,7 @@ export interface DevToolsSetup { * to switch between the tools. * @param devTool The dev tools descriptor */ - register: (devTool: DevTool) => void; + register: (devTool: CreateDevToolArgs) => DevToolApp; } export interface DevToolsStart { @@ -46,53 +47,13 @@ export interface DevToolsStart { * becomes an implementation detail. * @deprecated */ - getSortedDevTools: () => readonly DevTool[]; -} - -/** - * Descriptor for a dev tool. A dev tool works similar to an application - * registered in the core application service. - */ -export interface DevTool { - /** - * The id of the dev tools. This will become part of the URL path - * (`dev_tools/${devTool.id}`. It has to be unique among registered - * dev tools. - */ - id: string; - /** - * The human readable name of the dev tool. Should be internationalized. - * This will be used as a label in the tab above the actual tool. - */ - title: string; - mount: App['mount']; - /** - * Flag indicating to disable the tab of this dev tool. Navigating to a - * disabled dev tool will be treated as the navigation to an unknown route - * (redirect to the console). - */ - disabled?: boolean; - /** - * Optional tooltip content of the tab. - */ - tooltipContent?: string; - /** - * Flag indicating whether the dev tool will do routing within the `dev_tools/${devTool.id}/` - * prefix. If it is set to true, the dev tool is responsible to redirect - * the user when navigating to unknown URLs within the prefix. If set - * to false only the root URL of the dev tool will be recognized as valid. - */ - enableRouting: boolean; - /** - * Number used to order the tabs. - */ - order: number; + getSortedDevTools: () => readonly DevToolApp[]; } export class DevToolsPlugin implements Plugin { - private readonly devTools = new Map(); + private readonly devTools = new Map(); - private getSortedDevTools(): readonly DevTool[] { + private getSortedDevTools(): readonly DevToolApp[] { return sortBy([...this.devTools.values()], 'order'); } @@ -115,14 +76,16 @@ export class DevToolsPlugin implements Plugin { }); return { - register: (devTool: DevTool) => { - if (this.devTools.has(devTool.id)) { + register: (devToolArgs: CreateDevToolArgs) => { + if (this.devTools.has(devToolArgs.id)) { throw new Error( - `Dev tool with id [${devTool.id}] has already been registered. Use a unique id.` + `Dev tool with id [${devToolArgs.id}] has already been registered. Use a unique id.` ); } + const devTool = createDevToolApp(devToolArgs); this.devTools.set(devTool.id, devTool); + return devTool; }, }; } diff --git a/src/plugins/discover/kibana.json b/src/plugins/discover/kibana.json index 2d41293f263698..0b3a07e98624ef 100644 --- a/src/plugins/discover/kibana.json +++ b/src/plugins/discover/kibana.json @@ -2,5 +2,16 @@ "id": "discover", "version": "kibana", "server": true, - "ui": true + "ui": true, + "requiredPlugins": [ + "charts", + "data", + "embeddable", + "inspector", + "kibanaLegacy", + "navigation", + "uiActions", + "visualizations" + ], + "optionalPlugins": ["home", "share"] } diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/_discover.scss b/src/plugins/discover/public/application/_discover.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/_discover.scss rename to src/plugins/discover/public/application/_discover.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/_hacks.scss b/src/plugins/discover/public/application/_hacks.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/_hacks.scss rename to src/plugins/discover/public/application/_hacks.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/_mixins.scss b/src/plugins/discover/public/application/_mixins.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/_mixins.scss rename to src/plugins/discover/public/application/_mixins.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/_index.scss b/src/plugins/discover/public/application/angular/_index.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/_index.scss rename to src/plugins/discover/public/application/angular/_index.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context.html b/src/plugins/discover/public/application/angular/context.html similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context.html rename to src/plugins/discover/public/application/angular/context.html diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context.js b/src/plugins/discover/public/application/angular/context.js similarity index 98% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context.js rename to src/plugins/discover/public/application/angular/context.js index 032ec7af09a301..33bbc8cb2e6cf9 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context.js +++ b/src/plugins/discover/public/application/angular/context.js @@ -32,7 +32,7 @@ const k7Breadcrumbs = $route => { return [ ...getRootBreadcrumbs(), { - text: i18n.translate('kbn.context.breadcrumb', { + text: i18n.translate('discover.context.breadcrumb', { defaultMessage: 'Context of {indexPatternTitle}#{docId}', values: { indexPatternTitle: indexPattern.title, diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/NOTES.md b/src/plugins/discover/public/application/angular/context/NOTES.md similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/NOTES.md rename to src/plugins/discover/public/application/angular/context/NOTES.md diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/_index.scss b/src/plugins/discover/public/application/angular/context/_index.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/_index.scss rename to src/plugins/discover/public/application/angular/context/_index.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/_stubs.js b/src/plugins/discover/public/application/angular/context/api/_stubs.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/_stubs.js rename to src/plugins/discover/public/application/angular/context/api/_stubs.js diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/anchor.js b/src/plugins/discover/public/application/angular/context/api/anchor.js similarity index 95% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/anchor.js rename to src/plugins/discover/public/application/angular/context/api/anchor.js index 4338d3e1dbdbd7..4df5ba989f7985 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/anchor.js +++ b/src/plugins/discover/public/application/angular/context/api/anchor.js @@ -46,7 +46,7 @@ export function fetchAnchorProvider(indexPatterns, searchSource) { if (_.get(response, ['hits', 'total'], 0) < 1) { throw new Error( - i18n.translate('kbn.context.failedToLoadAnchorDocumentErrorDescription', { + i18n.translate('discover.context.failedToLoadAnchorDocumentErrorDescription', { defaultMessage: 'Failed to load anchor document.', }) ); diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/anchor.test.js b/src/plugins/discover/public/application/angular/context/api/anchor.test.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/anchor.test.js rename to src/plugins/discover/public/application/angular/context/api/anchor.test.js diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/context.predecessors.test.js b/src/plugins/discover/public/application/angular/context/api/context.predecessors.test.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/context.predecessors.test.js rename to src/plugins/discover/public/application/angular/context/api/context.predecessors.test.js diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/context.successors.test.js b/src/plugins/discover/public/application/angular/context/api/context.successors.test.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/context.successors.test.js rename to src/plugins/discover/public/application/angular/context/api/context.successors.test.js diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/context.ts b/src/plugins/discover/public/application/angular/context/api/context.ts similarity index 97% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/context.ts rename to src/plugins/discover/public/application/angular/context/api/context.ts index 2760eec38755e2..0bca820f9a7238 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/context.ts +++ b/src/plugins/discover/public/application/angular/context/api/context.ts @@ -17,17 +17,13 @@ * under the License. */ +import { Filter, IndexPatternsContract, IndexPattern } from 'src/plugins/data/public'; import { reverseSortDir, SortDirection } from './utils/sorting'; import { extractNanos, convertIsoToMillis } from './utils/date_conversion'; import { fetchHitsInInterval } from './utils/fetch_hits_in_interval'; import { generateIntervals } from './utils/generate_intervals'; import { getEsQuerySearchAfter } from './utils/get_es_query_search_after'; import { getEsQuerySort } from './utils/get_es_query_sort'; -import { - Filter, - IndexPatternsContract, - IndexPattern, -} from '../../../../../../../../../plugins/data/public'; import { getServices } from '../../../../kibana_services'; export type SurrDocType = 'successors' | 'predecessors'; diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/__tests__/date_conversion.test.ts b/src/plugins/discover/public/application/angular/context/api/utils/date_conversion.test.ts similarity index 96% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/__tests__/date_conversion.test.ts rename to src/plugins/discover/public/application/angular/context/api/utils/date_conversion.test.ts index b9ec105cc0e7bd..223b1747182966 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/__tests__/date_conversion.test.ts +++ b/src/plugins/discover/public/application/angular/context/api/utils/date_conversion.test.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { extractNanos } from '../date_conversion'; +import { extractNanos } from './date_conversion'; describe('function extractNanos', function() { test('extract nanos of 2014-01-01', function() { diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/date_conversion.ts b/src/plugins/discover/public/application/angular/context/api/utils/date_conversion.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/date_conversion.ts rename to src/plugins/discover/public/application/angular/context/api/utils/date_conversion.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/fetch_hits_in_interval.ts b/src/plugins/discover/public/application/angular/context/api/utils/fetch_hits_in_interval.ts similarity index 95% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/fetch_hits_in_interval.ts rename to src/plugins/discover/public/application/angular/context/api/utils/fetch_hits_in_interval.ts index 8eed5d33ab004b..437898201863f2 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/fetch_hits_in_interval.ts +++ b/src/plugins/discover/public/application/angular/context/api/utils/fetch_hits_in_interval.ts @@ -16,11 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { - ISearchSource, - EsQuerySortValue, - SortDirection, -} from '../../../../../../../../../../plugins/data/public'; +import { ISearchSource, EsQuerySortValue, SortDirection } from '../../../../../../../data/public'; import { convertTimeValueToIso } from './date_conversion'; import { EsHitRecordList } from '../context'; import { IntervalValue } from './generate_intervals'; diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/generate_intervals.ts b/src/plugins/discover/public/application/angular/context/api/utils/generate_intervals.ts similarity index 95% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/generate_intervals.ts rename to src/plugins/discover/public/application/angular/context/api/utils/generate_intervals.ts index b14180d32b4f24..1497f54aa5079a 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/generate_intervals.ts +++ b/src/plugins/discover/public/application/angular/context/api/utils/generate_intervals.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { SortDirection } from '../../../../../../../../../../plugins/data/public'; +import { SortDirection } from '../../../../../../../data/public'; export type IntervalValue = number | null; diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/get_es_query_search_after.ts b/src/plugins/discover/public/application/angular/context/api/utils/get_es_query_search_after.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/get_es_query_search_after.ts rename to src/plugins/discover/public/application/angular/context/api/utils/get_es_query_search_after.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/get_es_query_sort.ts b/src/plugins/discover/public/application/angular/context/api/utils/get_es_query_sort.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/get_es_query_sort.ts rename to src/plugins/discover/public/application/angular/context/api/utils/get_es_query_sort.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/__tests__/sorting.test.ts b/src/plugins/discover/public/application/angular/context/api/utils/sorting.test.ts similarity index 94% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/__tests__/sorting.test.ts rename to src/plugins/discover/public/application/angular/context/api/utils/sorting.test.ts index eeae2aa2c5d0ae..350a0a8ede8d5f 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/__tests__/sorting.test.ts +++ b/src/plugins/discover/public/application/angular/context/api/utils/sorting.test.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { reverseSortDir, SortDirection } from '../sorting'; +import { reverseSortDir, SortDirection } from './sorting'; describe('function reverseSortDir', function() { test('reverse a given sort direction', function() { diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/sorting.ts b/src/plugins/discover/public/application/angular/context/api/utils/sorting.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/sorting.ts rename to src/plugins/discover/public/application/angular/context/api/utils/sorting.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/components/action_bar/_action_bar.scss b/src/plugins/discover/public/application/angular/context/components/action_bar/_action_bar.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/components/action_bar/_action_bar.scss rename to src/plugins/discover/public/application/angular/context/components/action_bar/_action_bar.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/components/action_bar/_index.scss b/src/plugins/discover/public/application/angular/context/components/action_bar/_index.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/components/action_bar/_index.scss rename to src/plugins/discover/public/application/angular/context/components/action_bar/_index.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/components/action_bar/action_bar.test.tsx b/src/plugins/discover/public/application/angular/context/components/action_bar/action_bar.test.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/components/action_bar/action_bar.test.tsx rename to src/plugins/discover/public/application/angular/context/components/action_bar/action_bar.test.tsx diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/components/action_bar/action_bar.tsx b/src/plugins/discover/public/application/angular/context/components/action_bar/action_bar.tsx similarity index 93% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/components/action_bar/action_bar.tsx rename to src/plugins/discover/public/application/angular/context/components/action_bar/action_bar.tsx index 8fcfcba08955c3..97a29ab21c5818 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/components/action_bar/action_bar.tsx +++ b/src/plugins/discover/public/application/angular/context/components/action_bar/action_bar.tsx @@ -111,7 +111,7 @@ export function ActionBar({ }} flush="right" > - + @@ -119,10 +119,10 @@ export function ActionBar({ {isSuccessor ? ( ) : ( )} diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/components/action_bar/action_bar_directive.ts b/src/plugins/discover/public/application/angular/context/components/action_bar/action_bar_directive.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/components/action_bar/action_bar_directive.ts rename to src/plugins/discover/public/application/angular/context/components/action_bar/action_bar_directive.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/components/action_bar/action_bar_warning.tsx b/src/plugins/discover/public/application/angular/context/components/action_bar/action_bar_warning.tsx similarity index 90% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/components/action_bar/action_bar_warning.tsx rename to src/plugins/discover/public/application/angular/context/components/action_bar/action_bar_warning.tsx index 6b922bb05a2434..db757881ad819c 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/components/action_bar/action_bar_warning.tsx +++ b/src/plugins/discover/public/application/angular/context/components/action_bar/action_bar_warning.tsx @@ -31,12 +31,12 @@ export function ActionBarWarning({ docCount, type }: { docCount: number; type: S title={ docCount === 0 ? ( ) : ( @@ -55,12 +55,12 @@ export function ActionBarWarning({ docCount, type }: { docCount: number; type: S title={ docCount === 0 ? ( ) : ( diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/components/action_bar/index.ts b/src/plugins/discover/public/application/angular/context/components/action_bar/index.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/components/action_bar/index.ts rename to src/plugins/discover/public/application/angular/context/components/action_bar/index.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/helpers/call_after_bindings_workaround.js b/src/plugins/discover/public/application/angular/context/helpers/call_after_bindings_workaround.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/helpers/call_after_bindings_workaround.js rename to src/plugins/discover/public/application/angular/context/helpers/call_after_bindings_workaround.js diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query/actions.js b/src/plugins/discover/public/application/angular/context/query/actions.js similarity index 95% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query/actions.js rename to src/plugins/discover/public/application/angular/context/query/actions.js index efc230d2cd4aef..1b1fa7138bfda7 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query/actions.js +++ b/src/plugins/discover/public/application/angular/context/query/actions.js @@ -26,7 +26,7 @@ import { fetchAnchorProvider } from '../api/anchor'; import { fetchContextProvider } from '../api/context'; import { getQueryParameterActions } from '../query_parameters'; import { FAILURE_REASONS, LOADING_STATUS } from './constants'; -import { MarkdownSimple } from '../../../../../../../../../plugins/kibana_react/public'; +import { MarkdownSimple } from '../../../../../../kibana_react/public'; export function QueryActionsProvider(Promise) { const { filterManager, indexPatterns, data } = getServices(); @@ -80,7 +80,7 @@ export function QueryActionsProvider(Promise) { error => { setFailedStatus(state)('anchor', { error }); getServices().toastNotifications.addDanger({ - title: i18n.translate('kbn.context.unableToLoadAnchorDocumentDescription', { + title: i18n.translate('discover.context.unableToLoadAnchorDocumentDescription', { defaultMessage: 'Unable to load the anchor document', }), text: {error.message}, @@ -133,7 +133,7 @@ export function QueryActionsProvider(Promise) { error => { setFailedStatus(state)(type, { error }); getServices().toastNotifications.addDanger({ - title: i18n.translate('kbn.context.unableToLoadDocumentDescription', { + title: i18n.translate('discover.context.unableToLoadDocumentDescription', { defaultMessage: 'Unable to load documents', }), text: {error.message}, diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query/constants.js b/src/plugins/discover/public/application/angular/context/query/constants.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query/constants.js rename to src/plugins/discover/public/application/angular/context/query/constants.js diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query/index.js b/src/plugins/discover/public/application/angular/context/query/index.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query/index.js rename to src/plugins/discover/public/application/angular/context/query/index.js diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query/state.js b/src/plugins/discover/public/application/angular/context/query/state.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query/state.js rename to src/plugins/discover/public/application/angular/context/query/state.js diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query_parameters/actions.js b/src/plugins/discover/public/application/angular/context/query_parameters/actions.js similarity index 96% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query_parameters/actions.js rename to src/plugins/discover/public/application/angular/context/query_parameters/actions.js index 5c1700e7763610..4f86dea08fe848 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query_parameters/actions.js +++ b/src/plugins/discover/public/application/angular/context/query_parameters/actions.js @@ -18,7 +18,7 @@ */ import _ from 'lodash'; -import { esFilters } from '../../../../../../../../../plugins/data/public'; +import { esFilters } from '../../../../../../data/public'; import { MAX_CONTEXT_SIZE, MIN_CONTEXT_SIZE, QUERY_PARAMETER_KEYS } from './constants'; diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query_parameters/actions.test.ts b/src/plugins/discover/public/application/angular/context/query_parameters/actions.test.ts similarity index 97% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query_parameters/actions.test.ts rename to src/plugins/discover/public/application/angular/context/query_parameters/actions.test.ts index 35fbd33fb4bc97..00747fcc812279 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query_parameters/actions.test.ts +++ b/src/plugins/discover/public/application/angular/context/query_parameters/actions.test.ts @@ -19,8 +19,8 @@ // @ts-ignore import { getQueryParameterActions } from './actions'; -import { FilterManager } from '../../../../../../../../../plugins/data/public'; -import { coreMock } from '../../../../../../../../../core/public/mocks'; +import { FilterManager } from '../../../../../../data/public'; +import { coreMock } from '../../../../../../../core/public/mocks'; const setupMock = coreMock.createSetup(); let state: { diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query_parameters/constants.ts b/src/plugins/discover/public/application/angular/context/query_parameters/constants.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query_parameters/constants.ts rename to src/plugins/discover/public/application/angular/context/query_parameters/constants.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query_parameters/index.js b/src/plugins/discover/public/application/angular/context/query_parameters/index.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query_parameters/index.js rename to src/plugins/discover/public/application/angular/context/query_parameters/index.js diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query_parameters/state.ts b/src/plugins/discover/public/application/angular/context/query_parameters/state.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query_parameters/state.ts rename to src/plugins/discover/public/application/angular/context/query_parameters/state.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context_app.html b/src/plugins/discover/public/application/angular/context_app.html similarity index 78% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context_app.html rename to src/plugins/discover/public/application/angular/context_app.html index 8bbb746fa45f87..1f2d6a073150b2 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context_app.html +++ b/src/plugins/discover/public/application/angular/context_app.html @@ -21,7 +21,7 @@
@@ -30,7 +30,7 @@
@@ -92,7 +92,7 @@ >
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context_app.js b/src/plugins/discover/public/application/angular/context_app.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context_app.js rename to src/plugins/discover/public/application/angular/context_app.js diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context_state.test.ts b/src/plugins/discover/public/application/angular/context_state.test.ts similarity index 97% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context_state.test.ts rename to src/plugins/discover/public/application/angular/context_state.test.ts index 1fa71ed11643a6..83bf1b1d7e3d51 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context_state.test.ts +++ b/src/plugins/discover/public/application/angular/context_state.test.ts @@ -19,8 +19,8 @@ import { getState } from './context_state'; import { createBrowserHistory, History } from 'history'; -import { FilterManager, Filter } from '../../../../../../../plugins/data/public'; -import { coreMock } from '../../../../../../../core/public/mocks'; +import { FilterManager, Filter } from '../../../../data/public'; +import { coreMock } from '../../../../../core/public/mocks'; const setupMock = coreMock.createSetup(); describe('Test Discover Context State', () => { diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context_state.ts b/src/plugins/discover/public/application/angular/context_state.ts similarity index 98% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context_state.ts rename to src/plugins/discover/public/application/angular/context_state.ts index b46995d44d826d..7a92a6ace125b3 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context_state.ts +++ b/src/plugins/discover/public/application/angular/context_state.ts @@ -23,8 +23,8 @@ import { createKbnUrlStateStorage, syncStates, BaseStateContainer, -} from '../../../../../../../plugins/kibana_utils/public'; -import { esFilters, FilterManager, Filter, Query } from '../../../../../../../plugins/data/public'; +} from '../../../../kibana_utils/public'; +import { esFilters, FilterManager, Filter, Query } from '../../../../data/public'; export interface AppState { /** diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/__snapshots__/no_results.test.js.snap b/src/plugins/discover/public/application/angular/directives/__snapshots__/no_results.test.js.snap similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/__snapshots__/no_results.test.js.snap rename to src/plugins/discover/public/application/angular/directives/__snapshots__/no_results.test.js.snap diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/_histogram.scss b/src/plugins/discover/public/application/angular/directives/_histogram.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/_histogram.scss rename to src/plugins/discover/public/application/angular/directives/_histogram.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/_index.scss b/src/plugins/discover/public/application/angular/directives/_index.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/_index.scss rename to src/plugins/discover/public/application/angular/directives/_index.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/_no_results.scss b/src/plugins/discover/public/application/angular/directives/_no_results.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/_no_results.scss rename to src/plugins/discover/public/application/angular/directives/_no_results.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/collapsible_sidebar/_collapsible_sidebar.scss b/src/plugins/discover/public/application/angular/directives/collapsible_sidebar/_collapsible_sidebar.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/collapsible_sidebar/_collapsible_sidebar.scss rename to src/plugins/discover/public/application/angular/directives/collapsible_sidebar/_collapsible_sidebar.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/collapsible_sidebar/_depth.scss b/src/plugins/discover/public/application/angular/directives/collapsible_sidebar/_depth.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/collapsible_sidebar/_depth.scss rename to src/plugins/discover/public/application/angular/directives/collapsible_sidebar/_depth.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/collapsible_sidebar/_index.scss b/src/plugins/discover/public/application/angular/directives/collapsible_sidebar/_index.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/collapsible_sidebar/_index.scss rename to src/plugins/discover/public/application/angular/directives/collapsible_sidebar/_index.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/collapsible_sidebar/collapsible_sidebar.ts b/src/plugins/discover/public/application/angular/directives/collapsible_sidebar/collapsible_sidebar.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/collapsible_sidebar/collapsible_sidebar.ts rename to src/plugins/discover/public/application/angular/directives/collapsible_sidebar/collapsible_sidebar.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/debounce/debounce.js b/src/plugins/discover/public/application/angular/directives/debounce/debounce.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/debounce/debounce.js rename to src/plugins/discover/public/application/angular/directives/debounce/debounce.js diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/debounce/__tests__/debounce.js b/src/plugins/discover/public/application/angular/directives/debounce/debounce.test.ts similarity index 73% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/debounce/__tests__/debounce.js rename to src/plugins/discover/public/application/angular/directives/debounce/debounce.test.ts index 43fa5ffbf299a2..bc08d8566d48a4 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/debounce/__tests__/debounce.js +++ b/src/plugins/discover/public/application/angular/directives/debounce/debounce.test.ts @@ -17,42 +17,56 @@ * under the License. */ -import sinon from 'sinon'; -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import { DebounceProvider } from '../index'; -import { pluginInstance } from 'plugins/kibana/discover/legacy'; - -let debounce; -let debounceFromProvider; -let $timeout; - -function init() { - pluginInstance.initializeServices(); - pluginInstance.initializeInnerAngular(); - ngMock.module('app/discover'); - - ngMock.inject(function($injector, _$timeout_, Private) { - $timeout = _$timeout_; - - debounce = $injector.get('debounce'); - debounceFromProvider = Private(DebounceProvider); - }); -} +import sinon, { SinonSpy } from 'sinon'; +import angular, { auto, ITimeoutService } from 'angular'; +import 'angular-mocks'; +import 'angular-sanitize'; +import 'angular-route'; + +// @ts-ignore +import { DebounceProvider } from './index'; +import { coreMock } from '../../../../../../../core/public/mocks'; +import { initializeInnerAngularModule } from '../../../../get_inner_angular'; +import { navigationPluginMock } from '../../../../../../navigation/public/mocks'; +import { dataPluginMock } from '../../../../../../data/public/mocks'; +import { initAngularBootstrap } from '../../../../../../kibana_legacy/public'; describe('debounce service', function() { - let spy; - beforeEach(function() { + let debounce: (fn: () => void, timeout: number, options?: any) => any; + let debounceFromProvider: (fn: () => void, timeout: number, options?: any) => any; + let $timeout: ITimeoutService; + let spy: SinonSpy; + + beforeEach(() => { spy = sinon.spy(); - init(); + + initAngularBootstrap(); + + initializeInnerAngularModule( + 'app/discover', + coreMock.createStart(), + navigationPluginMock.createStartContract(), + dataPluginMock.createStartContract() + ); + + angular.mock.module('app/discover'); + + angular.mock.inject( + ($injector: auto.IInjectorService, _$timeout_: ITimeoutService, Private: any) => { + $timeout = _$timeout_; + + debounce = $injector.get('debounce'); + debounceFromProvider = Private(DebounceProvider); + } + ); }); it('should have a cancel method', function() { const bouncer = debounce(() => {}, 100); const bouncerFromProvider = debounceFromProvider(() => {}, 100); - expect(bouncer).to.have.property('cancel'); - expect(bouncerFromProvider).to.have.property('cancel'); + expect(bouncer).toHaveProperty('cancel'); + expect(bouncerFromProvider).toHaveProperty('cancel'); }); describe('delayed execution', function() { diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/debounce/index.js b/src/plugins/discover/public/application/angular/directives/debounce/index.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/debounce/index.js rename to src/plugins/discover/public/application/angular/directives/debounce/index.js diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/fixed_scroll.js b/src/plugins/discover/public/application/angular/directives/fixed_scroll.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/fixed_scroll.js rename to src/plugins/discover/public/application/angular/directives/fixed_scroll.js diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/histogram.tsx b/src/plugins/discover/public/application/angular/directives/histogram.tsx similarity index 97% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/histogram.tsx rename to src/plugins/discover/public/application/angular/directives/histogram.tsx index 8c55622e4c6041..d856be58958f16 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/histogram.tsx +++ b/src/plugins/discover/public/application/angular/directives/histogram.tsx @@ -39,6 +39,7 @@ import { TooltipType, ElementClickListener, XYChartElementEvent, + BrushEndListener, } from '@elastic/charts'; import { i18n } from '@kbn/i18n'; @@ -143,13 +144,12 @@ export class DiscoverHistogram extends Component { - const range = { - from: min, - to: max, - }; - - this.props.timefilterUpdateHandler(range); + public onBrushEnd: BrushEndListener = ({ x }) => { + if (!x) { + return; + } + const [from, to] = x; + this.props.timefilterUpdateHandler({ from, to }); }; public onElementClick = (xInterval: number): ElementClickListener => ([elementData]) => { @@ -175,7 +175,7 @@ export class DiscoverHistogram extends Component

@@ -114,14 +114,14 @@ export class DiscoverNoResults extends Component {

@@ -155,7 +155,7 @@ export class DiscoverNoResults extends Component { @@ -168,7 +168,7 @@ export class DiscoverNoResults extends Component { @@ -181,7 +181,7 @@ export class DiscoverNoResults extends Component { @@ -194,7 +194,7 @@ export class DiscoverNoResults extends Component { @@ -210,14 +210,14 @@ export class DiscoverNoResults extends Component {

@@ -256,7 +256,7 @@ export class DiscoverNoResults extends Component { } diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/no_results.test.js b/src/plugins/discover/public/application/angular/directives/no_results.test.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/no_results.test.js rename to src/plugins/discover/public/application/angular/directives/no_results.test.js diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/render_complete.ts b/src/plugins/discover/public/application/angular/directives/render_complete.ts similarity index 92% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/render_complete.ts rename to src/plugins/discover/public/application/angular/directives/render_complete.ts index 7757deb806a18e..635cf68f12fcbb 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/render_complete.ts +++ b/src/plugins/discover/public/application/angular/directives/render_complete.ts @@ -17,7 +17,7 @@ * under the License. */ import { IScope } from 'angular'; -import { RenderCompleteHelper } from '../../../../../../../../plugins/kibana_utils/public'; +import { RenderCompleteHelper } from '../../../../../kibana_utils/public'; export function createRenderCompleteDirective() { return { diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/uninitialized.tsx b/src/plugins/discover/public/application/angular/directives/uninitialized.tsx similarity index 92% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/uninitialized.tsx rename to src/plugins/discover/public/application/angular/directives/uninitialized.tsx index b308607bbfbb07..d04aea09331159 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/uninitialized.tsx +++ b/src/plugins/discover/public/application/angular/directives/uninitialized.tsx @@ -37,7 +37,7 @@ export const DiscoverUninitialized = ({ onRefresh }: Props) => { title={

@@ -45,7 +45,7 @@ export const DiscoverUninitialized = ({ onRefresh }: Props) => { body={

@@ -53,7 +53,7 @@ export const DiscoverUninitialized = ({ onRefresh }: Props) => { actions={ diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.html b/src/plugins/discover/public/application/angular/discover.html similarity index 89% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.html rename to src/plugins/discover/public/application/angular/discover.html index 1221b01657e45f..b4db89b9275b46 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.html +++ b/src/plugins/discover/public/application/angular/discover.html @@ -61,7 +61,7 @@

{{screenTitle}}

@@ -83,7 +83,7 @@

{{screenTitle}}

@@ -93,7 +93,7 @@

{{screenTitle}}

{{(hits || 0) | number:0}} @@ -104,12 +104,12 @@

{{screenTitle}}

id="reload_saved_search" ng-click="resetQuery()" > - {{::'kbn.discover.reloadSavedSearchButton' | i18n: {defaultMessage: 'Reset search'} }} + {{::'discover.reloadSavedSearchButton' | i18n: {defaultMessage: 'Reset search'} }}
@@ -117,7 +117,7 @@

{{screenTitle}}

@@ -141,7 +141,7 @@

{{screenTitle}}

> {{screenTitle}} >

{{screenTitle}} class="dscTable__footer" > {{screenTitle}}
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js b/src/plugins/discover/public/application/angular/discover.js similarity index 93% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js rename to src/plugins/discover/public/application/angular/discover.js index c1de704d1c00a6..2afd0322f87014 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js +++ b/src/plugins/discover/public/application/angular/discover.js @@ -26,11 +26,8 @@ import dateMath from '@elastic/datemath'; import { i18n } from '@kbn/i18n'; import { getState, splitState } from './discover_state'; -import { RequestAdapter } from '../../../../../../../plugins/inspector/public'; -import { - SavedObjectSaveModal, - showSaveModal, -} from '../../../../../../../plugins/saved_objects/public'; +import { RequestAdapter } from '../../../../inspector/public'; +import { SavedObjectSaveModal, showSaveModal } from '../../../../saved_objects/public'; import { getSortArray, getSortForSearchSource } from './doc_table'; import * as columnActions from './doc_table/actions/columns'; @@ -75,9 +72,9 @@ import { syncQueryStateWithUrl, getDefaultQuery, search, -} from '../../../../../../../plugins/data/public'; +} from '../../../../data/public'; import { getIndexPatternId } from '../helpers/get_index_pattern_id'; -import { addFatalError } from '../../../../../../../plugins/kibana_legacy/public'; +import { addFatalError } from '../../../../kibana_legacy/public'; const fetchStatuses = { UNINITIALIZED: 'uninitialized', @@ -99,10 +96,10 @@ app.config($routeProvider => { } return { - text: i18n.translate('kbn.discover.badge.readOnly.text', { + text: i18n.translate('discover.badge.readOnly.text', { defaultMessage: 'Read only', }), - tooltip: i18n.translate('kbn.discover.badge.readOnly.tooltip', { + tooltip: i18n.translate('discover.badge.readOnly.tooltip', { defaultMessage: 'Unable to save searches', }), iconType: 'glasses', @@ -333,10 +330,10 @@ function discoverController( const getTopNavLinks = () => { const newSearch = { id: 'new', - label: i18n.translate('kbn.discover.localMenu.localMenu.newSearchTitle', { + label: i18n.translate('discover.localMenu.localMenu.newSearchTitle', { defaultMessage: 'New', }), - description: i18n.translate('kbn.discover.localMenu.newSearchDescription', { + description: i18n.translate('discover.localMenu.newSearchDescription', { defaultMessage: 'New Search', }), run: function() { @@ -349,10 +346,10 @@ function discoverController( const saveSearch = { id: 'save', - label: i18n.translate('kbn.discover.localMenu.saveTitle', { + label: i18n.translate('discover.localMenu.saveTitle', { defaultMessage: 'Save', }), - description: i18n.translate('kbn.discover.localMenu.saveSearchDescription', { + description: i18n.translate('discover.localMenu.saveSearchDescription', { defaultMessage: 'Save Search', }), testId: 'discoverSaveButton', @@ -389,7 +386,7 @@ function discoverController( title={savedSearch.title} showCopyOnSave={!!savedSearch.id} objectType="search" - description={i18n.translate('kbn.discover.localMenu.saveSaveSearchDescription', { + description={i18n.translate('discover.localMenu.saveSaveSearchDescription', { defaultMessage: 'Save your Discover search so you can use it in visualizations and dashboards', })} @@ -402,10 +399,10 @@ function discoverController( const openSearch = { id: 'open', - label: i18n.translate('kbn.discover.localMenu.openTitle', { + label: i18n.translate('discover.localMenu.openTitle', { defaultMessage: 'Open', }), - description: i18n.translate('kbn.discover.localMenu.openSavedSearchDescription', { + description: i18n.translate('discover.localMenu.openSavedSearchDescription', { defaultMessage: 'Open Saved Search', }), testId: 'discoverOpenButton', @@ -419,10 +416,10 @@ function discoverController( const shareSearch = { id: 'share', - label: i18n.translate('kbn.discover.localMenu.shareTitle', { + label: i18n.translate('discover.localMenu.shareTitle', { defaultMessage: 'Share', }), - description: i18n.translate('kbn.discover.localMenu.shareSearchDescription', { + description: i18n.translate('discover.localMenu.shareSearchDescription', { defaultMessage: 'Share Search', }), testId: 'shareTopNavButton', @@ -446,10 +443,10 @@ function discoverController( const inspectSearch = { id: 'inspect', - label: i18n.translate('kbn.discover.localMenu.inspectTitle', { + label: i18n.translate('discover.localMenu.inspectTitle', { defaultMessage: 'Inspect', }), - description: i18n.translate('kbn.discover.localMenu.openInspectorForSearchDescription', { + description: i18n.translate('discover.localMenu.openInspectorForSearchDescription', { defaultMessage: 'Open Inspector for search', }), testId: 'openInspectorButton', @@ -492,7 +489,7 @@ function discoverController( const pageTitleSuffix = savedSearch.id && savedSearch.title ? `: ${savedSearch.title}` : ''; chrome.docTitle.change(`Discover${pageTitleSuffix}`); - const discoverBreadcrumbsTitle = i18n.translate('kbn.discover.discoverBreadcrumbTitle', { + const discoverBreadcrumbsTitle = i18n.translate('discover.discoverBreadcrumbTitle', { defaultMessage: 'Discover', }); @@ -606,16 +603,16 @@ function discoverController( $scope.state.sort = getSortArray($scope.state.sort, $scope.indexPattern); $scope.getBucketIntervalToolTipText = () => { - return i18n.translate('kbn.discover.bucketIntervalTooltip', { + return i18n.translate('discover.bucketIntervalTooltip', { defaultMessage: 'This interval creates {bucketsDescription} to show in the selected time range, so it has been scaled to {bucketIntervalDescription}', values: { bucketsDescription: $scope.bucketInterval.scale > 1 - ? i18n.translate('kbn.discover.bucketIntervalTooltip.tooLargeBucketsText', { + ? i18n.translate('discover.bucketIntervalTooltip.tooLargeBucketsText', { defaultMessage: 'buckets that are too large', }) - : i18n.translate('kbn.discover.bucketIntervalTooltip.tooManyBucketsText', { + : i18n.translate('discover.bucketIntervalTooltip.tooManyBucketsText', { defaultMessage: 'too many buckets', }), bucketIntervalDescription: $scope.bucketInterval.description, @@ -748,7 +745,7 @@ function discoverController( $scope.$evalAsync(() => { if (id) { toastNotifications.addSuccess({ - title: i18n.translate('kbn.discover.notifications.savedSearchTitle', { + title: i18n.translate('discover.notifications.savedSearchTitle', { defaultMessage: `Search '{savedSearchTitle}' was saved`, values: { savedSearchTitle: savedSearch.title, @@ -769,7 +766,7 @@ function discoverController( return { id }; } catch (saveError) { toastNotifications.addDanger({ - title: i18n.translate('kbn.discover.notifications.notSavedSearchTitle', { + title: i18n.translate('discover.notifications.notSavedSearchTitle', { defaultMessage: `Search '{savedSearchTitle}' was not saved.`, values: { savedSearchTitle: savedSearch.title, @@ -812,7 +809,7 @@ function discoverController( $scope.fetchError = fetchError; } else { toastNotifications.addError(error, { - title: i18n.translate('kbn.discover.errorLoadingData', { + title: i18n.translate('discover.errorLoadingData', { defaultMessage: 'Error loading data', }), toastMessage: error.shortMessage || error.body?.message, @@ -903,10 +900,10 @@ function discoverController( function logInspectorRequest() { inspectorAdapters.requests.reset(); - const title = i18n.translate('kbn.discover.inspectorRequestDataTitle', { + const title = i18n.translate('discover.inspectorRequestDataTitle', { defaultMessage: 'data', }); - const description = i18n.translate('kbn.discover.inspectorRequestDescription', { + const description = i18n.translate('discover.inspectorRequestDescription', { defaultMessage: 'This request queries Elasticsearch to fetch the data for the search.', }); inspectorRequest = inspectorAdapters.requests.start(title, { description }); @@ -1049,7 +1046,7 @@ function discoverController( } function getIndexPatternWarning(index) { - return i18n.translate('kbn.discover.valueIsNotConfiguredIndexPatternIDWarningTitle', { + return i18n.translate('discover.valueIsNotConfiguredIndexPatternIDWarningTitle', { defaultMessage: '{stateVal} is not a configured index pattern ID', values: { stateVal: `"${index}"`, @@ -1076,7 +1073,7 @@ function discoverController( if (ownIndexPattern) { toastNotifications.addWarning({ title: warningTitle, - text: i18n.translate('kbn.discover.showingSavedIndexPatternWarningDescription', { + text: i18n.translate('discover.showingSavedIndexPatternWarningDescription', { defaultMessage: 'Showing the saved index pattern: "{ownIndexPatternTitle}" ({ownIndexPatternId})', values: { @@ -1090,7 +1087,7 @@ function discoverController( toastNotifications.addWarning({ title: warningTitle, - text: i18n.translate('kbn.discover.showingDefaultIndexPatternWarningDescription', { + text: i18n.translate('discover.showingDefaultIndexPatternWarningDescription', { defaultMessage: 'Showing the default index pattern: "{loadedIndexPatternTitle}" ({loadedIndexPatternId})', values: { diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover_state.test.ts b/src/plugins/discover/public/application/angular/discover_state.test.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover_state.test.ts rename to src/plugins/discover/public/application/angular/discover_state.test.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover_state.ts b/src/plugins/discover/public/application/angular/discover_state.ts similarity index 96% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover_state.ts rename to src/plugins/discover/public/application/angular/discover_state.ts index 2a036f0ac60ad3..46500d9fdf85e2 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover_state.ts +++ b/src/plugins/discover/public/application/angular/discover_state.ts @@ -24,9 +24,9 @@ import { syncState, ReduxLikeStateContainer, IKbnUrlStateStorage, -} from '../../../../../../../plugins/kibana_utils/public'; -import { esFilters, Filter, Query } from '../../../../../../../plugins/data/public'; -import { migrateLegacyQuery } from '../../../../../../../plugins/kibana_legacy/public'; +} from '../../../../kibana_utils/public'; +import { esFilters, Filter, Query } from '../../../../data/public'; +import { migrateLegacyQuery } from '../../../../kibana_legacy/public'; export interface AppState { /** diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc.html b/src/plugins/discover/public/application/angular/doc.html similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc.html rename to src/plugins/discover/public/application/angular/doc.html diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc.ts b/src/plugins/discover/public/application/angular/doc.ts similarity index 91% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc.ts rename to src/plugins/discover/public/application/angular/doc.ts index 092e3c79b1007c..2d0d45e5003fbd 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc.ts +++ b/src/plugins/discover/public/application/angular/doc.ts @@ -49,7 +49,9 @@ app.config(($routeProvider: any) => { }) // the new route, es 7 deprecated types, es 8 removed them .when('/discover/doc/:indexPattern/:index', { - controller: ($scope: LazyScope, $route: any, es: any) => { + // have to be written as function expression, because it's not compiled in dev mode + // eslint-disable-next-line object-shorthand + controller: function($scope: LazyScope, $route: any, es: any) { timefilter.disableAutoRefreshSelector(); timefilter.disableTimeRangeSelector(); $scope.esClient = es; diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/_doc_table.scss b/src/plugins/discover/public/application/angular/doc_table/_doc_table.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/_doc_table.scss rename to src/plugins/discover/public/application/angular/doc_table/_doc_table.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/_index.scss b/src/plugins/discover/public/application/angular/doc_table/_index.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/_index.scss rename to src/plugins/discover/public/application/angular/doc_table/_index.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/actions/columns.ts b/src/plugins/discover/public/application/angular/doc_table/actions/columns.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/actions/columns.ts rename to src/plugins/discover/public/application/angular/doc_table/actions/columns.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/_index.scss b/src/plugins/discover/public/application/angular/doc_table/components/_index.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/_index.scss rename to src/plugins/discover/public/application/angular/doc_table/components/_index.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/_table_header.scss b/src/plugins/discover/public/application/angular/doc_table/components/_table_header.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/_table_header.scss rename to src/plugins/discover/public/application/angular/doc_table/components/_table_header.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/pager/__snapshots__/tool_bar_pager_buttons.test.tsx.snap b/src/plugins/discover/public/application/angular/doc_table/components/pager/__snapshots__/tool_bar_pager_buttons.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/pager/__snapshots__/tool_bar_pager_buttons.test.tsx.snap rename to src/plugins/discover/public/application/angular/doc_table/components/pager/__snapshots__/tool_bar_pager_buttons.test.tsx.snap diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/pager/__snapshots__/tool_bar_pager_text.test.tsx.snap b/src/plugins/discover/public/application/angular/doc_table/components/pager/__snapshots__/tool_bar_pager_text.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/pager/__snapshots__/tool_bar_pager_text.test.tsx.snap rename to src/plugins/discover/public/application/angular/doc_table/components/pager/__snapshots__/tool_bar_pager_text.test.tsx.snap diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/pager/index.ts b/src/plugins/discover/public/application/angular/doc_table/components/pager/index.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/pager/index.ts rename to src/plugins/discover/public/application/angular/doc_table/components/pager/index.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/pager/tool_bar_pager_buttons.test.tsx b/src/plugins/discover/public/application/angular/doc_table/components/pager/tool_bar_pager_buttons.test.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/pager/tool_bar_pager_buttons.test.tsx rename to src/plugins/discover/public/application/angular/doc_table/components/pager/tool_bar_pager_buttons.test.tsx diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/pager/tool_bar_pager_buttons.tsx b/src/plugins/discover/public/application/angular/doc_table/components/pager/tool_bar_pager_buttons.tsx similarity index 92% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/pager/tool_bar_pager_buttons.tsx rename to src/plugins/discover/public/application/angular/doc_table/components/pager/tool_bar_pager_buttons.tsx index 6f1cf81e2c541b..e95153d85b0641 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/pager/tool_bar_pager_buttons.tsx +++ b/src/plugins/discover/public/application/angular/doc_table/components/pager/tool_bar_pager_buttons.tsx @@ -35,7 +35,7 @@ export function ToolBarPagerButtons(props: Props) { disabled={!props.hasPreviousPage} data-test-subj="btnPrevPage" aria-label={i18n.translate( - 'kbn.discover.docTable.pager.toolbarPagerButtons.previousButtonAriaLabel', + 'discover.docTable.pager.toolbarPagerButtons.previousButtonAriaLabel', { defaultMessage: 'Previous page in table', } @@ -49,7 +49,7 @@ export function ToolBarPagerButtons(props: Props) { disabled={!props.hasNextPage} data-test-subj="btnNextPage" aria-label={i18n.translate( - 'kbn.discover.docTable.pager.toolbarPagerButtons.nextButtonAriaLabel', + 'discover.docTable.pager.toolbarPagerButtons.nextButtonAriaLabel', { defaultMessage: 'Next page in table', } diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/pager/tool_bar_pager_text.test.tsx b/src/plugins/discover/public/application/angular/doc_table/components/pager/tool_bar_pager_text.test.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/pager/tool_bar_pager_text.test.tsx rename to src/plugins/discover/public/application/angular/doc_table/components/pager/tool_bar_pager_text.test.tsx diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/pager/tool_bar_pager_text.tsx b/src/plugins/discover/public/application/angular/doc_table/components/pager/tool_bar_pager_text.tsx similarity index 95% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/pager/tool_bar_pager_text.tsx rename to src/plugins/discover/public/application/angular/doc_table/components/pager/tool_bar_pager_text.tsx index 84338d817c86b5..46e3cd9511eb59 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/pager/tool_bar_pager_text.tsx +++ b/src/plugins/discover/public/application/angular/doc_table/components/pager/tool_bar_pager_text.tsx @@ -30,7 +30,7 @@ export function ToolBarPagerText({ startItem, endItem, totalItems }: Props) {
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_header.ts b/src/plugins/discover/public/application/angular/doc_table/components/table_header.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_header.ts rename to src/plugins/discover/public/application/angular/doc_table/components/table_header.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_header/__snapshots__/table_header.test.tsx.snap b/src/plugins/discover/public/application/angular/doc_table/components/table_header/__snapshots__/table_header.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_header/__snapshots__/table_header.test.tsx.snap rename to src/plugins/discover/public/application/angular/doc_table/components/table_header/__snapshots__/table_header.test.tsx.snap diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_header/helpers.tsx b/src/plugins/discover/public/application/angular/doc_table/components/table_header/helpers.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_header/helpers.tsx rename to src/plugins/discover/public/application/angular/doc_table/components/table_header/helpers.tsx diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_header/table_header.test.tsx b/src/plugins/discover/public/application/angular/doc_table/components/table_header/table_header.test.tsx similarity index 99% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_header/table_header.test.tsx rename to src/plugins/discover/public/application/angular/doc_table/components/table_header/table_header.test.tsx index 89f73022627c50..b201bea26503ea 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_header/table_header.test.tsx +++ b/src/plugins/discover/public/application/angular/doc_table/components/table_header/table_header.test.tsx @@ -25,8 +25,6 @@ import { findTestSubject } from '@elastic/eui/lib/test'; import { SortOrder } from './helpers'; import { IndexPattern, IFieldType } from '../../../../../kibana_services'; -jest.mock('ui/new_platform'); - function getMockIndexPattern() { return ({ id: 'test', diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_header/table_header.tsx b/src/plugins/discover/public/application/angular/doc_table/components/table_header/table_header.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_header/table_header.tsx rename to src/plugins/discover/public/application/angular/doc_table/components/table_header/table_header.tsx diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_header/table_header_column.tsx b/src/plugins/discover/public/application/angular/doc_table/components/table_header/table_header_column.tsx similarity index 88% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_header/table_header_column.tsx rename to src/plugins/discover/public/application/angular/doc_table/components/table_header/table_header_column.tsx index d1a9a5146fb8a5..4c09ff8701f30a 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_header/table_header_column.tsx +++ b/src/plugins/discover/public/application/angular/doc_table/components/table_header/table_header_column.tsx @@ -80,21 +80,21 @@ export function TableHeaderColumn({ const getSortButtonAriaLabel = () => { const sortAscendingMessage = i18n.translate( - 'kbn.docTable.tableHeader.sortByColumnAscendingAriaLabel', + 'discover.docTable.tableHeader.sortByColumnAscendingAriaLabel', { defaultMessage: 'Sort {columnName} ascending', values: { columnName: name }, } ); const sortDescendingMessage = i18n.translate( - 'kbn.docTable.tableHeader.sortByColumnDescendingAriaLabel', + 'discover.docTable.tableHeader.sortByColumnDescendingAriaLabel', { defaultMessage: 'Sort {columnName} descending', values: { columnName: name }, } ); const stopSortingMessage = i18n.translate( - 'kbn.docTable.tableHeader.sortByColumnUnsortedAriaLabel', + 'discover.docTable.tableHeader.sortByColumnUnsortedAriaLabel', { defaultMessage: 'Stop sorting on {columnName}', values: { columnName: name }, @@ -126,42 +126,42 @@ export function TableHeaderColumn({ // Remove Button { active: isRemoveable && typeof onRemoveColumn === 'function', - ariaLabel: i18n.translate('kbn.docTable.tableHeader.removeColumnButtonAriaLabel', { + ariaLabel: i18n.translate('discover.docTable.tableHeader.removeColumnButtonAriaLabel', { defaultMessage: 'Remove {columnName} column', values: { columnName: name }, }), className: 'fa fa-remove kbnDocTableHeader__move', onClick: () => onRemoveColumn && onRemoveColumn(name), testSubject: `docTableRemoveHeader-${name}`, - tooltip: i18n.translate('kbn.docTable.tableHeader.removeColumnButtonTooltip', { + tooltip: i18n.translate('discover.docTable.tableHeader.removeColumnButtonTooltip', { defaultMessage: 'Remove Column', }), }, // Move Left Button { active: colLeftIdx >= 0 && typeof onMoveColumn === 'function', - ariaLabel: i18n.translate('kbn.docTable.tableHeader.moveColumnLeftButtonAriaLabel', { + ariaLabel: i18n.translate('discover.docTable.tableHeader.moveColumnLeftButtonAriaLabel', { defaultMessage: 'Move {columnName} column to the left', values: { columnName: name }, }), className: 'fa fa-angle-double-left kbnDocTableHeader__move', onClick: () => onMoveColumn && onMoveColumn(name, colLeftIdx), testSubject: `docTableMoveLeftHeader-${name}`, - tooltip: i18n.translate('kbn.docTable.tableHeader.moveColumnLeftButtonTooltip', { + tooltip: i18n.translate('discover.docTable.tableHeader.moveColumnLeftButtonTooltip', { defaultMessage: 'Move column to the left', }), }, // Move Right Button { active: colRightIdx >= 0 && typeof onMoveColumn === 'function', - ariaLabel: i18n.translate('kbn.docTable.tableHeader.moveColumnRightButtonAriaLabel', { + ariaLabel: i18n.translate('discover.docTable.tableHeader.moveColumnRightButtonAriaLabel', { defaultMessage: 'Move {columnName} column to the right', values: { columnName: name }, }), className: 'fa fa-angle-double-right kbnDocTableHeader__move', onClick: () => onMoveColumn && onMoveColumn(name, colRightIdx), testSubject: `docTableMoveRightHeader-${name}`, - tooltip: i18n.translate('kbn.docTable.tableHeader.moveColumnRightButtonTooltip', { + tooltip: i18n.translate('discover.docTable.tableHeader.moveColumnRightButtonTooltip', { defaultMessage: 'Move column to the right', }), }, diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row.ts b/src/plugins/discover/public/application/angular/doc_table/components/table_row.ts similarity index 96% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row.ts rename to src/plugins/discover/public/application/angular/doc_table/components/table_row.ts index 698bfe7416d420..3b48c4c79365e2 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row.ts +++ b/src/plugins/discover/public/application/angular/doc_table/components/table_row.ts @@ -23,15 +23,15 @@ import $ from 'jquery'; import rison from 'rison-node'; import '../../doc_viewer'; // @ts-ignore -import { noWhiteSpace } from '../../../../../../common/utils/no_white_space'; +import { noWhiteSpace } from '../../../../../../../legacy/core_plugins/kibana/common/utils/no_white_space'; import openRowHtml from './table_row/open.html'; import detailsHtml from './table_row/details.html'; -import { dispatchRenderComplete } from '../../../../../../../../../plugins/kibana_utils/public'; +import { dispatchRenderComplete } from '../../../../../../kibana_utils/public'; import cellTemplateHtml from '../components/table_row/cell.html'; import truncateByHeightTemplateHtml from '../components/table_row/truncate_by_height.html'; -import { esFilters } from '../../../../../../../../../plugins/data/public'; +import { esFilters } from '../../../../../../data/public'; import { getServices } from '../../../../kibana_services'; // guesstimate at the minimum number of chars wide cells in the table should be diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row/_cell.scss b/src/plugins/discover/public/application/angular/doc_table/components/table_row/_cell.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row/_cell.scss rename to src/plugins/discover/public/application/angular/doc_table/components/table_row/_cell.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row/_details.scss b/src/plugins/discover/public/application/angular/doc_table/components/table_row/_details.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row/_details.scss rename to src/plugins/discover/public/application/angular/doc_table/components/table_row/_details.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row/_index.scss b/src/plugins/discover/public/application/angular/doc_table/components/table_row/_index.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row/_index.scss rename to src/plugins/discover/public/application/angular/doc_table/components/table_row/_index.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row/_open.scss b/src/plugins/discover/public/application/angular/doc_table/components/table_row/_open.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row/_open.scss rename to src/plugins/discover/public/application/angular/doc_table/components/table_row/_open.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row/cell.html b/src/plugins/discover/public/application/angular/doc_table/components/table_row/cell.html similarity index 66% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row/cell.html rename to src/plugins/discover/public/application/angular/doc_table/components/table_row/cell.html index 0704016a52bbdd..e8c4fceeca7ffd 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row/cell.html +++ b/src/plugins/discover/public/application/angular/doc_table/components/table_row/cell.html @@ -18,17 +18,17 @@ data-column="<%- column %>" tooltip-append-to-body="1" data-test-subj="docTableCellFilter" - tooltip="{{ ::'kbn.docTable.tableRow.filterForValueButtonTooltip' | i18n: {defaultMessage: 'Filter for value'} }}" + tooltip="{{ ::'discover.docTable.tableRow.filterForValueButtonTooltip' | i18n: {defaultMessage: 'Filter for value'} }}" tooltip-placement="bottom" - aria-label="{{ ::'kbn.docTable.tableRow.filterForValueButtonAriaLabel' | i18n: {defaultMessage: 'Filter for value'} }}" + aria-label="{{ ::'discover.docTable.tableRow.filterForValueButtonAriaLabel' | i18n: {defaultMessage: 'Filter for value'} }}" > diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row/details.html b/src/plugins/discover/public/application/angular/doc_table/components/table_row/details.html similarity index 89% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row/details.html rename to src/plugins/discover/public/application/angular/doc_table/components/table_row/details.html index d149a9023816a8..37ae08246d1d3f 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row/details.html +++ b/src/plugins/discover/public/application/angular/doc_table/components/table_row/details.html @@ -8,7 +8,7 @@

@@ -22,7 +22,7 @@ data-test-subj="docTableRowAction" ng-href="{{ getContextAppHref() }}" ng-if="indexPattern.isTimeBased()" - i18n-id="kbn.docTable.tableRow.viewSurroundingDocumentsLinkText" + i18n-id="discover.docTable.tableRow.viewSurroundingDocumentsLinkText" i18n-default-message="View surrounding documents" >
@@ -31,7 +31,7 @@ class="euiLink" data-test-subj="docTableRowAction" ng-href="#/discover/doc/{{indexPattern.id}}/{{row._index}}?id={{uriEncodedId}}" - i18n-id="kbn.docTable.tableRow.viewSingleDocumentLinkText" + i18n-id="discover.docTable.tableRow.viewSingleDocumentLinkText" i18n-default-message="View single document" >
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row/open.html b/src/plugins/discover/public/application/angular/doc_table/components/table_row/open.html similarity index 72% rename from src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row/open.html rename to src/plugins/discover/public/application/angular/doc_table/components/table_row/open.html index d6c4b858d2b476..6a14b6fc703489 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_row/open.html +++ b/src/plugins/discover/public/application/angular/doc_table/components/table_row/open.html @@ -2,7 +2,7 @@ +
+
+`; + +exports[`DownNoExpressionSelect component should shallow renders against props 1`] = ` + + } + data-test-subj="xpack.uptime.alerts.monitorStatus.numTimesExpression" + description="matching monitors are down >" + id="ping-count" + value="5 times" +/> +`; diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/__tests__/__snapshots__/time_expression_select.test.tsx.snap b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/__tests__/__snapshots__/time_expression_select.test.tsx.snap new file mode 100644 index 00000000000000..cbbaccbab34e4e --- /dev/null +++ b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/__tests__/__snapshots__/time_expression_select.test.tsx.snap @@ -0,0 +1,160 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`TimeExpressionSelect component should renders against props 1`] = ` +
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+`; + +exports[`TimeExpressionSelect component should shallow renders against props 1`] = ` + + + + } + data-test-subj="xpack.uptime.alerts.monitorStatus.timerangeValueExpression" + description="within" + id="timerange" + value="last 15" + /> + + + + +
+ +
+
+ + [Function] + + + } + data-test-subj="xpack.uptime.alerts.monitorStatus.timerangeUnitExpression" + description="" + id="timerange-unit" + value="minutes" + /> +
+
+`; diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/__tests__/down_number_select.test.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/__tests__/down_number_select.test.tsx new file mode 100644 index 00000000000000..13db9f2f809094 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/__tests__/down_number_select.test.tsx @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { renderWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; +import { DownNoExpressionSelect } from '../down_number_select'; + +describe('DownNoExpressionSelect component', () => { + const filters = + '"{"bool":{"filter":[{"bool":{"should":[{"match":{"observer.geo.name":"US-West"}}],"minimum_should_match":1}},' + + '{"bool":{"should":[{"match":{"url.port":443}}],"minimum_should_match":1}}]}}"'; + + it('should shallow renders against props', function() { + const component = shallowWithIntl( + + ); + expect(component).toMatchSnapshot(); + }); + + it('should renders against props', function() { + const component = renderWithIntl( + + ); + expect(component).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/__tests__/time_expression_select.test.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/__tests__/time_expression_select.test.tsx new file mode 100644 index 00000000000000..37df7dfdaaec1c --- /dev/null +++ b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/__tests__/time_expression_select.test.tsx @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { renderWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; +import { TimeExpressionSelect } from '../time_expression_select'; + +describe('TimeExpressionSelect component', () => { + it('should shallow renders against props', function() { + const component = shallowWithIntl(); + expect(component).toMatchSnapshot(); + }); + + it('should renders against props', function() { + const component = renderWithIntl(); + expect(component).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/down_number_select.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/down_number_select.tsx new file mode 100644 index 00000000000000..7f68aef8e179c4 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/down_number_select.tsx @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useEffect, useState } from 'react'; +import { AlertExpressionPopover } from '../alert_expression_popover'; +import * as labels from '../translations'; +import { AlertFieldNumber } from '../alert_field_number'; + +interface Props { + setAlertParams: (key: string, value: any) => void; + filters: string; +} + +export const DownNoExpressionSelect: React.FC = ({ filters, setAlertParams }) => { + const [numTimes, setNumTimes] = useState(5); + + useEffect(() => { + setAlertParams('numTimes', numTimes); + }, [numTimes, setAlertParams]); + + return ( + + } + data-test-subj="xpack.uptime.alerts.monitorStatus.numTimesExpression" + description={filters ? labels.MATCHING_MONITORS_DOWN : labels.ANY_MONITOR_DOWN} + id="ping-count" + value={`${numTimes} times`} + /> + ); +}; diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/filters_expression_select.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/filters_expression_select.tsx new file mode 100644 index 00000000000000..8298f202b94584 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/filters_expression_select.tsx @@ -0,0 +1,163 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useEffect, useState } from 'react'; +import { useSelector } from 'react-redux'; +import { EuiButtonIcon, EuiExpression, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; +import { FilterPopover } from '../../filter_group/filter_popover'; +import { overviewFiltersSelector } from '../../../../state/selectors'; +import { useFilterUpdate } from '../../../../hooks/use_filter_update'; +import { filterLabels } from '../../filter_group/translations'; +import { alertFilterLabels } from './translations'; + +interface Props { + newFilters: string[]; + onRemoveFilter: (val: string) => void; + setAlertParams: (key: string, value: any) => void; +} + +export const FiltersExpressionsSelect: React.FC = ({ + setAlertParams, + newFilters, + onRemoveFilter, +}) => { + const { tags, ports, schemes, locations } = useSelector(overviewFiltersSelector); + + const [updatedFieldValues, setUpdatedFieldValues] = useState<{ + fieldName: string; + values: string[]; + }>({ fieldName: '', values: [] }); + + const currentFilters = useFilterUpdate(updatedFieldValues.fieldName, updatedFieldValues.values); + + useEffect(() => { + if (updatedFieldValues.fieldName === 'observer.geo.name') { + setAlertParams('locations', updatedFieldValues.values); + } + }, [setAlertParams, updatedFieldValues]); + + useEffect(() => { + setAlertParams('locations', []); + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const selectedTags = currentFilters.get('tags'); + const selectedPorts = currentFilters.get('url.port'); + const selectedScheme = currentFilters.get('monitor.type'); + const selectedLocation = currentFilters.get('observer.geo.name'); + + const getSelectedItems = (fieldName: string) => currentFilters.get(fieldName) || []; + + const onFilterFieldChange = (fieldName: string, values: string[]) => { + setUpdatedFieldValues({ fieldName, values }); + }; + + const monitorFilters = [ + { + onFilterFieldChange, + loading: false, + fieldName: 'url.port', + id: 'filter_port', + disabled: ports?.length === 0, + items: ports?.map((p: number) => p.toString()) ?? [], + selectedItems: getSelectedItems('url.port'), + title: filterLabels.PORT, + description: selectedPorts ? alertFilterLabels.USING_PORT : alertFilterLabels.USING, + value: selectedPorts?.join(',') ?? alertFilterLabels.ANY_PORT, + }, + { + onFilterFieldChange, + loading: false, + fieldName: 'tags', + id: 'filter_tags', + disabled: tags?.length === 0, + items: tags ?? [], + selectedItems: getSelectedItems('tags'), + title: filterLabels.TAGS, + description: selectedTags ? alertFilterLabels.WITH_TAG : alertFilterLabels.WITH, + value: selectedTags?.join(',') ?? alertFilterLabels.ANY_TAG, + }, + { + onFilterFieldChange, + loading: false, + fieldName: 'monitor.type', + id: 'filter_scheme', + disabled: schemes?.length === 0, + items: schemes ?? [], + selectedItems: getSelectedItems('monitor.type'), + title: filterLabels.SCHEME, + description: selectedScheme ? alertFilterLabels.OF_TYPE : alertFilterLabels.OF, + value: selectedScheme?.join(',') ?? alertFilterLabels.ANY_TYPE, + }, + { + onFilterFieldChange, + loading: false, + fieldName: 'observer.geo.name', + id: 'filter_location', + disabled: locations?.length === 0, + items: locations ?? [], + selectedItems: getSelectedItems('observer.geo.name'), + title: filterLabels.SCHEME, + description: selectedLocation ? alertFilterLabels.FROM_LOCATION : alertFilterLabels.FROM, + value: selectedLocation?.join(',') ?? alertFilterLabels.ANY_LOCATION, + }, + ]; + + const [isOpen, setIsOpen] = useState({ + filter_port: false, + filter_tags: false, + filter_scheme: false, + filter_location: false, + }); + + const filtersToDisplay = monitorFilters.filter( + curr => curr.selectedItems.length > 0 || newFilters?.includes(curr.fieldName) + ); + + return ( + <> + {filtersToDisplay.map(({ description, value, ...item }) => ( + + + setIsOpen({ ...isOpen, [item.id]: !isOpen[item.id] })} + /> + } + forceOpen={isOpen[item.id]} + setForceOpen={() => { + setIsOpen({ ...isOpen, [item.id]: !isOpen[item.id] }); + }} + /> + + + { + onRemoveFilter(item.fieldName); + onFilterFieldChange(item.fieldName, []); + }} + /> + + + + + ))} + + + + ); +}; diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/index.ts b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/index.ts new file mode 100644 index 00000000000000..acc19dfbc8f8b5 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { DownNoExpressionSelect } from './down_number_select'; +export { FiltersExpressionsSelect } from './filters_expression_select'; +export { TimeExpressionSelect } from './time_expression_select'; diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/time_expression_select.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/time_expression_select.tsx new file mode 100644 index 00000000000000..8cab4315967d36 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/time_expression_select.tsx @@ -0,0 +1,118 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useEffect, useState } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiFlexGroup, EuiFlexItem, EuiSelectable, EuiTitle } from '@elastic/eui'; +import { AlertExpressionPopover } from '../alert_expression_popover'; +import * as labels from '../translations'; +import { AlertFieldNumber } from '../alert_field_number'; +import { timeExpLabels } from './translations'; + +interface Props { + setAlertParams: (key: string, value: any) => void; +} + +const TimeRangeOptions = [ + { + 'aria-label': labels.SECONDS_TIME_RANGE, + 'data-test-subj': 'xpack.uptime.alerts.monitorStatus.timerangeUnitSelectable.secondsOption', + key: 's', + label: labels.SECONDS, + }, + { + 'aria-label': labels.MINUTES_TIME_RANGE, + 'data-test-subj': 'xpack.uptime.alerts.monitorStatus.timerangeUnitSelectable.minutesOption', + checked: 'on', + key: 'm', + label: labels.MINUTES, + }, + { + 'aria-label': labels.HOURS_TIME_RANGE, + 'data-test-subj': 'xpack.uptime.alerts.monitorStatus.timerangeUnitSelectable.hoursOption', + key: 'h', + label: labels.HOURS, + }, + { + 'aria-label': labels.DAYS_TIME_RANGE, + 'data-test-subj': 'xpack.uptime.alerts.monitorStatus.timerangeUnitSelectable.daysOption', + key: 'd', + label: labels.DAYS, + }, +]; + +export const TimeExpressionSelect: React.FC = ({ setAlertParams }) => { + const [numUnits, setNumUnits] = useState(15); + + const [timerangeUnitOptions, setTimerangeUnitOptions] = useState(TimeRangeOptions); + + useEffect(() => { + const timerangeUnit = timerangeUnitOptions.find(({ checked }) => checked === 'on')?.key ?? 'm'; + setAlertParams('timerange', { from: `now-${numUnits}${timerangeUnit}`, to: 'now' }); + }, [numUnits, timerangeUnitOptions, setAlertParams]); + + return ( + + + + } + data-test-subj="xpack.uptime.alerts.monitorStatus.timerangeValueExpression" + description="within" + id="timerange" + value={`last ${numUnits}`} + /> + + + + +
+ +
+
+ { + if (newOptions.reduce((acc, { checked }) => acc || checked === 'on', false)) { + setTimerangeUnitOptions(newOptions); + } + }} + singleSelection={true} + listProps={{ + showIcons: true, + }} + > + {list => list} + + + } + data-test-subj="xpack.uptime.alerts.monitorStatus.timerangeUnitExpression" + description="" + id="timerange-unit" + value={ + timerangeUnitOptions.find(({ checked }) => checked === 'on')?.label.toLowerCase() ?? '' + } + /> +
+
+ ); +}; diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/translations.ts b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/translations.ts new file mode 100644 index 00000000000000..5fefc9f3ae35b5 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/translations.ts @@ -0,0 +1,72 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const alertFilterLabels = { + USING: i18n.translate('xpack.uptime.alerts.monitorStatus.filters.using', { + defaultMessage: 'Using', + }), + + USING_PORT: i18n.translate('xpack.uptime.alerts.monitorStatus.filters.usingPort', { + defaultMessage: 'Using port', + }), + + ANY_PORT: i18n.translate('xpack.uptime.alerts.monitorStatus.filters.anyPort', { + defaultMessage: 'any port', + }), + + WITH: i18n.translate('xpack.uptime.alerts.monitorStatus.filters.with', { + defaultMessage: 'Using', + }), + + WITH_TAG: i18n.translate('xpack.uptime.alerts.monitorStatus.filters.withTag', { + defaultMessage: 'With tag', + }), + + ANY_TAG: i18n.translate('xpack.uptime.alerts.monitorStatus.filters.anyTag', { + defaultMessage: 'any tag', + }), + + OF: i18n.translate('xpack.uptime.alerts.monitorStatus.filters.of', { + defaultMessage: 'Of', + }), + + OF_TYPE: i18n.translate('xpack.uptime.alerts.monitorStatus.filters.ofType', { + defaultMessage: 'Of type', + }), + + ANY_TYPE: i18n.translate('xpack.uptime.alerts.monitorStatus.filters.anyType', { + defaultMessage: 'any type', + }), + + FROM: i18n.translate('xpack.uptime.alerts.monitorStatus.filters.from', { + defaultMessage: 'From', + }), + + FROM_LOCATION: i18n.translate('xpack.uptime.alerts.monitorStatus.filters.fromLocation', { + defaultMessage: 'From location', + }), + + ANY_LOCATION: i18n.translate('xpack.uptime.alerts.monitorStatus.filters.anyLocation', { + defaultMessage: 'any location', + }), +}; + +export const timeExpLabels = { + OPEN_TIME_POPOVER: i18n.translate( + 'xpack.uptime.alerts.monitorStatus.timerangeUnitExpression.ariaLabel', + { + defaultMessage: 'Open the popover for time range unit select field', + } + ), + SELECT_TIME_RANGE_ARIA: i18n.translate( + 'xpack.uptime.alerts.monitorStatus.timerangeUnitSelectable', + { + defaultMessage: 'Selectable field for the time range units alerts should use', + } + ), +}; diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/settings_message_expression_popover.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/settings_message_expression_popover.tsx new file mode 100644 index 00000000000000..8d9de08751eeef --- /dev/null +++ b/x-pack/plugins/uptime/public/components/overview/alerts/settings_message_expression_popover.tsx @@ -0,0 +1,72 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiExpression, EuiPopover } from '@elastic/eui'; +import { Link } from 'react-router-dom'; +import React, { useState } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { SETTINGS_ROUTE } from '../../../../common/constants'; + +interface SettingsMessageExpressionPopoverProps { + 'aria-label': string; + description: string; + id: string; + setAlertFlyoutVisible: (value: boolean) => void; + value: string; +} + +export const SettingsMessageExpressionPopover: React.FC = ({ + 'aria-label': ariaLabel, + description, + setAlertFlyoutVisible, + value, + id, +}) => { + const [isOpen, setIsOpen] = useState(false); + return ( + setIsOpen(!isOpen)} + value={value} + /> + } + isOpen={isOpen} + closePopover={() => setIsOpen(false)} + > + + { + setAlertFlyoutVisible(false); + }} + onKeyUp={e => { + if (e.key === 'Enter') { + setAlertFlyoutVisible(false); + } + }} + > + settings page + + + ), + }} + /> + + ); +}; diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/toggle_alert_flyout_button.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/toggle_alert_flyout_button.tsx index 92fc015a276d3f..cba96cd2fe5b12 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/toggle_alert_flyout_button.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/toggle_alert_flyout_button.tsx @@ -4,27 +4,128 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiButtonEmpty, EuiContextMenuItem, EuiContextMenuPanel, EuiPopover } from '@elastic/eui'; +import { + EuiButtonEmpty, + EuiContextMenu, + EuiContextMenuPanelDescriptor, + EuiContextMenuPanelItemDescriptor, + EuiLink, + EuiPopover, +} from '@elastic/eui'; import React, { useState } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -import { i18n } from '@kbn/i18n'; import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; +import { CLIENT_ALERT_TYPES } from '../../../../common/constants'; +import { ToggleFlyoutTranslations } from './translations'; +import { ToggleAlertFlyoutButtonProps } from './alerts_containers'; -interface Props { - setAlertFlyoutVisible: (value: boolean) => void; +interface ComponentProps { + setAlertFlyoutVisible: (value: boolean | string) => void; } -export const ToggleAlertFlyoutButtonComponent = ({ setAlertFlyoutVisible }: Props) => { +type Props = ComponentProps & ToggleAlertFlyoutButtonProps; + +const ALERT_CONTEXT_MAIN_PANEL_ID = 0; +const ALERT_CONTEXT_SELECT_TYPE_PANEL_ID = 1; + +export const ToggleAlertFlyoutButtonComponent: React.FC = ({ + alertOptions, + setAlertFlyoutVisible, +}) => { const [isOpen, setIsOpen] = useState(false); const kibana = useKibana(); + const monitorStatusAlertContextMenuItem: EuiContextMenuPanelItemDescriptor = { + 'aria-label': ToggleFlyoutTranslations.toggleMonitorStatusAriaLabel, + 'data-test-subj': 'xpack.uptime.toggleAlertFlyout', + name: ToggleFlyoutTranslations.toggleMonitorStatusContent, + onClick: () => { + setAlertFlyoutVisible(CLIENT_ALERT_TYPES.MONITOR_STATUS); + setIsOpen(false); + }, + }; + + const tlsAlertContextMenuItem: EuiContextMenuPanelItemDescriptor = { + 'aria-label': ToggleFlyoutTranslations.toggleTlsAriaLabel, + 'data-test-subj': 'xpack.uptime.toggleTlsAlertFlyout', + name: ToggleFlyoutTranslations.toggleTlsContent, + onClick: () => { + setAlertFlyoutVisible(CLIENT_ALERT_TYPES.TLS); + setIsOpen(false); + }, + }; + + const managementContextItem: EuiContextMenuPanelItemDescriptor = { + 'aria-label': ToggleFlyoutTranslations.navigateToAlertingUIAriaLabel, + 'data-test-subj': 'xpack.uptime.navigateToAlertingUi', + name: ( + + + + ), + icon: 'tableOfContents', + }; + + let selectionItems: EuiContextMenuPanelItemDescriptor[] = []; + if (!alertOptions) { + selectionItems = [monitorStatusAlertContextMenuItem, tlsAlertContextMenuItem]; + } else { + alertOptions.forEach(option => { + if (option === CLIENT_ALERT_TYPES.MONITOR_STATUS) + selectionItems.push(monitorStatusAlertContextMenuItem); + else if (option === CLIENT_ALERT_TYPES.TLS) selectionItems.push(tlsAlertContextMenuItem); + }); + } + + if (selectionItems.length === 1) { + selectionItems[0].icon = 'bell'; + } + + let panels: EuiContextMenuPanelDescriptor[]; + if (selectionItems.length === 1) { + panels = [ + { + id: ALERT_CONTEXT_MAIN_PANEL_ID, + title: 'main panel', + items: [...selectionItems, managementContextItem], + }, + ]; + } else { + panels = [ + { + id: ALERT_CONTEXT_MAIN_PANEL_ID, + title: 'main panel', + items: [ + { + 'aria-label': ToggleFlyoutTranslations.openAlertContextPanelAriaLabel, + 'data-test-subj': 'xpack.uptime.openAlertContextPanel', + name: ToggleFlyoutTranslations.openAlertContextPanelLabel, + icon: 'bell', + panel: ALERT_CONTEXT_SELECT_TYPE_PANEL_ID, + }, + managementContextItem, + ], + }, + { + id: ALERT_CONTEXT_SELECT_TYPE_PANEL_ID, + title: 'create alerts', + items: selectionItems, + }, + ]; + } return ( - { - setAlertFlyoutVisible(true); - setIsOpen(false); - }} - > - - , - - - , - ]} - /> + ); }; diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/translations.ts b/x-pack/plugins/uptime/public/components/overview/alerts/translations.ts new file mode 100644 index 00000000000000..637fe0a108958b --- /dev/null +++ b/x-pack/plugins/uptime/public/components/overview/alerts/translations.ts @@ -0,0 +1,192 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const SECONDS_TIME_RANGE = i18n.translate( + 'xpack.uptime.alerts.timerangeUnitSelectable.secondsOption.ariaLabel', + { + defaultMessage: '"Seconds" time range select item', + } +); + +export const SECONDS = i18n.translate('xpack.uptime.alerts.monitorStatus.timerangeOption.seconds', { + defaultMessage: 'seconds', +}); + +export const MINUTES_TIME_RANGE = i18n.translate( + 'xpack.uptime.alerts.timerangeUnitSelectable.minutesOption.ariaLabel', + { + defaultMessage: '"Minutes" time range select item', + } +); + +export const MINUTES = i18n.translate('xpack.uptime.alerts.monitorStatus.timerangeOption.minutes', { + defaultMessage: 'minutes', +}); + +export const HOURS_TIME_RANGE = i18n.translate( + 'xpack.uptime.alerts.timerangeUnitSelectable.hoursOption.ariaLabel', + { + defaultMessage: '"Hours" time range select item', + } +); + +export const HOURS = i18n.translate('xpack.uptime.alerts.monitorStatus.timerangeOption.hours', { + defaultMessage: 'hours', +}); + +export const DAYS_TIME_RANGE = i18n.translate( + 'xpack.uptime.alerts.timerangeUnitSelectable.daysOption.ariaLabel', + { + defaultMessage: '"Days" time range select item', + } +); + +export const DAYS = i18n.translate('xpack.uptime.alerts.monitorStatus.timerangeOption.days', { + defaultMessage: 'days', +}); + +export const ALERT_KUERY_BAR_ARIA = i18n.translate( + 'xpack.uptime.alerts.monitorStatus.filterBar.ariaLabel', + { + defaultMessage: 'Input that allows filtering criteria for the monitor status alert', + } +); + +export const OPEN_THE_POPOVER_DOWN_COUNT = i18n.translate( + 'xpack.uptime.alerts.monitorStatus.numTimesExpression.ariaLabel', + { + defaultMessage: 'Open the popover for down count input', + } +); + +export const ENTER_NUMBER_OF_DOWN_COUNTS = i18n.translate( + 'xpack.uptime.alerts.monitorStatus.numTimesField.ariaLabel', + { + defaultMessage: 'Enter number of down counts required to trigger the alert', + } +); + +export const MATCHING_MONITORS_DOWN = i18n.translate( + 'xpack.uptime.alerts.monitorStatus.numTimesExpression.matchingMonitors.description', + { + defaultMessage: 'matching monitors are down >', + } +); + +export const ANY_MONITOR_DOWN = i18n.translate( + 'xpack.uptime.alerts.monitorStatus.numTimesExpression.anyMonitors.description', + { + defaultMessage: 'any monitor is down >', + } +); + +export const OPEN_THE_POPOVER_TIME_RANGE_VALUE = i18n.translate( + 'xpack.uptime.alerts.monitorStatus.timerangeValueExpression.ariaLabel', + { + defaultMessage: 'Open the popover for time range value field', + } +); + +export const ENTER_NUMBER_OF_TIME_UNITS = i18n.translate( + 'xpack.uptime.alerts.monitorStatus.timerangeValueField.ariaLabel', + { + defaultMessage: `Enter the number of time units for the alert's range`, + } +); + +export const ADD_FILTER = i18n.translate('xpack.uptime.alerts.monitorStatus.addFilter', { + defaultMessage: `Add filter`, +}); + +export const LOCATION = i18n.translate('xpack.uptime.alerts.monitorStatus.addFilter.location', { + defaultMessage: `Location`, +}); + +export const TAG = i18n.translate('xpack.uptime.alerts.monitorStatus.addFilter.tag', { + defaultMessage: `Tag`, +}); + +export const PORT = i18n.translate('xpack.uptime.alerts.monitorStatus.addFilter.port', { + defaultMessage: `Port`, +}); + +export const TYPE = i18n.translate('xpack.uptime.alerts.monitorStatus.addFilter.type', { + defaultMessage: `Type`, +}); + +export const TlsTranslations = { + criteriaAriaLabel: i18n.translate('xpack.uptime.alerts.tls.criteriaExpression.ariaLabel', { + defaultMessage: + 'An expression displaying the criteria for monitor that are watched by this alert', + }), + criteriaDescription: i18n.translate('xpack.uptime.alerts.tls.criteriaExpression.description', { + defaultMessage: 'when', + description: + 'The context of this `when` is in the conditional sense, like "when there are three cookies, eat them all".', + }), + criteriaValue: i18n.translate('xpack.uptime.alerts.tls.criteriaExpression.value', { + defaultMessage: 'any monitor', + }), + expirationAriaLabel: i18n.translate('xpack.uptime.alerts.tls.expirationExpression.ariaLabel', { + defaultMessage: + 'An expression displaying the threshold that will trigger the TLS alert for certificate expiration', + }), + expirationDescription: i18n.translate( + 'xpack.uptime.alerts.tls.expirationExpression.description', + { + defaultMessage: 'has a certificate expiring within', + } + ), + expirationValue: (value?: number) => + i18n.translate('xpack.uptime.alerts.tls.expirationExpression.value', { + defaultMessage: '{value} days', + values: { value }, + }), + ageAriaLabel: i18n.translate('xpack.uptime.alerts.tls.ageExpression.ariaLabel', { + defaultMessage: + 'An expressing displaying the threshold that will trigger the TLS alert for old certificates', + }), + ageDescription: i18n.translate('xpack.uptime.alerts.tls.ageExpression.description', { + defaultMessage: 'or older than', + }), + ageValue: (value?: number) => + i18n.translate('xpack.uptime.alerts.tls.ageExpression.value', { + defaultMessage: '{value} days', + values: { value }, + }), +}; + +export const ToggleFlyoutTranslations = { + toggleButtonAriaLabel: i18n.translate('xpack.uptime.alertsPopover.toggleButton.ariaLabel', { + defaultMessage: 'Open alert context menu', + }), + openAlertContextPanelAriaLabel: i18n.translate('xpack.uptime.openAlertContextPanel.ariaLabel', { + defaultMessage: 'Open the alert context panel so you can choose an alert type', + }), + openAlertContextPanelLabel: i18n.translate('xpack.uptime.openAlertContextPanel.label', { + defaultMessage: 'Create alert', + }), + toggleTlsAriaLabel: i18n.translate('xpack.uptime.toggleTlsAlertButton.ariaLabel', { + defaultMessage: 'Open TLS alert flyout', + }), + toggleTlsContent: i18n.translate('xpack.uptime.toggleTlsAlertButton.content', { + defaultMessage: 'TLS alert', + }), + toggleMonitorStatusAriaLabel: i18n.translate('xpack.uptime.toggleAlertFlyout.ariaLabel', { + defaultMessage: 'Open add alert flyout', + }), + toggleMonitorStatusContent: i18n.translate('xpack.uptime.toggleAlertButton.content', { + defaultMessage: 'Monitor status alert', + }), + navigateToAlertingUIAriaLabel: i18n.translate('xpack.uptime.navigateToAlertingUi', { + defaultMessage: 'Leave Uptime and go to Alerting Management page', + }), + navigateToAlertingButtonContent: i18n.translate('xpack.uptime.navigateToAlertingButton.content', { + defaultMessage: 'Manage alerts', + }), +}; diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/uptime_alerts_flyout_wrapper.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/uptime_alerts_flyout_wrapper.tsx index 9b1d3a73dc6614..de4e67cfe8edce 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/uptime_alerts_flyout_wrapper.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/uptime_alerts_flyout_wrapper.tsx @@ -10,14 +10,12 @@ import { AlertAdd } from '../../../../../../plugins/triggers_actions_ui/public'; interface Props { alertFlyoutVisible: boolean; alertTypeId?: string; - canChangeTrigger?: boolean; setAlertFlyoutVisibility: React.Dispatch>; } export const UptimeAlertsFlyoutWrapperComponent = ({ alertFlyoutVisible, alertTypeId, - canChangeTrigger, setAlertFlyoutVisibility, }: Props) => ( ); diff --git a/x-pack/plugins/uptime/public/components/overview/filter_group/__tests__/__snapshots__/filter_popover.test.tsx.snap b/x-pack/plugins/uptime/public/components/overview/filter_group/__tests__/__snapshots__/filter_popover.test.tsx.snap index 31a7e12dbdc291..c7ffc36532b71c 100644 --- a/x-pack/plugins/uptime/public/components/overview/filter_group/__tests__/__snapshots__/filter_popover.test.tsx.snap +++ b/x-pack/plugins/uptime/public/components/overview/filter_group/__tests__/__snapshots__/filter_popover.test.tsx.snap @@ -21,7 +21,7 @@ exports[`FilterPopover component does not show item list when loading 1`] = ` ownFocus={true} panelPaddingSize="m" withTitle={true} - zIndex={1000} + zIndex={10000} > void; } export const FilterGroupComponent: React.FC = ({ - currentFilter, overviewFilters, loading, - onFilterUpdate, }) => { const { locations, ports, schemes, tags } = overviewFilters; - let filterKueries: Map; - try { - filterKueries = new Map(JSON.parse(currentFilter)); - } catch { - filterKueries = new Map(); - } + const [updatedFieldValues, setUpdatedFieldValues] = useState<{ + fieldName: string; + values: string[]; + }>({ fieldName: '', values: [] }); - /** - * Handle an added or removed value to filter against for an uptime field. - * @param fieldName the name of the field to filter against - * @param values the list of values to use when filter a field - */ - const onFilterFieldChange = (fieldName: string, values: string[]) => { - // add new term to filter map, toggle it off if already present - const updatedFilterMap = new Map(filterKueries); - updatedFilterMap.set(fieldName, values); - Array.from(updatedFilterMap.keys()).forEach(key => { - const value = updatedFilterMap.get(key); - if (value && value.length === 0) { - updatedFilterMap.delete(key); - } - }); + const currentFilters = useFilterUpdate(updatedFieldValues.fieldName, updatedFieldValues.values); - // store the new set of filters - const persistedFilters = Array.from(updatedFilterMap); - onFilterUpdate(persistedFilters.length === 0 ? '' : JSON.stringify(persistedFilters)); + const onFilterFieldChange = (fieldName: string, values: string[]) => { + setUpdatedFieldValues({ fieldName, values }); }; - const getSelectedItems = (fieldName: string) => filterKueries.get(fieldName) || []; + const getSelectedItems = (fieldName: string) => currentFilters.get(fieldName) || []; const filterPopoverProps: FilterPopoverProps[] = [ { @@ -64,9 +45,7 @@ export const FilterGroupComponent: React.FC = ({ id: 'location', items: locations, selectedItems: getSelectedItems('observer.geo.name'), - title: i18n.translate('xpack.uptime.filterBar.options.location.name', { - defaultMessage: 'Location', - }), + title: filterLabels.LOCATION, }, { loading, @@ -76,7 +55,7 @@ export const FilterGroupComponent: React.FC = ({ disabled: ports.length === 0, items: ports.map((p: number) => p.toString()), selectedItems: getSelectedItems('url.port'), - title: i18n.translate('xpack.uptime.filterBar.options.portLabel', { defaultMessage: 'Port' }), + title: filterLabels.PORT, }, { loading, @@ -86,9 +65,7 @@ export const FilterGroupComponent: React.FC = ({ disabled: schemes.length === 0, items: schemes, selectedItems: getSelectedItems('monitor.type'), - title: i18n.translate('xpack.uptime.filterBar.options.schemeLabel', { - defaultMessage: 'Scheme', - }), + title: filterLabels.SCHEME, }, { loading, @@ -98,9 +75,7 @@ export const FilterGroupComponent: React.FC = ({ disabled: tags.length === 0, items: tags, selectedItems: getSelectedItems('tags'), - title: i18n.translate('xpack.uptime.filterBar.options.tagsLabel', { - defaultMessage: 'Tags', - }), + title: filterLabels.TAGS, }, ]; diff --git a/x-pack/plugins/uptime/public/components/overview/filter_group/filter_group_container.tsx b/x-pack/plugins/uptime/public/components/overview/filter_group/filter_group_container.tsx index 3612604fdf1162..67cb89745269e9 100644 --- a/x-pack/plugins/uptime/public/components/overview/filter_group/filter_group_container.tsx +++ b/x-pack/plugins/uptime/public/components/overview/filter_group/filter_group_container.tsx @@ -5,56 +5,41 @@ */ import React, { useContext, useEffect } from 'react'; -import { connect } from 'react-redux'; -import { useUrlParams } from '../../../hooks'; +import { useDispatch, useSelector } from 'react-redux'; +import { useGetUrlParams } from '../../../hooks'; import { parseFiltersMap } from './parse_filter_map'; -import { AppState } from '../../../state'; -import { fetchOverviewFilters, GetOverviewFiltersPayload } from '../../../state/actions'; +import { fetchOverviewFilters } from '../../../state/actions'; import { FilterGroupComponent } from './index'; -import { OverviewFilters } from '../../../../common/runtime_types/overview_filters'; import { UptimeRefreshContext } from '../../../contexts'; +import { filterGroupDataSelector } from '../../../state/selectors'; -interface OwnProps { +interface Props { esFilters?: string; } -interface StoreProps { - esKuery: string; - lastRefresh: number; - loading: boolean; - overviewFilters: OverviewFilters; -} - -interface DispatchProps { - loadFilterGroup: typeof fetchOverviewFilters; -} +export const FilterGroup: React.FC = ({ esFilters }: Props) => { + const { lastRefresh } = useContext(UptimeRefreshContext); -type Props = OwnProps & StoreProps & DispatchProps; + const { esKuery, filters: overviewFilters, loading } = useSelector(filterGroupDataSelector); -export const Container: React.FC = ({ - esKuery, - esFilters, - loading, - loadFilterGroup, - overviewFilters, -}: Props) => { - const { lastRefresh } = useContext(UptimeRefreshContext); + const { dateRangeStart, dateRangeEnd, statusFilter, filters: urlFilters } = useGetUrlParams(); - const [getUrlParams, updateUrl] = useUrlParams(); - const { dateRangeStart, dateRangeEnd, statusFilter, filters: urlFilters } = getUrlParams(); + const dispatch = useDispatch(); useEffect(() => { const filterSelections = parseFiltersMap(urlFilters); - loadFilterGroup({ - dateRangeStart, - dateRangeEnd, - locations: filterSelections.locations ?? [], - ports: filterSelections.ports ?? [], - schemes: filterSelections.schemes ?? [], - search: esKuery, - statusFilter, - tags: filterSelections.tags ?? [], - }); + dispatch( + fetchOverviewFilters({ + dateRangeStart, + dateRangeEnd, + locations: filterSelections.locations ?? [], + ports: filterSelections.ports ?? [], + schemes: filterSelections.schemes ?? [], + search: esKuery, + statusFilter, + tags: filterSelections.tags ?? [], + }) + ); }, [ lastRefresh, dateRangeStart, @@ -63,42 +48,8 @@ export const Container: React.FC = ({ esFilters, statusFilter, urlFilters, - loadFilterGroup, + dispatch, ]); - // update filters in the URL from filter group - const onFilterUpdate = (filtersKuery: string) => { - if (urlFilters !== filtersKuery) { - updateUrl({ filters: filtersKuery, pagination: '' }); - } - }; - - return ( - - ); + return ; }; - -const mapStateToProps = ({ - overviewFilters: { loading, filters }, - ui: { esKuery, lastRefresh }, -}: AppState): StoreProps => ({ - esKuery, - overviewFilters: filters, - lastRefresh, - loading, -}); - -const mapDispatchToProps = (dispatch: any): DispatchProps => ({ - loadFilterGroup: (payload: GetOverviewFiltersPayload) => dispatch(fetchOverviewFilters(payload)), -}); - -export const FilterGroup = connect( - // @ts-ignore connect is expecting null | undefined for some reason - mapStateToProps, - mapDispatchToProps -)(Container); diff --git a/x-pack/plugins/uptime/public/components/overview/filter_group/filter_popover.tsx b/x-pack/plugins/uptime/public/components/overview/filter_group/filter_popover.tsx index ac65063ee897d3..18d40b83be3696 100644 --- a/x-pack/plugins/uptime/public/components/overview/filter_group/filter_popover.tsx +++ b/x-pack/plugins/uptime/public/components/overview/filter_group/filter_popover.tsx @@ -20,6 +20,9 @@ export interface FilterPopoverProps { onFilterFieldChange: (fieldName: string, values: string[]) => void; selectedItems: string[]; title: string; + btnContent?: JSX.Element; + forceOpen?: boolean; + setForceOpen?: (val: boolean) => void; } const isItemSelected = (selectedItems: string[], item: string): 'on' | undefined => @@ -34,6 +37,9 @@ export const FilterPopover = ({ onFilterFieldChange, selectedItems, title, + btnContent, + forceOpen, + setForceOpen, }: FilterPopoverProps) => { const [isOpen, setIsOpen] = useState(false); const [itemsToDisplay, setItemsToDisplay] = useState([]); @@ -52,28 +58,33 @@ export const FilterPopover = ({ return ( 0} - numFilters={items.length} - numActiveFilters={tempSelectedItems.length} - onClick={() => { - setIsOpen(!isOpen); - onFilterFieldChange(fieldName, tempSelectedItems); - }} - title={title} - /> + btnContent ?? ( + 0} + numFilters={items.length} + numActiveFilters={tempSelectedItems.length} + onClick={() => { + setIsOpen(!isOpen); + onFilterFieldChange(fieldName, tempSelectedItems); + }} + title={title} + /> + ) } closePopover={() => { setIsOpen(false); onFilterFieldChange(fieldName, tempSelectedItems); + if (setForceOpen) { + setForceOpen(false); + } }} data-test-subj={`filter-popover_${id}`} id={id} - isOpen={isOpen} + isOpen={isOpen || forceOpen} ownFocus={true} withTitle - zIndex={1000} + zIndex={10000} > ({ - setEsKueryFilters: (esFilters: string) => dispatch(setEsKueryString(esFilters)), -}); - -const mapStateToProps = (state: AppState) => ({ ...selectIndexPattern(state) }); - -export const OverviewPage = connect(mapStateToProps, mapDispatchToProps)(OverviewPageComponent); +export const OverviewPage: React.FC = props => { + const dispatch = useDispatch(); + const setEsKueryFilters = useCallback( + (esFilters: string) => dispatch(setEsKueryString(esFilters)), + [dispatch] + ); + const indexPattern = useSelector(selectIndexPattern); + return ( + + ); +}; diff --git a/x-pack/plugins/uptime/public/hooks/use_filter_update.ts b/x-pack/plugins/uptime/public/hooks/use_filter_update.ts new file mode 100644 index 00000000000000..97ef6b0e67ad2e --- /dev/null +++ b/x-pack/plugins/uptime/public/hooks/use_filter_update.ts @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useEffect } from 'react'; +import { useUrlParams } from './use_url_params'; + +/** + * Handle an added or removed value to filter against for an uptime field. + * @param fieldName the name of the field to filter against + * @param values the list of values to use when filter a field + */ + +export const useFilterUpdate = (fieldName?: string, values?: string[]) => { + const [getUrlParams, updateUrl] = useUrlParams(); + + const { filters: currentFilters } = getUrlParams(); + + // update filters in the URL from filter group + const onFilterUpdate = (filtersKuery: string) => { + if (currentFilters !== filtersKuery) { + updateUrl({ filters: filtersKuery, pagination: '' }); + } + }; + + let filterKueries: Map; + try { + filterKueries = new Map(JSON.parse(currentFilters)); + } catch { + filterKueries = new Map(); + } + + useEffect(() => { + if (fieldName) { + // add new term to filter map, toggle it off if already present + const updatedFilterMap = new Map(filterKueries); + updatedFilterMap.set(fieldName, values); + Array.from(updatedFilterMap.keys()).forEach(key => { + const value = updatedFilterMap.get(key); + if (value && value.length === 0) { + updatedFilterMap.delete(key); + } + }); + + // store the new set of filters + const persistedFilters = Array.from(updatedFilterMap); + onFilterUpdate(persistedFilters.length === 0 ? '' : JSON.stringify(persistedFilters)); + } + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [fieldName, values]); + + return filterKueries; +}; diff --git a/x-pack/plugins/uptime/public/lib/alert_types/__tests__/monitor_status.test.ts b/x-pack/plugins/uptime/public/lib/alert_types/__tests__/monitor_status.test.ts index 81402c00e484ec..0d18facaa5bbb2 100644 --- a/x-pack/plugins/uptime/public/lib/alert_types/__tests__/monitor_status.test.ts +++ b/x-pack/plugins/uptime/public/lib/alert_types/__tests__/monitor_status.test.ts @@ -174,7 +174,7 @@ describe('monitor status alert type', () => { {{context.downMonitorsWithGeo}}", "iconClass": "uptimeApp", "id": "xpack.uptime.alerts.monitorStatus", - "name": "Uptime monitor status", + "name": , "validate": [Function], } `); diff --git a/x-pack/plugins/uptime/public/lib/alert_types/index.ts b/x-pack/plugins/uptime/public/lib/alert_types/index.ts index f7ab254ffe675f..9a0151e95748c5 100644 --- a/x-pack/plugins/uptime/public/lib/alert_types/index.ts +++ b/x-pack/plugins/uptime/public/lib/alert_types/index.ts @@ -6,7 +6,11 @@ import { AlertTypeModel } from '../../../../triggers_actions_ui/public'; import { initMonitorStatusAlertType } from './monitor_status'; +import { initTlsAlertType } from './tls'; export type AlertTypeInitializer = (dependenies: { autocomplete: any }) => AlertTypeModel; -export const alertTypeInitializers: AlertTypeInitializer[] = [initMonitorStatusAlertType]; +export const alertTypeInitializers: AlertTypeInitializer[] = [ + initMonitorStatusAlertType, + initTlsAlertType, +]; diff --git a/x-pack/plugins/uptime/public/lib/alert_types/monitor_status.tsx b/x-pack/plugins/uptime/public/lib/alert_types/monitor_status.tsx index e7695fb1cbb56c..66e61fbf73b649 100644 --- a/x-pack/plugins/uptime/public/lib/alert_types/monitor_status.tsx +++ b/x-pack/plugins/uptime/public/lib/alert_types/monitor_status.tsx @@ -12,6 +12,9 @@ import { AlertTypeModel } from '../../../../triggers_actions_ui/public'; import { AlertTypeInitializer } from '.'; import { StatusCheckExecutorParamsType } from '../../../common/runtime_types'; import { AlertMonitorStatus } from '../../components/overview/alerts/alerts_containers'; +import { MonitorStatusTitle } from './monitor_status_title'; +import { CLIENT_ALERT_TYPES } from '../../../common/constants'; +import { MonitorStatusTranslations } from './translations'; export const validate = (alertParams: any) => { const errors: Record = {}; @@ -52,15 +55,15 @@ export const validate = (alertParams: any) => { return { errors }; }; +const { defaultActionMessage } = MonitorStatusTranslations; + export const initMonitorStatusAlertType: AlertTypeInitializer = ({ autocomplete, }): AlertTypeModel => ({ - id: 'xpack.uptime.alerts.monitorStatus', - name: 'Uptime monitor status', + id: CLIENT_ALERT_TYPES.MONITOR_STATUS, + name: , iconClass: 'uptimeApp', - alertParamsExpression: params => { - return ; - }, + alertParamsExpression: params => , validate, - defaultActionMessage: `{{context.message}}\nLast triggered at: {{state.lastTriggeredAt}}\n{{context.downMonitorsWithGeo}}`, + defaultActionMessage, }); diff --git a/x-pack/plugins/uptime/public/lib/alert_types/monitor_status_title.tsx b/x-pack/plugins/uptime/public/lib/alert_types/monitor_status_title.tsx new file mode 100644 index 00000000000000..3fe497f9e88bc3 --- /dev/null +++ b/x-pack/plugins/uptime/public/lib/alert_types/monitor_status_title.tsx @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { useSelector } from 'react-redux'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner, EuiText } from '@elastic/eui'; +import { snapshotDataSelector } from '../../state/selectors'; + +export const MonitorStatusTitle = () => { + const { count, loading } = useSelector(snapshotDataSelector); + return ( + + + {' '} + + + {!loading ? ( + + {count.total} monitors + + ) : ( + + )} + + + ); +}; diff --git a/x-pack/plugins/uptime/public/lib/alert_types/tls.tsx b/x-pack/plugins/uptime/public/lib/alert_types/tls.tsx new file mode 100644 index 00000000000000..0a5c09acbb69fe --- /dev/null +++ b/x-pack/plugins/uptime/public/lib/alert_types/tls.tsx @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { AlertTypeModel } from '../../../../triggers_actions_ui/public'; +import { CLIENT_ALERT_TYPES } from '../../../common/constants'; +import { TlsTranslations } from './translations'; +import { AlertTypeInitializer } from '.'; +import { AlertTls } from '../../components/overview/alerts/alerts_containers/alert_tls'; + +const { name, defaultActionMessage } = TlsTranslations; + +export const initTlsAlertType: AlertTypeInitializer = (): AlertTypeModel => ({ + id: CLIENT_ALERT_TYPES.TLS, + iconClass: 'uptimeApp', + alertParamsExpression: () => , + name, + validate: () => ({ errors: {} }), + defaultActionMessage, +}); diff --git a/x-pack/plugins/uptime/public/lib/alert_types/translations.ts b/x-pack/plugins/uptime/public/lib/alert_types/translations.ts new file mode 100644 index 00000000000000..cdf3cd107b00f9 --- /dev/null +++ b/x-pack/plugins/uptime/public/lib/alert_types/translations.ts @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const MonitorStatusTranslations = { + defaultActionMessage: i18n.translate('xpack.uptime.alerts.monitorStatus.defaultActionMessage', { + defaultMessage: '{contextMessage}\nLast triggered at: {lastTriggered}\n{downMonitors}', + values: { + contextMessage: '{{context.message}}', + lastTriggered: '{{state.lastTriggeredAt}}', + downMonitors: '{{context.downMonitorsWithGeo}}', + }, + }), + name: i18n.translate('xpack.uptime.alerts.monitorStatus.clientName', { + defaultMessage: 'Uptime monitor status', + }), +}; + +export const TlsTranslations = { + defaultActionMessage: i18n.translate('xpack.uptime.alerts.tls.defaultActionMessage', { + defaultMessage: `Detected {count} TLS certificates expiring or becoming too old. + +{expiringConditionalOpen} +Expiring cert count: {expiringCount} +Expiring Certificates: {expiringCommonNameAndDate} +{expiringConditionalClose} + +{agingConditionalOpen} +Aging cert count: {agingCount} +Aging Certificates: {agingCommonNameAndDate} +{agingConditionalClose} +`, + values: { + count: '{{state.count}}', + expiringCount: '{{state.expiringCount}}', + expiringCommonNameAndDate: '{{state.expiringCommonNameAndDate}}', + expiringConditionalOpen: '{{#state.hasExpired}}', + expiringConditionalClose: '{{/state.hasExpired}}', + agingCount: '{{state.agingCount}}', + agingCommonNameAndDate: '{{state.agingCommonNameAndDate}}', + agingConditionalOpen: '{{#state.hasAging}}', + agingConditionalClose: '{{/state.hasAging}}', + }, + }), + name: i18n.translate('xpack.uptime.alerts.tls.clientName', { + defaultMessage: 'Uptime TLS', + }), +}; diff --git a/x-pack/plugins/uptime/public/pages/certificates.tsx b/x-pack/plugins/uptime/public/pages/certificates.tsx index d6c1b8e2b4568c..92a41adcf01cdf 100644 --- a/x-pack/plugins/uptime/public/pages/certificates.tsx +++ b/x-pack/plugins/uptime/public/pages/certificates.tsx @@ -19,13 +19,14 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { useTrackPageview } from '../../../observability/public'; import { PageHeader } from './page_header'; import { useBreadcrumbs } from '../hooks/use_breadcrumbs'; -import { OVERVIEW_ROUTE, SETTINGS_ROUTE } from '../../common/constants'; +import { OVERVIEW_ROUTE, SETTINGS_ROUTE, CLIENT_ALERT_TYPES } from '../../common/constants'; import { getDynamicSettings } from '../state/actions/dynamic_settings'; import { UptimeRefreshContext } from '../contexts'; import * as labels from './translations'; import { UptimePage, useUptimeTelemetry } from '../hooks'; import { certificatesSelector, getCertificatesAction } from '../state/certificates/certificates'; import { CertificateList, CertificateSearch, CertSort } from '../components/certificates'; +import { ToggleAlertFlyoutButton } from '../components/overview/alerts/alerts_containers'; const DEFAULT_PAGE_SIZE = 10; const LOCAL_STORAGE_KEY = 'xpack.uptime.certList.pageSize'; @@ -83,6 +84,9 @@ export const CertificatesPage: React.FC = () => { + + + diff --git a/x-pack/plugins/uptime/public/pages/overview.tsx b/x-pack/plugins/uptime/public/pages/overview.tsx index fefd804cbfabf0..65f64aa7352a9e 100644 --- a/x-pack/plugins/uptime/public/pages/overview.tsx +++ b/x-pack/plugins/uptime/public/pages/overview.tsx @@ -11,22 +11,20 @@ import { i18n } from '@kbn/i18n'; import { useUptimeTelemetry, UptimePage, useGetUrlParams } from '../hooks'; import { stringifyUrlParams } from '../lib/helper/stringify_url_params'; import { PageHeader } from './page_header'; -import { DataPublicPluginSetup, IIndexPattern } from '../../../../../src/plugins/data/public'; +import { IIndexPattern } from '../../../../../src/plugins/data/public'; import { useUpdateKueryString } from '../hooks'; import { useBreadcrumbs } from '../hooks/use_breadcrumbs'; import { useTrackPageview } from '../../../observability/public'; import { MonitorList } from '../components/overview/monitor_list/monitor_list_container'; import { EmptyState, FilterGroup, KueryBar, ParsingErrorCallout } from '../components/overview'; import { StatusPanel } from '../components/overview/status_panel'; +import { OverviewPageProps } from '../components/overview/overview_container'; -interface OverviewPageProps { - autocomplete: DataPublicPluginSetup['autocomplete']; +interface Props extends OverviewPageProps { indexPattern: IIndexPattern | null; setEsKueryFilters: (esFilters: string) => void; } -type Props = OverviewPageProps; - const EuiFlexItemStyled = styled(EuiFlexItem)` && { min-width: 598px; diff --git a/x-pack/plugins/uptime/public/state/actions/ui.ts b/x-pack/plugins/uptime/public/state/actions/ui.ts index 4885f974dbbd49..80e8796843ac2e 100644 --- a/x-pack/plugins/uptime/public/state/actions/ui.ts +++ b/x-pack/plugins/uptime/public/state/actions/ui.ts @@ -12,7 +12,9 @@ export interface PopoverState { export type UiPayload = PopoverState & string & number & Map; -export const setAlertFlyoutVisible = createAction('TOGGLE ALERT FLYOUT'); +export const setAlertFlyoutVisible = createAction('TOGGLE ALERT FLYOUT'); + +export const setAlertFlyoutType = createAction('SET ALERT FLYOUT TYPE'); export const setBasePath = createAction('SET BASE PATH'); diff --git a/x-pack/plugins/uptime/public/state/reducers/ui.ts b/x-pack/plugins/uptime/public/state/reducers/ui.ts index c533f293fc940a..82c2bfe2c0cec2 100644 --- a/x-pack/plugins/uptime/public/state/reducers/ui.ts +++ b/x-pack/plugins/uptime/public/state/reducers/ui.ts @@ -12,11 +12,13 @@ import { setEsKueryString, triggerAppRefresh, UiPayload, + setAlertFlyoutType, setAlertFlyoutVisible, } from '../actions'; export interface UiState { alertFlyoutVisible: boolean; + alertFlyoutType?: string; basePath: string; esKuery: string; integrationsPopoverOpen: PopoverState | null; @@ -57,6 +59,11 @@ export const uiReducer = handleActions( ...state, esKuery: action.payload as string, }), + + [String(setAlertFlyoutType)]: (state, action: Action) => ({ + ...state, + alertFlyoutType: action.payload, + }), }, initialState ); diff --git a/x-pack/plugins/uptime/public/state/selectors/index.ts b/x-pack/plugins/uptime/public/state/selectors/index.ts index 15fc8b8a7b1735..2b7eabe727ad1a 100644 --- a/x-pack/plugins/uptime/public/state/selectors/index.ts +++ b/x-pack/plugins/uptime/public/state/selectors/index.ts @@ -91,6 +91,8 @@ export const selectDurationLines = ({ monitorDuration }: AppState) => { export const selectAlertFlyoutVisibility = ({ ui: { alertFlyoutVisible } }: AppState) => alertFlyoutVisible; +export const selectAlertFlyoutType = ({ ui: { alertFlyoutType } }: AppState) => alertFlyoutType; + export const selectMonitorStatusAlert = ({ indexPattern, overviewFilters, ui }: AppState) => ({ filters: ui.esKuery, indexPattern: indexPattern.index_pattern, @@ -105,3 +107,16 @@ export const monitorListSelector = ({ monitorList, ui: { lastRefresh } }: AppSta monitorList, lastRefresh, }); + +export const overviewFiltersSelector = ({ overviewFilters }: AppState) => { + return overviewFilters.filters; +}; + +export const filterGroupDataSelector = ({ + overviewFilters: { loading, filters }, + ui: { esKuery }, +}: AppState) => ({ + esKuery, + filters, + loading, +}); diff --git a/x-pack/plugins/uptime/public/uptime_app.tsx b/x-pack/plugins/uptime/public/uptime_app.tsx index 0d18f959230d11..836d942d92165e 100644 --- a/x-pack/plugins/uptime/public/uptime_app.tsx +++ b/x-pack/plugins/uptime/public/uptime_app.tsx @@ -105,10 +105,7 @@ const Application = (props: UptimeAppProps) => {
- +
diff --git a/x-pack/plugins/uptime/server/lib/alerts/__tests__/common.test.ts b/x-pack/plugins/uptime/server/lib/alerts/__tests__/common.test.ts new file mode 100644 index 00000000000000..cd06370816cccc --- /dev/null +++ b/x-pack/plugins/uptime/server/lib/alerts/__tests__/common.test.ts @@ -0,0 +1,180 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { updateState } from '../common'; + +describe('updateState', () => { + let spy: jest.SpyInstance; + beforeEach(() => { + spy = jest.spyOn(Date.prototype, 'toISOString'); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('sets initial state values', () => { + spy.mockImplementation(() => 'foo date string'); + const result = updateState({}, false); + expect(spy).toHaveBeenCalledTimes(1); + expect(result).toMatchInlineSnapshot(` + Object { + "currentTriggerStarted": undefined, + "firstCheckedAt": "foo date string", + "firstTriggeredAt": undefined, + "isTriggered": false, + "lastCheckedAt": "foo date string", + "lastResolvedAt": undefined, + "lastTriggeredAt": undefined, + } + `); + }); + + it('updates the correct field in subsequent calls', () => { + spy + .mockImplementationOnce(() => 'first date string') + .mockImplementationOnce(() => 'second date string'); + const firstState = updateState({}, false); + const secondState = updateState(firstState, true); + expect(spy).toHaveBeenCalledTimes(2); + expect(firstState).toMatchInlineSnapshot(` + Object { + "currentTriggerStarted": undefined, + "firstCheckedAt": "first date string", + "firstTriggeredAt": undefined, + "isTriggered": false, + "lastCheckedAt": "first date string", + "lastResolvedAt": undefined, + "lastTriggeredAt": undefined, + } + `); + expect(secondState).toMatchInlineSnapshot(` + Object { + "currentTriggerStarted": "second date string", + "firstCheckedAt": "first date string", + "firstTriggeredAt": "second date string", + "isTriggered": true, + "lastCheckedAt": "second date string", + "lastResolvedAt": undefined, + "lastTriggeredAt": "second date string", + } + `); + }); + + it('correctly marks resolution times', () => { + spy + .mockImplementationOnce(() => 'first date string') + .mockImplementationOnce(() => 'second date string') + .mockImplementationOnce(() => 'third date string'); + const firstState = updateState({}, true); + const secondState = updateState(firstState, true); + const thirdState = updateState(secondState, false); + expect(spy).toHaveBeenCalledTimes(3); + expect(firstState).toMatchInlineSnapshot(` + Object { + "currentTriggerStarted": "first date string", + "firstCheckedAt": "first date string", + "firstTriggeredAt": "first date string", + "isTriggered": true, + "lastCheckedAt": "first date string", + "lastResolvedAt": undefined, + "lastTriggeredAt": "first date string", + } + `); + expect(secondState).toMatchInlineSnapshot(` + Object { + "currentTriggerStarted": "first date string", + "firstCheckedAt": "first date string", + "firstTriggeredAt": "first date string", + "isTriggered": true, + "lastCheckedAt": "second date string", + "lastResolvedAt": undefined, + "lastTriggeredAt": "second date string", + } + `); + expect(thirdState).toMatchInlineSnapshot(` + Object { + "currentTriggerStarted": undefined, + "firstCheckedAt": "first date string", + "firstTriggeredAt": "first date string", + "isTriggered": false, + "lastCheckedAt": "third date string", + "lastResolvedAt": "third date string", + "lastTriggeredAt": "second date string", + } + `); + }); + + it('correctly marks state fields across multiple triggers/resolutions', () => { + spy + .mockImplementationOnce(() => 'first date string') + .mockImplementationOnce(() => 'second date string') + .mockImplementationOnce(() => 'third date string') + .mockImplementationOnce(() => 'fourth date string') + .mockImplementationOnce(() => 'fifth date string'); + const firstState = updateState({}, false); + const secondState = updateState(firstState, true); + const thirdState = updateState(secondState, false); + const fourthState = updateState(thirdState, true); + const fifthState = updateState(fourthState, false); + expect(spy).toHaveBeenCalledTimes(5); + expect(firstState).toMatchInlineSnapshot(` + Object { + "currentTriggerStarted": undefined, + "firstCheckedAt": "first date string", + "firstTriggeredAt": undefined, + "isTriggered": false, + "lastCheckedAt": "first date string", + "lastResolvedAt": undefined, + "lastTriggeredAt": undefined, + } + `); + expect(secondState).toMatchInlineSnapshot(` + Object { + "currentTriggerStarted": "second date string", + "firstCheckedAt": "first date string", + "firstTriggeredAt": "second date string", + "isTriggered": true, + "lastCheckedAt": "second date string", + "lastResolvedAt": undefined, + "lastTriggeredAt": "second date string", + } + `); + expect(thirdState).toMatchInlineSnapshot(` + Object { + "currentTriggerStarted": undefined, + "firstCheckedAt": "first date string", + "firstTriggeredAt": "second date string", + "isTriggered": false, + "lastCheckedAt": "third date string", + "lastResolvedAt": "third date string", + "lastTriggeredAt": "second date string", + } + `); + expect(fourthState).toMatchInlineSnapshot(` + Object { + "currentTriggerStarted": "fourth date string", + "firstCheckedAt": "first date string", + "firstTriggeredAt": "second date string", + "isTriggered": true, + "lastCheckedAt": "fourth date string", + "lastResolvedAt": "third date string", + "lastTriggeredAt": "fourth date string", + } + `); + expect(fifthState).toMatchInlineSnapshot(` + Object { + "currentTriggerStarted": undefined, + "firstCheckedAt": "first date string", + "firstTriggeredAt": "second date string", + "isTriggered": false, + "lastCheckedAt": "fifth date string", + "lastResolvedAt": "fifth date string", + "lastTriggeredAt": "fourth date string", + } + `); + }); +}); diff --git a/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts b/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts index 24da3f3fa4d067..1cc0f1da820c1d 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/__tests__/status_check.test.ts @@ -7,7 +7,6 @@ import { contextMessage, uniqueMonitorIds, - updateState, statusCheckAlertFactory, fullListByIdAndLocation, } from '../status_check'; @@ -337,179 +336,6 @@ describe('status check alert', () => { }); }); - describe('updateState', () => { - let spy: jest.SpyInstance; - beforeEach(() => { - spy = jest.spyOn(Date.prototype, 'toISOString'); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - it('sets initial state values', () => { - spy.mockImplementation(() => 'foo date string'); - const result = updateState({}, false); - expect(spy).toHaveBeenCalledTimes(1); - expect(result).toMatchInlineSnapshot(` - Object { - "currentTriggerStarted": undefined, - "firstCheckedAt": "foo date string", - "firstTriggeredAt": undefined, - "isTriggered": false, - "lastCheckedAt": "foo date string", - "lastResolvedAt": undefined, - "lastTriggeredAt": undefined, - } - `); - }); - - it('updates the correct field in subsequent calls', () => { - spy - .mockImplementationOnce(() => 'first date string') - .mockImplementationOnce(() => 'second date string'); - const firstState = updateState({}, false); - const secondState = updateState(firstState, true); - expect(spy).toHaveBeenCalledTimes(2); - expect(firstState).toMatchInlineSnapshot(` - Object { - "currentTriggerStarted": undefined, - "firstCheckedAt": "first date string", - "firstTriggeredAt": undefined, - "isTriggered": false, - "lastCheckedAt": "first date string", - "lastResolvedAt": undefined, - "lastTriggeredAt": undefined, - } - `); - expect(secondState).toMatchInlineSnapshot(` - Object { - "currentTriggerStarted": "second date string", - "firstCheckedAt": "first date string", - "firstTriggeredAt": "second date string", - "isTriggered": true, - "lastCheckedAt": "second date string", - "lastResolvedAt": undefined, - "lastTriggeredAt": "second date string", - } - `); - }); - - it('correctly marks resolution times', () => { - spy - .mockImplementationOnce(() => 'first date string') - .mockImplementationOnce(() => 'second date string') - .mockImplementationOnce(() => 'third date string'); - const firstState = updateState({}, true); - const secondState = updateState(firstState, true); - const thirdState = updateState(secondState, false); - expect(spy).toHaveBeenCalledTimes(3); - expect(firstState).toMatchInlineSnapshot(` - Object { - "currentTriggerStarted": "first date string", - "firstCheckedAt": "first date string", - "firstTriggeredAt": "first date string", - "isTriggered": true, - "lastCheckedAt": "first date string", - "lastResolvedAt": undefined, - "lastTriggeredAt": "first date string", - } - `); - expect(secondState).toMatchInlineSnapshot(` - Object { - "currentTriggerStarted": "first date string", - "firstCheckedAt": "first date string", - "firstTriggeredAt": "first date string", - "isTriggered": true, - "lastCheckedAt": "second date string", - "lastResolvedAt": undefined, - "lastTriggeredAt": "second date string", - } - `); - expect(thirdState).toMatchInlineSnapshot(` - Object { - "currentTriggerStarted": undefined, - "firstCheckedAt": "first date string", - "firstTriggeredAt": "first date string", - "isTriggered": false, - "lastCheckedAt": "third date string", - "lastResolvedAt": "third date string", - "lastTriggeredAt": "second date string", - } - `); - }); - - it('correctly marks state fields across multiple triggers/resolutions', () => { - spy - .mockImplementationOnce(() => 'first date string') - .mockImplementationOnce(() => 'second date string') - .mockImplementationOnce(() => 'third date string') - .mockImplementationOnce(() => 'fourth date string') - .mockImplementationOnce(() => 'fifth date string'); - const firstState = updateState({}, false); - const secondState = updateState(firstState, true); - const thirdState = updateState(secondState, false); - const fourthState = updateState(thirdState, true); - const fifthState = updateState(fourthState, false); - expect(spy).toHaveBeenCalledTimes(5); - expect(firstState).toMatchInlineSnapshot(` - Object { - "currentTriggerStarted": undefined, - "firstCheckedAt": "first date string", - "firstTriggeredAt": undefined, - "isTriggered": false, - "lastCheckedAt": "first date string", - "lastResolvedAt": undefined, - "lastTriggeredAt": undefined, - } - `); - expect(secondState).toMatchInlineSnapshot(` - Object { - "currentTriggerStarted": "second date string", - "firstCheckedAt": "first date string", - "firstTriggeredAt": "second date string", - "isTriggered": true, - "lastCheckedAt": "second date string", - "lastResolvedAt": undefined, - "lastTriggeredAt": "second date string", - } - `); - expect(thirdState).toMatchInlineSnapshot(` - Object { - "currentTriggerStarted": undefined, - "firstCheckedAt": "first date string", - "firstTriggeredAt": "second date string", - "isTriggered": false, - "lastCheckedAt": "third date string", - "lastResolvedAt": "third date string", - "lastTriggeredAt": "second date string", - } - `); - expect(fourthState).toMatchInlineSnapshot(` - Object { - "currentTriggerStarted": "fourth date string", - "firstCheckedAt": "first date string", - "firstTriggeredAt": "second date string", - "isTriggered": true, - "lastCheckedAt": "fourth date string", - "lastResolvedAt": "third date string", - "lastTriggeredAt": "fourth date string", - } - `); - expect(fifthState).toMatchInlineSnapshot(` - Object { - "currentTriggerStarted": undefined, - "firstCheckedAt": "first date string", - "firstTriggeredAt": "second date string", - "isTriggered": false, - "lastCheckedAt": "fifth date string", - "lastResolvedAt": "fifth date string", - "lastTriggeredAt": "fourth date string", - } - `); - }); - }); - describe('uniqueMonitorIds', () => { let items: GetMonitorStatusResult[]; beforeEach(() => { diff --git a/x-pack/plugins/uptime/server/lib/alerts/__tests__/tls.test.ts b/x-pack/plugins/uptime/server/lib/alerts/__tests__/tls.test.ts new file mode 100644 index 00000000000000..2b74d608417cc2 --- /dev/null +++ b/x-pack/plugins/uptime/server/lib/alerts/__tests__/tls.test.ts @@ -0,0 +1,147 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import moment from 'moment'; +import { getCertSummary } from '../tls'; +import { Cert } from '../../../../common/runtime_types'; + +describe('tls alert', () => { + describe('getCertSummary', () => { + let mockCerts: Cert[]; + let diffSpy: jest.SpyInstance; + + beforeEach(() => { + diffSpy = jest.spyOn(moment.prototype, 'diff'); + mockCerts = [ + { + not_after: '2020-07-16T03:15:39.000Z', + not_before: '2019-07-24T03:15:39.000Z', + common_name: 'Common-One', + monitors: [{ name: 'monitor-one', id: 'monitor1' }], + sha256: 'abc', + }, + { + not_after: '2020-07-18T03:15:39.000Z', + not_before: '2019-07-20T03:15:39.000Z', + common_name: 'Common-Two', + monitors: [{ name: 'monitor-two', id: 'monitor2' }], + sha256: 'bcd', + }, + { + not_after: '2020-07-19T03:15:39.000Z', + not_before: '2019-07-22T03:15:39.000Z', + common_name: 'Common-Three', + monitors: [{ name: 'monitor-three', id: 'monitor3' }], + sha256: 'cde', + }, + { + not_after: '2020-07-25T03:15:39.000Z', + not_before: '2019-07-25T03:15:39.000Z', + common_name: 'Common-Four', + monitors: [{ name: 'monitor-four', id: 'monitor4' }], + sha256: 'def', + }, + ]; + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('sorts expiring certs appropriately when creating summary', () => { + diffSpy + .mockReturnValueOnce(900) + .mockReturnValueOnce(901) + .mockReturnValueOnce(902); + const result = getCertSummary( + mockCerts, + new Date('2020-07-20T05:00:00.000Z').valueOf(), + new Date('2019-03-01T00:00:00.000Z').valueOf() + ); + expect(result).toMatchInlineSnapshot(` + Object { + "agingCommonNameAndDate": "", + "agingCount": 0, + "count": 4, + "expiringCommonNameAndDate": "Common-One, expired on 2020-07-16T03:15:39.000Z 900 days ago.; Common-Two, expired on 2020-07-18T03:15:39.000Z 901 days ago.; Common-Three, expired on 2020-07-19T03:15:39.000Z 902 days ago.", + "expiringCount": 3, + "hasAging": null, + "hasExpired": true, + } + `); + }); + + it('sorts aging certs appropriate when creating summary', () => { + diffSpy + .mockReturnValueOnce(702) + .mockReturnValueOnce(701) + .mockReturnValueOnce(700); + const result = getCertSummary( + mockCerts, + new Date('2020-07-01T12:00:00.000Z').valueOf(), + new Date('2019-09-01T03:00:00.000Z').valueOf() + ); + expect(result).toMatchInlineSnapshot(` + Object { + "agingCommonNameAndDate": "Common-Two, valid since 2019-07-20T03:15:39.000Z, 702 days ago.; Common-Three, valid since 2019-07-22T03:15:39.000Z, 701 days ago.; Common-One, valid since 2019-07-24T03:15:39.000Z, 700 days ago.", + "agingCount": 4, + "count": 4, + "expiringCommonNameAndDate": "", + "expiringCount": 0, + "hasAging": true, + "hasExpired": null, + } + `); + }); + + it('handles negative diff values appropriately for aging certs', () => { + diffSpy + .mockReturnValueOnce(700) + .mockReturnValueOnce(-90) + .mockReturnValueOnce(-80); + const result = getCertSummary( + mockCerts, + new Date('2020-07-01T12:00:00.000Z').valueOf(), + new Date('2019-09-01T03:00:00.000Z').valueOf() + ); + expect(result).toMatchInlineSnapshot(` + Object { + "agingCommonNameAndDate": "Common-Two, valid since 2019-07-20T03:15:39.000Z, 700 days ago.; Common-Three, invalid until 2019-07-22T03:15:39.000Z, 90 days from now.; Common-One, invalid until 2019-07-24T03:15:39.000Z, 80 days from now.", + "agingCount": 4, + "count": 4, + "expiringCommonNameAndDate": "", + "expiringCount": 0, + "hasAging": true, + "hasExpired": null, + } + `); + }); + + it('handles negative diff values appropriately for expiring certs', () => { + diffSpy + // negative days are in the future, positive days are in the past + .mockReturnValueOnce(-96) + .mockReturnValueOnce(-94) + .mockReturnValueOnce(2); + const result = getCertSummary( + mockCerts, + new Date('2020-07-20T05:00:00.000Z').valueOf(), + new Date('2019-03-01T00:00:00.000Z').valueOf() + ); + expect(result).toMatchInlineSnapshot(` + Object { + "agingCommonNameAndDate": "", + "agingCount": 0, + "count": 4, + "expiringCommonNameAndDate": "Common-One, expires on 2020-07-16T03:15:39.000Z in 96 days.; Common-Two, expires on 2020-07-18T03:15:39.000Z in 94 days.; Common-Three, expired on 2020-07-19T03:15:39.000Z 2 days ago.", + "expiringCount": 3, + "hasAging": null, + "hasExpired": true, + } + `); + }); + }); +}); diff --git a/x-pack/plugins/uptime/server/lib/alerts/common.ts b/x-pack/plugins/uptime/server/lib/alerts/common.ts new file mode 100644 index 00000000000000..f33cd9052d2295 --- /dev/null +++ b/x-pack/plugins/uptime/server/lib/alerts/common.ts @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { isRight } from 'fp-ts/lib/Either'; +import { UptimeCommonState, UptimeCommonStateType } from '../../../common/runtime_types'; + +export type UpdateUptimeAlertState = ( + state: Record, + isTriggeredNow: boolean +) => UptimeCommonState; + +export const updateState: UpdateUptimeAlertState = (state, isTriggeredNow) => { + const now = new Date().toISOString(); + const decoded = UptimeCommonStateType.decode(state); + if (!isRight(decoded)) { + const triggerVal = isTriggeredNow ? now : undefined; + return { + currentTriggerStarted: triggerVal, + firstCheckedAt: now, + firstTriggeredAt: triggerVal, + isTriggered: isTriggeredNow, + lastTriggeredAt: triggerVal, + lastCheckedAt: now, + lastResolvedAt: undefined, + }; + } + const { + currentTriggerStarted, + firstCheckedAt, + firstTriggeredAt, + lastTriggeredAt, + // this is the stale trigger status, we're naming it `wasTriggered` + // to differentiate it from the `isTriggeredNow` param + isTriggered: wasTriggered, + lastResolvedAt, + } = decoded.right; + + let cts: string | undefined; + if (isTriggeredNow && !currentTriggerStarted) { + cts = now; + } else if (isTriggeredNow) { + cts = currentTriggerStarted; + } + return { + currentTriggerStarted: cts, + firstCheckedAt: firstCheckedAt ?? now, + firstTriggeredAt: isTriggeredNow && !firstTriggeredAt ? now : firstTriggeredAt, + lastCheckedAt: now, + lastTriggeredAt: isTriggeredNow ? now : lastTriggeredAt, + lastResolvedAt: !isTriggeredNow && wasTriggered ? now : lastResolvedAt, + isTriggered: isTriggeredNow, + }; +}; diff --git a/x-pack/plugins/uptime/server/lib/alerts/index.ts b/x-pack/plugins/uptime/server/lib/alerts/index.ts index 0e61fd70e0024b..661df39ece6286 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/index.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/index.ts @@ -6,5 +6,9 @@ import { UptimeAlertTypeFactory } from './types'; import { statusCheckAlertFactory } from './status_check'; +import { tlsAlertFactory } from './tls'; -export const uptimeAlertTypeFactories: UptimeAlertTypeFactory[] = [statusCheckAlertFactory]; +export const uptimeAlertTypeFactories: UptimeAlertTypeFactory[] = [ + statusCheckAlertFactory, + tlsAlertFactory, +]; diff --git a/x-pack/plugins/uptime/server/lib/alerts/status_check.ts b/x-pack/plugins/uptime/server/lib/alerts/status_check.ts index f9df559a3977ba..0eaa12e8f4372b 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/status_check.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/status_check.ts @@ -11,13 +11,11 @@ import { i18n } from '@kbn/i18n'; import { AlertExecutorOptions } from '../../../../alerting/server'; import { UptimeAlertTypeFactory } from './types'; import { GetMonitorStatusResult } from '../requests'; -import { - StatusCheckExecutorParamsType, - StatusCheckAlertStateType, - StatusCheckAlertState, -} from '../../../common/runtime_types'; +import { StatusCheckExecutorParamsType } from '../../../common/runtime_types'; import { ACTION_GROUP_DEFINITIONS } from '../../../common/constants'; import { savedObjectsAdapter } from '../saved_objects'; +import { updateState } from './common'; +import { commonStateTranslations } from './translations'; const { MONITOR_STATUS } = ACTION_GROUP_DEFINITIONS; @@ -34,7 +32,7 @@ export const uniqueMonitorIds = (items: GetMonitorStatusResult[]): Set = /** * Generates a message to include in contexts of alerts. * @param monitors the list of monitors to include in the message - * @param max + * @param max the maximum number of items the summary should contain */ export const contextMessage = (monitorIds: string[], max: number): string => { const MIN = 2; @@ -122,58 +120,11 @@ export const fullListByIdAndLocation = ( ); }; -export const updateState = ( - state: Record, - isTriggeredNow: boolean -): StatusCheckAlertState => { - const now = new Date().toISOString(); - const decoded = StatusCheckAlertStateType.decode(state); - if (!isRight(decoded)) { - const triggerVal = isTriggeredNow ? now : undefined; - return { - currentTriggerStarted: triggerVal, - firstCheckedAt: now, - firstTriggeredAt: triggerVal, - isTriggered: isTriggeredNow, - lastTriggeredAt: triggerVal, - lastCheckedAt: now, - lastResolvedAt: undefined, - }; - } - const { - currentTriggerStarted, - firstCheckedAt, - firstTriggeredAt, - lastTriggeredAt, - // this is the stale trigger status, we're naming it `wasTriggered` - // to differentiate it from the `isTriggeredNow` param - isTriggered: wasTriggered, - lastResolvedAt, - } = decoded.right; - - let cts: string | undefined; - if (isTriggeredNow && !currentTriggerStarted) { - cts = now; - } else if (isTriggeredNow) { - cts = currentTriggerStarted; - } - - return { - currentTriggerStarted: cts, - firstCheckedAt: firstCheckedAt ?? now, - firstTriggeredAt: isTriggeredNow && !firstTriggeredAt ? now : firstTriggeredAt, - lastCheckedAt: now, - lastTriggeredAt: isTriggeredNow ? now : lastTriggeredAt, - lastResolvedAt: !isTriggeredNow && wasTriggered ? now : lastResolvedAt, - isTriggered: isTriggeredNow, - }; -}; - // Right now the maximum number of monitors shown in the message is hardcoded here. // we might want to make this a parameter in the future const DEFAULT_MAX_MESSAGE_ROWS = 3; -export const statusCheckAlertFactory: UptimeAlertTypeFactory = (server, libs) => ({ +export const statusCheckAlertFactory: UptimeAlertTypeFactory = (_server, libs) => ({ id: 'xpack.uptime.alerts.monitorStatus', name: i18n.translate('xpack.uptime.alerts.monitorStatus', { defaultMessage: 'Uptime monitor status', @@ -218,72 +169,7 @@ export const statusCheckAlertFactory: UptimeAlertTypeFactory = (server, libs) => ), }, ], - state: [ - { - name: 'firstCheckedAt', - description: i18n.translate( - 'xpack.uptime.alerts.monitorStatus.actionVariables.state.firstCheckedAt', - { - defaultMessage: 'Timestamp indicating when this alert first checked', - } - ), - }, - { - name: 'firstTriggeredAt', - description: i18n.translate( - 'xpack.uptime.alerts.monitorStatus.actionVariables.state.firstTriggeredAt', - { - defaultMessage: 'Timestamp indicating when the alert first triggered', - } - ), - }, - { - name: 'currentTriggerStarted', - description: i18n.translate( - 'xpack.uptime.alerts.monitorStatus.actionVariables.state.currentTriggerStarted', - { - defaultMessage: - 'Timestamp indicating when the current trigger state began, if alert is triggered', - } - ), - }, - { - name: 'isTriggered', - description: i18n.translate( - 'xpack.uptime.alerts.monitorStatus.actionVariables.state.isTriggered', - { - defaultMessage: `Flag indicating if the alert is currently triggering`, - } - ), - }, - { - name: 'lastCheckedAt', - description: i18n.translate( - 'xpack.uptime.alerts.monitorStatus.actionVariables.state.lastCheckedAt', - { - defaultMessage: `Timestamp indicating the alert's most recent check time`, - } - ), - }, - { - name: 'lastResolvedAt', - description: i18n.translate( - 'xpack.uptime.alerts.monitorStatus.actionVariables.state.lastResolvedAt', - { - defaultMessage: `Timestamp indicating the most recent resolution time for this alert`, - } - ), - }, - { - name: 'lastTriggeredAt', - description: i18n.translate( - 'xpack.uptime.alerts.monitorStatus.actionVariables.state.lastTriggeredAt', - { - defaultMessage: `Timestamp indicating the alert's most recent trigger time`, - } - ), - }, - ], + state: [...commonStateTranslations], }, async executor(options: AlertExecutorOptions) { const { params: rawParams } = options; diff --git a/x-pack/plugins/uptime/server/lib/alerts/tls.ts b/x-pack/plugins/uptime/server/lib/alerts/tls.ts new file mode 100644 index 00000000000000..518e3ed93b4249 --- /dev/null +++ b/x-pack/plugins/uptime/server/lib/alerts/tls.ts @@ -0,0 +1,152 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import moment from 'moment'; +import { schema } from '@kbn/config-schema'; +import { UptimeAlertTypeFactory } from './types'; +import { savedObjectsAdapter } from '../saved_objects'; +import { updateState } from './common'; +import { ACTION_GROUP_DEFINITIONS, DYNAMIC_SETTINGS_DEFAULTS } from '../../../common/constants'; +import { Cert, CertResult } from '../../../common/runtime_types'; +import { commonStateTranslations, tlsTranslations } from './translations'; + +const { TLS } = ACTION_GROUP_DEFINITIONS; + +const DEFAULT_FROM = 'now-1d'; +const DEFAULT_TO = 'now'; +const DEFAULT_INDEX = 0; +const DEFAULT_SIZE = 20; + +interface TlsAlertState { + count: number; + agingCount: number; + agingCommonNameAndDate: string; + expiringCount: number; + expiringCommonNameAndDate: string; + hasAging: true | null; + hasExpired: true | null; +} + +const sortCerts = (a: string, b: string) => new Date(a).valueOf() - new Date(b).valueOf(); + +const mapCertsToSummaryString = ( + certs: Cert[], + certLimitMessage: (cert: Cert) => string, + maxSummaryItems: number +): string => + certs + .slice(0, maxSummaryItems) + .map(cert => `${cert.common_name}, ${certLimitMessage(cert)}`) + .reduce((prev, cur) => (prev === '' ? cur : prev.concat(`; ${cur}`)), ''); + +const getValidAfter = ({ not_after: date }: Cert) => { + if (!date) return 'Error, missing `certificate_not_valid_after` date.'; + const relativeDate = moment().diff(date, 'days'); + return relativeDate >= 0 + ? tlsTranslations.validAfterExpiredString(date, relativeDate) + : tlsTranslations.validAfterExpiringString(date, Math.abs(relativeDate)); +}; + +const getValidBefore = ({ not_before: date }: Cert): string => { + if (!date) return 'Error, missing `certificate_not_valid_before` date.'; + const relativeDate = moment().diff(date, 'days'); + return relativeDate >= 0 + ? tlsTranslations.validBeforeExpiredString(date, relativeDate) + : tlsTranslations.validBeforeExpiringString(date, Math.abs(relativeDate)); +}; + +export const getCertSummary = ( + certs: Cert[], + expirationThreshold: number, + ageThreshold: number, + maxSummaryItems: number = 3 +): TlsAlertState => { + certs.sort((a, b) => sortCerts(a.not_after ?? '', b.not_after ?? '')); + const expiring = certs.filter( + cert => new Date(cert.not_after ?? '').valueOf() < expirationThreshold + ); + + certs.sort((a, b) => sortCerts(a.not_before ?? '', b.not_before ?? '')); + const aging = certs.filter(cert => new Date(cert.not_before ?? '').valueOf() < ageThreshold); + + return { + count: certs.length, + agingCount: aging.length, + agingCommonNameAndDate: mapCertsToSummaryString(aging, getValidBefore, maxSummaryItems), + expiringCommonNameAndDate: mapCertsToSummaryString(expiring, getValidAfter, maxSummaryItems), + expiringCount: expiring.length, + hasAging: aging.length > 0 ? true : null, + hasExpired: expiring.length > 0 ? true : null, + }; +}; + +export const tlsAlertFactory: UptimeAlertTypeFactory = (_server, libs) => ({ + id: 'xpack.uptime.alerts.tls', + name: tlsTranslations.alertFactoryName, + validate: { + params: schema.object({}), + }, + defaultActionGroupId: TLS.id, + actionGroups: [ + { + id: TLS.id, + name: TLS.name, + }, + ], + actionVariables: { + context: [], + state: [...tlsTranslations.actionVariables, ...commonStateTranslations], + }, + async executor(options) { + const { + services: { alertInstanceFactory, callCluster, savedObjectsClient }, + state, + } = options; + const dynamicSettings = await savedObjectsAdapter.getUptimeDynamicSettings(savedObjectsClient); + + const { certs, total }: CertResult = await libs.requests.getCerts({ + callES: callCluster, + dynamicSettings, + from: DEFAULT_FROM, + to: DEFAULT_TO, + index: DEFAULT_INDEX, + size: DEFAULT_SIZE, + notValidAfter: `now+${dynamicSettings.certThresholds?.expiration ?? + DYNAMIC_SETTINGS_DEFAULTS.certThresholds?.expiration}d`, + notValidBefore: `now-${dynamicSettings.certThresholds?.age ?? + DYNAMIC_SETTINGS_DEFAULTS.certThresholds?.age}d`, + sortBy: 'common_name', + direction: 'desc', + }); + + const foundCerts = total > 0; + + if (foundCerts) { + const absoluteExpirationThreshold = moment() + .add( + dynamicSettings.certThresholds?.expiration ?? + DYNAMIC_SETTINGS_DEFAULTS.certThresholds?.expiration, + 'd' + ) + .valueOf(); + const absoluteAgeThreshold = moment() + .subtract( + dynamicSettings.certThresholds?.age ?? DYNAMIC_SETTINGS_DEFAULTS.certThresholds?.age, + 'd' + ) + .valueOf(); + const alertInstance = alertInstanceFactory(TLS.id); + const summary = getCertSummary(certs, absoluteExpirationThreshold, absoluteAgeThreshold); + alertInstance.replaceState({ + ...updateState(state, foundCerts), + ...summary, + }); + alertInstance.scheduleActions(TLS.id); + } + + return updateState(state, foundCerts); + }, +}); diff --git a/x-pack/plugins/uptime/server/lib/alerts/translations.ts b/x-pack/plugins/uptime/server/lib/alerts/translations.ts new file mode 100644 index 00000000000000..e41930aad5af0b --- /dev/null +++ b/x-pack/plugins/uptime/server/lib/alerts/translations.ts @@ -0,0 +1,150 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const commonStateTranslations = [ + { + name: 'firstCheckedAt', + description: i18n.translate( + 'xpack.uptime.alerts.monitorStatus.actionVariables.state.firstCheckedAt', + { + defaultMessage: 'Timestamp indicating when this alert first checked', + } + ), + }, + { + name: 'firstTriggeredAt', + description: i18n.translate( + 'xpack.uptime.alerts.monitorStatus.actionVariables.state.firstTriggeredAt', + { + defaultMessage: 'Timestamp indicating when the alert first triggered', + } + ), + }, + { + name: 'currentTriggerStarted', + description: i18n.translate( + 'xpack.uptime.alerts.monitorStatus.actionVariables.state.currentTriggerStarted', + { + defaultMessage: + 'Timestamp indicating when the current trigger state began, if alert is triggered', + } + ), + }, + { + name: 'isTriggered', + description: i18n.translate( + 'xpack.uptime.alerts.monitorStatus.actionVariables.state.isTriggered', + { + defaultMessage: `Flag indicating if the alert is currently triggering`, + } + ), + }, + { + name: 'lastCheckedAt', + description: i18n.translate( + 'xpack.uptime.alerts.monitorStatus.actionVariables.state.lastCheckedAt', + { + defaultMessage: `Timestamp indicating the alert's most recent check time`, + } + ), + }, + { + name: 'lastResolvedAt', + description: i18n.translate( + 'xpack.uptime.alerts.monitorStatus.actionVariables.state.lastResolvedAt', + { + defaultMessage: `Timestamp indicating the most recent resolution time for this alert`, + } + ), + }, + { + name: 'lastTriggeredAt', + description: i18n.translate( + 'xpack.uptime.alerts.monitorStatus.actionVariables.state.lastTriggeredAt', + { + defaultMessage: `Timestamp indicating the alert's most recent trigger time`, + } + ), + }, +]; + +export const tlsTranslations = { + alertFactoryName: i18n.translate('xpack.uptime.alerts.tls', { + defaultMessage: 'Uptime TLS', + }), + actionVariables: [ + { + name: 'count', + description: i18n.translate('xpack.uptime.alerts.tls.actionVariables.state.count', { + defaultMessage: 'The number of certs detected by the alert executor', + }), + }, + { + name: 'expiringCount', + description: i18n.translate('xpack.uptime.alerts.tls.actionVariables.state.expiringCount', { + defaultMessage: 'The number of expiring certs detected by the alert.', + }), + }, + { + name: 'expiringCommonNameAndDate', + description: i18n.translate( + 'xpack.uptime.alerts.tls.actionVariables.state.expiringCommonNameAndDate', + { + defaultMessage: 'The common names and expiration date/time of the detected certs', + } + ), + }, + { + name: 'agingCount', + description: i18n.translate('xpack.uptime.alerts.tls.actionVariables.state.agingCount', { + defaultMessage: 'The number of detected certs that are becoming too old.', + }), + }, + { + name: 'agingCommonNameAndDate', + description: i18n.translate( + 'xpack.uptime.alerts.tls.actionVariables.state.agingCommonNameAndDate', + { + defaultMessage: 'The common names and expiration date/time of the detected certs.', + } + ), + }, + ], + validAfterExpiredString: (date: string, relativeDate: number) => + i18n.translate('xpack.uptime.alerts.tls.validAfterExpiredString', { + defaultMessage: `expired on {date} {relativeDate} days ago.`, + values: { + date, + relativeDate, + }, + }), + validAfterExpiringString: (date: string, relativeDate: number) => + i18n.translate('xpack.uptime.alerts.tls.validAfterExpiringString', { + defaultMessage: `expires on {date} in {relativeDate} days.`, + values: { + date, + relativeDate, + }, + }), + validBeforeExpiredString: (date: string, relativeDate: number) => + i18n.translate('xpack.uptime.alerts.tls.validBeforeExpiredString', { + defaultMessage: 'valid since {date}, {relativeDate} days ago.', + values: { + date, + relativeDate, + }, + }), + validBeforeExpiringString: (date: string, relativeDate: number) => + i18n.translate('xpack.uptime.alerts.tls.validBeforeExpiringString', { + defaultMessage: 'invalid until {date}, {relativeDate} days from now.', + values: { + date, + relativeDate, + }, + }), +}; diff --git a/x-pack/plugins/uptime/server/lib/requests/get_certs.ts b/x-pack/plugins/uptime/server/lib/requests/get_certs.ts index 6820cd69376d18..57a59936ddf7c6 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_certs.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_certs.ts @@ -20,8 +20,10 @@ export const getCerts: UMElasticsearchQueryFn = asyn index, from, to, - search, size, + search, + notValidBefore, + notValidAfter, sortBy, direction, }) => { @@ -91,6 +93,10 @@ export const getCerts: UMElasticsearchQueryFn = asyn }, }; + if (!params.body.query.bool.should) { + params.body.query.bool.should = []; + } + if (search) { params.body.query.bool.minimum_should_match = 1; params.body.query.bool.should = [ @@ -110,6 +116,35 @@ export const getCerts: UMElasticsearchQueryFn = asyn ]; } + if (notValidBefore || notValidAfter) { + const validityFilters: any = { + bool: { + should: [], + }, + }; + if (notValidBefore) { + validityFilters.bool.should.push({ + range: { + 'tls.certificate_not_valid_before': { + lte: notValidBefore, + }, + }, + }); + } + if (notValidAfter) { + validityFilters.bool.should.push({ + range: { + 'tls.certificate_not_valid_after': { + lte: notValidAfter, + }, + }, + }); + } + + params.body.query.bool.filter.push(validityFilters); + } + + // console.log(JSON.stringify(params, null, 2)); const result = await callES('search', params); const certs = (result?.hits?.hits ?? []).map((hit: any) => { diff --git a/x-pack/run_functional_tests.sh b/x-pack/run_functional_tests.sh new file mode 100755 index 00000000000000..e94f283ea03942 --- /dev/null +++ b/x-pack/run_functional_tests.sh @@ -0,0 +1,3 @@ +export TEST_KIBANA_URL="http://elastic:mlqa_admin@localhost:5601" +export TEST_ES_URL="http://elastic:mlqa_admin@localhost:9200" +node ../scripts/functional_test_runner --include-tag walterra diff --git a/x-pack/test/api_integration/apis/endpoint/index.ts b/x-pack/test/api_integration/apis/endpoint/index.ts index 0a5f9aa595b8a3..0c7c03da6c5942 100644 --- a/x-pack/test/api_integration/apis/endpoint/index.ts +++ b/x-pack/test/api_integration/apis/endpoint/index.ts @@ -20,5 +20,6 @@ export default function endpointAPIIntegrationTests({ loadTestFile(require.resolve('./resolver')); loadTestFile(require.resolve('./metadata')); loadTestFile(require.resolve('./alerts')); + loadTestFile(require.resolve('./policy')); }); } diff --git a/x-pack/test/api_integration/apis/endpoint/policy.ts b/x-pack/test/api_integration/apis/endpoint/policy.ts new file mode 100644 index 00000000000000..76e4fe1a00ca46 --- /dev/null +++ b/x-pack/test/api_integration/apis/endpoint/policy.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect/expect.js'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function({ getService }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const supertest = getService('supertest'); + describe('Endpoint policy api', () => { + describe('GET /api/endpoint/policy_response', () => { + before(async () => await esArchiver.load('endpoint/policy')); + + after(async () => await esArchiver.unload('endpoint/policy')); + + it('should return one policy response for host', async () => { + const expectedHostId = '4f3b9858-a96d-49d8-a326-230d7763d767'; + const { body } = await supertest + .get(`/api/endpoint/policy_response?hostId=${expectedHostId}`) + .send() + .expect(200); + + expect(body.policy_response.host.id).to.eql(expectedHostId); + expect(body.policy_response.endpoint.policy).to.not.be(undefined); + }); + + it('should return not found if host has no policy response', async () => { + const { body } = await supertest + .get(`/api/endpoint/policy_response?hostId=bad_host_id`) + .send() + .expect(404); + + expect(body.message).to.contain('Policy Response Not Found'); + }); + }); + }); +} diff --git a/x-pack/test/api_integration/apis/fleet/agents/acks.ts b/x-pack/test/api_integration/apis/fleet/agents/acks.ts index adde6dd184b817..084f2b7a656dc3 100644 --- a/x-pack/test/api_integration/apis/fleet/agents/acks.ts +++ b/x-pack/test/api_integration/apis/fleet/agents/acks.ts @@ -106,7 +106,7 @@ export default function(providerContext: FtrProviderContext) { item.action_id === '48cebde1-c906-4893-b89f-595d943b72a2' ); expect(expectedEvents.length).to.eql(2); - const expectedEvent = expectedEvents.find( + const { id, ...expectedEvent } = expectedEvents.find( (item: Record) => item.action_id === '48cebde1-c906-4893-b89f-595d943b72a1' ); expect(expectedEvent).to.eql({ diff --git a/x-pack/test/api_integration/apis/ml/modules/setup_module.ts b/x-pack/test/api_integration/apis/ml/modules/setup_module.ts index c42fc95c1bc7f2..39c87a91f0ccf6 100644 --- a/x-pack/test/api_integration/apis/ml/modules/setup_module.ts +++ b/x-pack/test/api_integration/apis/ml/modules/setup_module.ts @@ -9,6 +9,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; import { JOB_STATE, DATAFEED_STATE } from '../../../../../plugins/ml/common/constants/states'; +import { Job } from '../../../../../plugins/ml/common/types/anomaly_detection_jobs'; import { USER } from '../../../../functional/services/machine_learning/security_common'; const COMMON_HEADERS = { @@ -23,7 +24,8 @@ export default ({ getService }: FtrProviderContext) => { const testDataListPositive = [ { - testTitleSuffix: 'for sample logs dataset with prefix and startDatafeed false', + testTitleSuffix: + 'for sample logs dataset with prefix, startDatafeed false and estimateModelMemory false', sourceDataArchive: 'ml/sample_logs', indexPattern: { name: 'kibana_sample_data_logs', timeField: '@timestamp' }, module: 'sample_data_weblogs', @@ -32,6 +34,7 @@ export default ({ getService }: FtrProviderContext) => { prefix: 'pf1_', indexPatternName: 'kibana_sample_data_logs', startDatafeed: false, + estimateModelMemory: false, }, expected: { responseCode: 200, @@ -40,16 +43,55 @@ export default ({ getService }: FtrProviderContext) => { jobId: 'pf1_low_request_rate', jobState: JOB_STATE.CLOSED, datafeedState: DATAFEED_STATE.STOPPED, + modelMemoryLimit: '10mb', }, { jobId: 'pf1_response_code_rates', jobState: JOB_STATE.CLOSED, datafeedState: DATAFEED_STATE.STOPPED, + modelMemoryLimit: '10mb', }, { jobId: 'pf1_url_scanning', jobState: JOB_STATE.CLOSED, datafeedState: DATAFEED_STATE.STOPPED, + modelMemoryLimit: '10mb', + }, + ], + }, + }, + { + testTitleSuffix: + 'for sample logs dataset with prefix, startDatafeed false and estimateModelMemory true', + sourceDataArchive: 'ml/sample_logs', + indexPattern: { name: 'kibana_sample_data_logs', timeField: '@timestamp' }, + module: 'sample_data_weblogs', + user: USER.ML_POWERUSER, + requestBody: { + prefix: 'pf2_', + indexPatternName: 'kibana_sample_data_logs', + startDatafeed: false, + }, + expected: { + responseCode: 200, + jobs: [ + { + jobId: 'pf2_low_request_rate', + jobState: JOB_STATE.CLOSED, + datafeedState: DATAFEED_STATE.STOPPED, + modelMemoryLimit: '11mb', + }, + { + jobId: 'pf2_response_code_rates', + jobState: JOB_STATE.CLOSED, + datafeedState: DATAFEED_STATE.STOPPED, + modelMemoryLimit: '11mb', + }, + { + jobId: 'pf2_url_scanning', + jobState: JOB_STATE.CLOSED, + datafeedState: DATAFEED_STATE.STOPPED, + modelMemoryLimit: '16mb', }, ], }, @@ -197,6 +239,36 @@ export default ({ getService }: FtrProviderContext) => { await ml.api.waitForJobState(job.jobId, job.jobState); await ml.api.waitForDatafeedState(datafeedId, job.datafeedState); } + + // compare model memory limits for created jobs + const expectedModelMemoryLimits = testData.expected.jobs + .map(j => ({ + id: j.jobId, + modelMemoryLimit: j.modelMemoryLimit, + })) + .sort(compareById); + + const { + body: { jobs }, + }: { + body: { + jobs: Job[]; + }; + } = await ml.api.getAnomalyDetectionJob(testData.expected.jobs.map(j => j.jobId).join()); + + const actualModelMemoryLimits = jobs + .map(j => ({ + id: j.job_id, + modelMemoryLimit: j.analysis_limits!.model_memory_limit, + })) + .sort(compareById); + + expect(actualModelMemoryLimits).to.eql( + expectedModelMemoryLimits, + `Expected job model memory limits '${JSON.stringify( + expectedModelMemoryLimits + )}' (got '${JSON.stringify(actualModelMemoryLimits)}')` + ); }); // TODO in future updates: add creation validations for created saved objects diff --git a/x-pack/test/api_integration/apis/uptime/rest/index.ts b/x-pack/test/api_integration/apis/uptime/rest/index.ts index f77c14e960ab23..2084c1a572058e 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/index.ts +++ b/x-pack/test/api_integration/apis/uptime/rest/index.ts @@ -46,6 +46,7 @@ export default function({ getService, loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./monitor_states_generated')); loadTestFile(require.resolve('./telemetry_collectors')); }); + describe('with real-world data', () => { beforeEach('load heartbeat data', async () => await esArchiver.load('uptime/full_heartbeat')); afterEach('unload', async () => await esArchiver.unload('uptime/full_heartbeat')); diff --git a/x-pack/test/functional/apps/infra/home_page.ts b/x-pack/test/functional/apps/infra/home_page.ts index ed8bec570ab60a..28d7956e353e14 100644 --- a/x-pack/test/functional/apps/infra/home_page.ts +++ b/x-pack/test/functional/apps/infra/home_page.ts @@ -33,6 +33,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { before(async () => { await esArchiver.load('infra/metrics_and_logs'); await pageObjects.common.navigateToApp('infraOps'); + await pageObjects.infraHome.waitForLoading(); }); after(async () => await esArchiver.unload('infra/metrics_and_logs')); diff --git a/x-pack/test/functional/apps/transform/creation_index_pattern.ts b/x-pack/test/functional/apps/transform/creation_index_pattern.ts index 0e61635fb70e4f..150b2a9a15147b 100644 --- a/x-pack/test/functional/apps/transform/creation_index_pattern.ts +++ b/x-pack/test/functional/apps/transform/creation_index_pattern.ts @@ -58,6 +58,7 @@ export default function({ getService }: FtrProviderContext) { return `user-${this.transformId}`; }, expected: { + pivotAdvancedEditorValueArr: ['{', ' "group_by": {', ' "category.keyword": {'], pivotAdvancedEditorValue: { group_by: { 'category.keyword': { @@ -117,6 +118,7 @@ export default function({ getService }: FtrProviderContext) { return `user-${this.transformId}`; }, expected: { + pivotAdvancedEditorValueArr: ['{', ' "group_by": {', ' "geoip.country_iso_code": {'], pivotAdvancedEditorValue: { group_by: { 'geoip.country_iso_code': { @@ -233,7 +235,7 @@ export default function({ getService }: FtrProviderContext) { it('displays the advanced configuration', async () => { await transform.wizard.enabledAdvancedPivotEditor(); await transform.wizard.assertAdvancedPivotEditorContent( - testData.expected.pivotAdvancedEditorValue + testData.expected.pivotAdvancedEditorValueArr ); }); diff --git a/x-pack/test/functional/apps/uptime/ml_anomaly.ts b/x-pack/test/functional/apps/uptime/ml_anomaly.ts index bcd165cc1afb78..c9668bed432b50 100644 --- a/x-pack/test/functional/apps/uptime/ml_anomaly.ts +++ b/x-pack/test/functional/apps/uptime/ml_anomaly.ts @@ -9,6 +9,8 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default ({ getService }: FtrProviderContext) => { const uptime = getService('uptime'); const log = getService('log'); + const esArchiver = getService('esArchiver'); + const archive = 'uptime/full_heartbeat'; describe('uptime ml anomaly', function() { this.tags(['skipFirefox']); @@ -17,6 +19,7 @@ export default ({ getService }: FtrProviderContext) => { const monitorId = '0000-intermittent'; before(async () => { + await esArchiver.loadIfNeeded(archive); if (!(await uptime.navigation.checkIfOnMonitorPage(monitorId))) { await uptime.navigation.loadDataAndGoToMonitorPage(dateStart, dateEnd, monitorId); } diff --git a/x-pack/test/functional/es_archives/endpoint/policy/data.json.gz b/x-pack/test/functional/es_archives/endpoint/policy/data.json.gz new file mode 100644 index 00000000000000..2fab424d27cad6 Binary files /dev/null and b/x-pack/test/functional/es_archives/endpoint/policy/data.json.gz differ diff --git a/x-pack/test/functional/es_archives/endpoint/policy/mappings.json b/x-pack/test/functional/es_archives/endpoint/policy/mappings.json new file mode 100644 index 00000000000000..4a9b0bdd11f0be --- /dev/null +++ b/x-pack/test/functional/es_archives/endpoint/policy/mappings.json @@ -0,0 +1,512 @@ +{ + "type": "index", + "value": { + "aliases": { + }, + "index": "metrics-endpoint.policy-default-1", + "mappings": { + "_meta": { + "version": "1.6.0-dev" + }, + "date_detection": false, + "dynamic_templates": [ + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "agent": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "elastic": { + "properties": { + "agent": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "endpoint": { + "properties": { + "policy": { + "properties": { + "applied": { + "properties": { + "actions": { + "properties": { + "configure_elasticsearch_connection": { + "properties": { + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "configure_kernel": { + "properties": { + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "configure_logging": { + "properties": { + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "configure_malware": { + "properties": { + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "connect_kernel": { + "properties": { + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "detect_file_open_events": { + "properties": { + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "detect_file_write_events": { + "properties": { + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "detect_image_load_events": { + "properties": { + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "detect_process_events": { + "properties": { + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "download_global_artifacts": { + "properties": { + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "download_model": { + "properties": { + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ingest_events_config": { + "properties": { + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "load_config": { + "properties": { + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "load_malware_model": { + "properties": { + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "read_elasticsearch_config": { + "properties": { + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "read_events_config": { + "properties": { + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "read_kernel_config": { + "properties": { + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "read_logging_config": { + "properties": { + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "read_malware_config": { + "properties": { + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "workflow": { + "properties": { + "message": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "configurations": { + "properties": { + "events": { + "properties": { + "concerned_actions": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "logging": { + "properties": { + "concerned_actions": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "malware": { + "properties": { + "concerned_actions": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "streaming": { + "properties": { + "concerned_actions": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "policy": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "response": { + "properties": { + "configurations": { + "properties": { + "events": { + "properties": { + "concerned_actions": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "logging": { + "properties": { + "concerned_actions": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "malware": { + "properties": { + "concerned_actions": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "streaming": { + "properties": { + "concerned_actions": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "event": { + "properties": { + "created": { + "type": "date" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "kind": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "stream": { + "properties": { + "dataset": { + "type": "constant_keyword" + }, + "namespace": { + "type": "constant_keyword" + }, + "type": { + "type": "constant_keyword" + } + } + } + } + }, + "settings": { + "index": { + "codec": "best_compression", + "lifecycle": { + "name": "metrics-default" + }, + "mapping": { + "total_fields": { + "limit": "10000" + } + }, + "number_of_replicas": "1", + "number_of_shards": "1", + "prefer_v2_templates": "true", + "query": { + "default_field": [ + "message" + ] + }, + "refresh_interval": "5s" + } + } + } +} diff --git a/x-pack/test/functional/page_objects/infra_home_page.ts b/x-pack/test/functional/page_objects/infra_home_page.ts index 998a60500aca29..51dad594f21f57 100644 --- a/x-pack/test/functional/page_objects/infra_home_page.ts +++ b/x-pack/test/functional/page_objects/infra_home_page.ts @@ -74,5 +74,9 @@ export function InfraHomePageProvider({ getService }: FtrProviderContext) { await testSubjects.click('configureSourceButton'); await testSubjects.exists('sourceConfigurationFlyout'); }, + + async waitForLoading() { + await testSubjects.missingOrFail('loadingMessage', { timeout: 20000 }); + }, }; } diff --git a/x-pack/test/functional/page_objects/uptime_page.ts b/x-pack/test/functional/page_objects/uptime_page.ts index 53c89eadeced74..1155b6a5cb2968 100644 --- a/x-pack/test/functional/page_objects/uptime_page.ts +++ b/x-pack/test/functional/page_objects/uptime_page.ts @@ -9,7 +9,7 @@ import { FtrProviderContext } from '../ftr_provider_context'; export function UptimePageProvider({ getPageObjects, getService }: FtrProviderContext) { const pageObjects = getPageObjects(['common', 'timePicker']); - const { alerts, common: commonService, monitor, navigation } = getService('uptime'); + const { common: commonService, monitor, navigation } = getService('uptime'); const retry = getService('retry'); return new (class UptimePage { @@ -97,42 +97,9 @@ export function UptimePageProvider({ getPageObjects, getService }: FtrProviderCo return await commonService.getSnapshotCount(); } - public async openAlertFlyoutAndCreateMonitorStatusAlert({ - alertInterval, - alertName, - alertNumTimes, - alertTags, - alertThrottleInterval, - alertTimerangeSelection, - alertType, - filters, - }: { - alertName: string; - alertTags: string[]; - alertInterval: string; - alertThrottleInterval: string; - alertNumTimes: string; - alertTimerangeSelection: string; - alertType?: string; - filters?: string; - }) { + public async setAlertKueryBarText(filters: string) { const { setKueryBarText } = commonService; - await alerts.openFlyout(); - if (alertType) { - await alerts.openMonitorStatusAlertType(alertType); - } - await alerts.setAlertName(alertName); - await alerts.setAlertTags(alertTags); - await alerts.setAlertInterval(alertInterval); - await alerts.setAlertThrottleInterval(alertThrottleInterval); - if (filters) { - await setKueryBarText('xpack.uptime.alerts.monitorStatus.filterBar', filters); - } - await alerts.setAlertStatusNumTimes(alertNumTimes); - await alerts.setAlertTimerangeSelection(alertTimerangeSelection); - await alerts.setMonitorStatusSelectableToHours(); - await alerts.setLocationsSelectable(); - await alerts.clickSaveAlertButtion(); + await setKueryBarText('xpack.uptime.alerts.monitorStatus.filterBar', filters); } public async setMonitorListPageSize(size: number): Promise { diff --git a/x-pack/test/functional/services/transform_ui/wizard.ts b/x-pack/test/functional/services/transform_ui/wizard.ts index e63af493438d67..4b136746eb525e 100644 --- a/x-pack/test/functional/services/transform_ui/wizard.ts +++ b/x-pack/test/functional/services/transform_ui/wizard.ts @@ -274,10 +274,15 @@ export function TransformWizardProvider({ getService }: FtrProviderContext) { await this.assertAggregationEntryExists(index, expectedLabel); }, - async assertAdvancedPivotEditorContent(expectedValue: Record) { + async assertAdvancedPivotEditorContent(expectedValue: string[]) { const advancedEditorString = await aceEditor.getValue('transformAdvancedPivotEditor'); - const advancedEditorValue = JSON.parse(advancedEditorString); - expect(advancedEditorValue).to.eql(expectedValue); + // Not all lines may be visible in the editor and thus aceEditor may not return all lines. + // This means we might not get back valid JSON so we only test against the first few lines + // and see if the string matches. + + // const advancedEditorValue = JSON.parse(advancedEditorString); + // expect(advancedEditorValue).to.eql(expectedValue); + expect(advancedEditorString.split('\n').splice(0, 3)).to.eql(expectedValue); }, async assertAdvancedPivotEditorSwitchExists() { diff --git a/x-pack/test/functional/services/uptime/alerts.ts b/x-pack/test/functional/services/uptime/alerts.ts index 3a8193ff3d3278..dc10fcccaa6cef 100644 --- a/x-pack/test/functional/services/uptime/alerts.ts +++ b/x-pack/test/functional/services/uptime/alerts.ts @@ -13,6 +13,7 @@ export function UptimeAlertsProvider({ getService }: FtrProviderContext) { return { async openFlyout() { await testSubjects.click('xpack.uptime.alertsPopover.toggleButton', 5000); + await testSubjects.click('xpack.uptime.openAlertContextPanel', 5000); await testSubjects.click('xpack.uptime.toggleAlertFlyout', 5000); }, async openMonitorStatusAlertType(alertType: string) { @@ -76,19 +77,37 @@ export function UptimeAlertsProvider({ getService }: FtrProviderContext) { ['xpack.uptime.alerts.monitorStatus.timerangeUnitSelectable.hoursOption'] ); }, - async setLocationsSelectable() { - await testSubjects.click( - 'xpack.uptime.alerts.monitorStatus.locationsSelectionExpression', - 5000 - ); - await testSubjects.click('xpack.uptime.alerts.monitorStatus.locationsSelectionSwitch', 5000); - await testSubjects.click( - 'xpack.uptime.alerts.monitorStatus.locationsSelectionSelectable', - 5000 - ); + async clickAddFilter() { + await testSubjects.click('uptimeCreateAlertAddFilter'); + }, + async clickAddFilterLocation() { + await this.clickAddFilter(); + await testSubjects.click('uptimeAlertAddFilter.observer.geo.name'); + }, + async clickAddFilterPort() { + await this.clickAddFilter(); + await testSubjects.click('uptimeAlertAddFilter.url.port'); + }, + async clickAddFilterType() { + await this.clickAddFilter(); + await testSubjects.click('uptimeAlertAddFilter.monitor.type'); + }, + async clickLocationExpression(filter: string) { + await testSubjects.click('uptimeCreateStatusAlert.filter_location'); + await testSubjects.click(`filter-popover-item_${filter}`); + return browser.pressKeys(browser.keys.ESCAPE); + }, + async clickPortExpression(filter: string) { + await testSubjects.click('uptimeCreateStatusAlert.filter_port'); + await testSubjects.click(`filter-popover-item_${filter}`); + return browser.pressKeys(browser.keys.ESCAPE); + }, + async clickTypeExpression(filter: string) { + await testSubjects.click('uptimeCreateStatusAlert.filter_scheme'); + await testSubjects.click(`filter-popover-item_${filter}`); return browser.pressKeys(browser.keys.ESCAPE); }, - async clickSaveAlertButtion() { + async clickSaveAlertButton() { return testSubjects.click('saveAlertButton'); }, }; diff --git a/x-pack/test/functional/services/uptime/ml_anomaly.ts b/x-pack/test/functional/services/uptime/ml_anomaly.ts index e15f47ddd9709a..a5f138b7a5716c 100644 --- a/x-pack/test/functional/services/uptime/ml_anomaly.ts +++ b/x-pack/test/functional/services/uptime/ml_anomaly.ts @@ -32,7 +32,7 @@ export function UptimeMLAnomalyProvider({ getService }: FtrProviderContext) { async createMLJob() { await testSubjects.click('uptimeMLCreateJobBtn'); - return retry.tryForTime(10000, async () => { + return retry.tryForTime(30000, async () => { await testSubjects.existOrFail('uptimeMLJobSuccessfullyCreated'); log.info('Job successfully created'); }); diff --git a/x-pack/test/functional/services/uptime/navigation.ts b/x-pack/test/functional/services/uptime/navigation.ts index c17fa3a5f63395..13d3cc62183bda 100644 --- a/x-pack/test/functional/services/uptime/navigation.ts +++ b/x-pack/test/functional/services/uptime/navigation.ts @@ -65,8 +65,8 @@ export function UptimeNavigationProvider({ getService, getPageObjects }: FtrProv }, goToCertificates: async () => { + await testSubjects.click('uptimeCertificatesLink'); return retry.tryForTime(30 * 1000, async () => { - await testSubjects.click('uptimeCertificatesLink'); await testSubjects.existOrFail('uptimeCertificatesPage'); }); }, diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts index 1facc05bc186d2..88d52578f692ed 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts @@ -56,7 +56,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { { name: connectorName, actionType: 'Slack', - referencedByCount: '0', }, ]); }); @@ -100,7 +99,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { { name: updatedConnectorName, actionType: 'Slack', - referencedByCount: '0', }, ]); }); diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts index 7970c9b24427e1..9d547876d9b3b5 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts @@ -75,29 +75,28 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const alertType = await pageObjects.alertDetailsUI.getAlertType(); expect(alertType).to.be(`Always Firing`); - const { actionType, actionCount } = await pageObjects.alertDetailsUI.getActionsLabels(); + const { actionType } = await pageObjects.alertDetailsUI.getActionsLabels(); expect(actionType).to.be(`Slack`); - expect(actionCount).to.be(`+1`); }); it('should disable the alert', async () => { - const enableSwitch = await testSubjects.find('enableSwitch'); + const disableSwitch = await testSubjects.find('disableSwitch'); - const isChecked = await enableSwitch.getAttribute('aria-checked'); - expect(isChecked).to.eql('true'); + const isChecked = await disableSwitch.getAttribute('aria-checked'); + expect(isChecked).to.eql('false'); - await enableSwitch.click(); + await disableSwitch.click(); - const enabledSwitchAfterDisabling = await testSubjects.find('enableSwitch'); - const isCheckedAfterDisabling = await enabledSwitchAfterDisabling.getAttribute( + const disableSwitchAfterDisabling = await testSubjects.find('disableSwitch'); + const isCheckedAfterDisabling = await disableSwitchAfterDisabling.getAttribute( 'aria-checked' ); - expect(isCheckedAfterDisabling).to.eql('false'); + expect(isCheckedAfterDisabling).to.eql('true'); }); it('shouldnt allow you to mute a disabled alert', async () => { - const disabledEnableSwitch = await testSubjects.find('enableSwitch'); - expect(await disabledEnableSwitch.getAttribute('aria-checked')).to.eql('false'); + const disabledDisableSwitch = await testSubjects.find('disableSwitch'); + expect(await disabledDisableSwitch.getAttribute('aria-checked')).to.eql('true'); const muteSwitch = await testSubjects.find('muteSwitch'); expect(await muteSwitch.getAttribute('aria-checked')).to.eql('false'); @@ -112,18 +111,18 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); it('should reenable a disabled the alert', async () => { - const enableSwitch = await testSubjects.find('enableSwitch'); + const disableSwitch = await testSubjects.find('disableSwitch'); - const isChecked = await enableSwitch.getAttribute('aria-checked'); - expect(isChecked).to.eql('false'); + const isChecked = await disableSwitch.getAttribute('aria-checked'); + expect(isChecked).to.eql('true'); - await enableSwitch.click(); + await disableSwitch.click(); - const enabledSwitchAfterReenabling = await testSubjects.find('enableSwitch'); - const isCheckedAfterDisabling = await enabledSwitchAfterReenabling.getAttribute( + const disableSwitchAfterReenabling = await testSubjects.find('disableSwitch'); + const isCheckedAfterDisabling = await disableSwitchAfterReenabling.getAttribute( 'aria-checked' ); - expect(isCheckedAfterDisabling).to.eql('true'); + expect(isCheckedAfterDisabling).to.eql('false'); }); it('should mute the alert', async () => { diff --git a/x-pack/test/functional_with_es_ssl/apps/uptime/alert_flyout.ts b/x-pack/test/functional_with_es_ssl/apps/uptime/alert_flyout.ts index 3e5a8c57c4c7e6..fb4f34d65f9b00 100644 --- a/x-pack/test/functional_with_es_ssl/apps/uptime/alert_flyout.ts +++ b/x-pack/test/functional_with_es_ssl/apps/uptime/alert_flyout.ts @@ -14,20 +14,67 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const pageObjects = getPageObjects(['common', 'uptime']); const supertest = getService('supertest'); const retry = getService('retry'); + let alerts: any; - it('posts an alert, verfies its presence, and deletes the alert', async () => { + before(async () => { + alerts = getService('uptime').alerts; + }); + + it('can open alert flyout', async () => { await pageObjects.uptime.goToUptimeOverviewAndLoadData(DEFAULT_DATE_START, DEFAULT_DATE_END); + await alerts.openFlyout(); + }); - await pageObjects.uptime.openAlertFlyoutAndCreateMonitorStatusAlert({ - alertInterval: '11', - alertName: 'uptime-test', - alertNumTimes: '3', - alertTags: ['uptime', 'another'], - alertThrottleInterval: '30', - alertTimerangeSelection: '1', - filters: 'monitor.id: "0001-up"', - }); + it('can set alert name', async () => { + await alerts.setAlertName('uptime-test'); + }); + + it('can set alert tags', async () => { + await alerts.setAlertTags(['uptime', 'another']); + }); + + it('can set alert interval', async () => { + await alerts.setAlertInterval('11'); + }); + + it('can set alert throttle interval', async () => { + await alerts.setAlertThrottleInterval('30'); + }); + + it('can set alert status number of time', async () => { + await alerts.setAlertStatusNumTimes('3'); + }); + it('can set alert time range', async () => { + await alerts.setAlertTimerangeSelection('1'); + }); + it('can set monitor hours', async () => { + await alerts.setMonitorStatusSelectableToHours(); + }); + + it('can set kuery bar filters', async () => { + await pageObjects.uptime.setAlertKueryBarText('monitor.id: "0001-up"'); + }); + + it('can select location filter', async () => { + await alerts.clickAddFilterLocation(); + await alerts.clickLocationExpression('mpls'); + }); + + it('can select port filter', async () => { + await alerts.clickAddFilterPort(); + await alerts.clickPortExpression('5678'); + }); + + it('can select type/scheme filter', async () => { + await alerts.clickAddFilterType(); + await alerts.clickTypeExpression('http'); + }); + + it('can save alert', async () => { + await alerts.clickSaveAlertButton(); + }); + it('posts an alert, verifies its presence, and deletes the alert', async () => { // The creation of the alert could take some time, so the first few times we query after // the previous line resolves, the API may not be done creating the alert yet, so we // put the fetch code in a retry block with a timeout. @@ -67,7 +114,10 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { expect(timerange.to).to.be('now'); expect(locations).to.eql(['mpls']); expect(filters).to.eql( - '{"bool":{"should":[{"match_phrase":{"monitor.id":"0001-up"}}],"minimum_should_match":1}}' + '{"bool":{"filter":[{"bool":{"should":[{"match_phrase":{"monitor.id":"0001-up"}}],' + + '"minimum_should_match":1}},{"bool":{"filter":[{"bool":{"should":[{"match":{"observer.geo.name":"mpls"}}],' + + '"minimum_should_match":1}},{"bool":{"filter":[{"bool":{"should":[{"match":{"url.port":5678}}],' + + '"minimum_should_match":1}},{"bool":{"should":[{"match":{"monitor.type":"http"}}],"minimum_should_match":1}}]}}]}}]}}' ); } finally { await supertest diff --git a/x-pack/test/functional_with_es_ssl/page_objects/alert_details.ts b/x-pack/test/functional_with_es_ssl/page_objects/alert_details.ts index 48ad59586f7935..bd824430687659 100644 --- a/x-pack/test/functional_with_es_ssl/page_objects/alert_details.ts +++ b/x-pack/test/functional_with_es_ssl/page_objects/alert_details.ts @@ -23,7 +23,6 @@ export function AlertDetailsPageProvider({ getService }: FtrProviderContext) { async getActionsLabels() { return { actionType: await testSubjects.getVisibleText('actionTypeLabel'), - actionCount: await testSubjects.getVisibleText('actionCountLabel'), }; }, async getAlertInstancesList() { @@ -71,12 +70,10 @@ export function AlertDetailsPageProvider({ getService }: FtrProviderContext) { const muteAlertInstanceButton = await testSubjects.find( `muteAlertInstanceButton_${instance}` ); - log.debug(`checked:${await muteAlertInstanceButton.getAttribute('checked')}`); - expect(await muteAlertInstanceButton.getAttribute('checked')).to.eql( - isMuted ? 'true' : null + log.debug(`checked:${await muteAlertInstanceButton.getAttribute('aria-checked')}`); + expect(await muteAlertInstanceButton.getAttribute('aria-checked')).to.eql( + isMuted ? 'true' : 'false' ); - - expect(await testSubjects.exists(`mutedAlertInstanceLabel_${instance}`)).to.eql(isMuted); }); }, async ensureAlertInstanceExistance(instance: string, shouldExist: boolean) { diff --git a/x-pack/test/functional_with_es_ssl/page_objects/triggers_actions_ui_page.ts b/x-pack/test/functional_with_es_ssl/page_objects/triggers_actions_ui_page.ts index 2a50c0117eae93..ca7f064e206900 100644 --- a/x-pack/test/functional_with_es_ssl/page_objects/triggers_actions_ui_page.ts +++ b/x-pack/test/functional_with_es_ssl/page_objects/triggers_actions_ui_page.ts @@ -69,10 +69,6 @@ export function TriggersActionsPageProvider({ getService }: FtrProviderContext) .findTestSubject('connectorsTableCell-actionType') .find('.euiTableCellContent') .text(), - referencedByCount: $(row) - .findTestSubject('connectorsTableCell-referencedByCount') - .find('.euiTableCellContent') - .text(), }; }); }, diff --git a/x-pack/test/kerberos_api_integration/apis/security/kerberos_login.ts b/x-pack/test/kerberos_api_integration/apis/security/kerberos_login.ts index 81999826adbb10..fbf9a977e8b1f0 100644 --- a/x-pack/test/kerberos_api_integration/apis/security/kerberos_login.ts +++ b/x-pack/test/kerberos_api_integration/apis/security/kerberos_login.ts @@ -100,7 +100,9 @@ export default function({ getService }: FtrProviderContext) { }); }); - describe('finishing SPNEGO', () => { + // Preventing ES Snapshot to be promoted + // https://github.com/elastic/kibana/issues/65114 + describe.skip('finishing SPNEGO', () => { it('should properly set cookie and authenticate user', async () => { const response = await supertest .get('/internal/security/me') diff --git a/x-pack/test/reporting/services/reporting_api.js b/x-pack/test/reporting/services/reporting_api.js index e81f02c8a8ae48..82fe6aea08ac31 100644 --- a/x-pack/test/reporting/services/reporting_api.js +++ b/x-pack/test/reporting/services/reporting_api.js @@ -97,7 +97,6 @@ export function ReportingAPIProvider({ getService }) { }, expectRecentPdfAppStats(stats, app, count) { - expect(stats.reporting.last_day.printable_pdf.app[app]).to.be(count); expect(stats.reporting.last_7_days.printable_pdf.app[app]).to.be(count); }, @@ -106,7 +105,6 @@ export function ReportingAPIProvider({ getService }) { }, expectRecentPdfLayoutStats(stats, layout, count) { - expect(stats.reporting.last_day.printable_pdf.layout[layout]).to.be(count); expect(stats.reporting.last_7_days.printable_pdf.layout[layout]).to.be(count); }, @@ -115,7 +113,6 @@ export function ReportingAPIProvider({ getService }) { }, expectRecentJobTypeTotalStats(stats, jobType, count) { - expect(stats.reporting.last_day[jobType].total).to.be(count); expect(stats.reporting.last_7_days[jobType].total).to.be(count); }, diff --git a/x-pack/test/siem_cypress/es_archives/prebuilt_rules_loaded/data.json.gz b/x-pack/test/siem_cypress/es_archives/prebuilt_rules_loaded/data.json.gz index 573c006d1507d9..cac63ed9c585f9 100644 Binary files a/x-pack/test/siem_cypress/es_archives/prebuilt_rules_loaded/data.json.gz and b/x-pack/test/siem_cypress/es_archives/prebuilt_rules_loaded/data.json.gz differ diff --git a/yarn.lock b/yarn.lock index 346c4d76d24c99..7c2282fc2915bd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1262,10 +1262,10 @@ dependencies: "@elastic/apm-rum-core" "^5.2.0" -"@elastic/charts@18.4.2": - version "18.4.2" - resolved "https://registry.yarnpkg.com/@elastic/charts/-/charts-18.4.2.tgz#7d3c40dca8a7a701fb7227382191b84d36d8b32a" - integrity sha512-fmEDRUeFEtVWGurafhp/5bHBypOjdXiRXY074tCqnez43hA2iA4v1KrZL8tPFlMePgc/kpZf9wb8aEwxlfWumw== +"@elastic/charts@19.1.2": + version "19.1.2" + resolved "https://registry.yarnpkg.com/@elastic/charts/-/charts-19.1.2.tgz#b78730eb11bdcb4fcf17b213411bc48ae9cb770d" + integrity sha512-Qu4Sgp9Uh5Ic7Te3mCi19wlt8qw9Io8+MmCwpeyUi0TeGCPEIrpHp+aL9JkP+qTQJk+oCrJcjeXo2MhzcwOdCw== dependencies: classnames "^2.2.6" d3-array "^1.2.4" @@ -2116,7 +2116,7 @@ resolved "https://registry.yarnpkg.com/@mapbox/geojson-normalize/-/geojson-normalize-0.0.1.tgz#1da1e6b3a7add3ad29909b30f438f60581b7cd80" integrity sha1-HaHms6et060pkJsw9Dj2BYG3zYA= -"@mapbox/geojson-rewind@^0.4.0", "@mapbox/geojson-rewind@^0.4.1": +"@mapbox/geojson-rewind@^0.4.1": version "0.4.1" resolved "https://registry.yarnpkg.com/@mapbox/geojson-rewind/-/geojson-rewind-0.4.1.tgz#357d79300adb7fec7c1f091512988bca6458f068" integrity sha512-mxo2MEr7izA1uOXcDsw99Kgg6xW3P4H2j4n1lmldsgviIelpssvP+jQDivFKOHrOVJDpTTi5oZJvRcHtU9Uufw== @@ -2126,6 +2126,14 @@ minimist "^1.2.5" sharkdown "^0.1.0" +"@mapbox/geojson-rewind@^0.5.0": + version "0.5.0" + resolved "https://registry.yarnpkg.com/@mapbox/geojson-rewind/-/geojson-rewind-0.5.0.tgz#91f0ad56008c120caa19414b644d741249f4f560" + integrity sha512-73l/qJQgj/T/zO1JXVfuVvvKDgikD/7D/rHAD28S9BG1OTstgmftrmqfCx4U+zQAmtsB6HcDA3a7ymdnJZAQgg== + dependencies: + concat-stream "~2.0.0" + minimist "^1.2.5" + "@mapbox/geojson-types@^1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@mapbox/geojson-types/-/geojson-types-1.0.2.tgz#9aecf642cb00eab1080a57c4f949a65b4a5846d6" @@ -2166,20 +2174,20 @@ resolved "https://registry.yarnpkg.com/@mapbox/mapbox-gl-rtl-text/-/mapbox-gl-rtl-text-0.2.3.tgz#a26ecfb3f0061456d93ee8570dd9587d226ea8bd" integrity sha512-RaCYfnxULUUUxNwcUimV9C/o2295ktTyLEUzD/+VWkqXqvaVfFcZ5slytGzb2Sd/Jj4MlbxD0DCZbfa6CzcmMw== -"@mapbox/mapbox-gl-supported@^1.4.0": - version "1.4.0" - resolved "https://registry.yarnpkg.com/@mapbox/mapbox-gl-supported/-/mapbox-gl-supported-1.4.0.tgz#36946b22944fe2cfa43cfafd5ef36fdb54a069e4" - integrity sha512-ZD0Io4XK+/vU/4zpANjOtdWfVszAgnaMPsGR6LKsWh4kLIEv9qoobTVmJPPuwuM+ZI2b3BlZ6DYw1XHVmv6YTA== +"@mapbox/mapbox-gl-supported@^1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@mapbox/mapbox-gl-supported/-/mapbox-gl-supported-1.5.0.tgz#f60b6a55a5d8e5ee908347d2ce4250b15103dc8e" + integrity sha512-/PT1P6DNf7vjEEiPkVIRJkvibbqWtqnyGaBz3nfRdcxclNSnSdaLU5tfAgcD7I8Yt5i+L19s406YLl1koLnLbg== "@mapbox/point-geometry@0.1.0", "@mapbox/point-geometry@^0.1.0", "@mapbox/point-geometry@~0.1.0": version "0.1.0" resolved "https://registry.yarnpkg.com/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz#8a83f9335c7860effa2eeeca254332aa0aeed8f2" integrity sha1-ioP5M1x4YO/6Lu7KJUMyqgru2PI= -"@mapbox/tiny-sdf@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@mapbox/tiny-sdf/-/tiny-sdf-1.1.0.tgz#b0b8f5c22005e6ddb838f421ffd257c1f74f9a20" - integrity sha512-dnhyk8X2BkDRWImgHILYAGgo+kuciNYX30CUKj/Qd5eNjh54OWM/mdOS/PWsPeN+3abtN+QDGYM4G220ynVJKA== +"@mapbox/tiny-sdf@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@mapbox/tiny-sdf/-/tiny-sdf-1.1.1.tgz#16a20c470741bfe9191deb336f46e194da4a91ff" + integrity sha512-Ihn1nZcGIswJ5XGbgFAvVumOgWpvIjBX9jiRlIl46uQG9vJOF51ViBYHF95rEZupuyQbEmhLaDPLQlU7fUTsBg== "@mapbox/unitbezier@^0.0.0": version "0.0.0" @@ -3616,6 +3624,11 @@ dependencies: "@turf/helpers" "6.x" +"@types/accept@3.1.1": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@types/accept/-/accept-3.1.1.tgz#74457f6afabd23181e32b6bafae238bda0ce0da7" + integrity sha512-pXwi0bKUriKuNUv7d1xwbxKTqyTIzmMr1StxcGARmiuTLQyjNo+YwDq0w8dzY8wQjPofdgs1hvQLTuJaGuSKiQ== + "@types/angular-mocks@^1.7.0": version "1.7.0" resolved "https://registry.yarnpkg.com/@types/angular-mocks/-/angular-mocks-1.7.0.tgz#310d999a3c47c10ecd8eef466b5861df84799429" @@ -3844,6 +3857,13 @@ dependencies: "@types/color-convert" "*" +"@types/compression-webpack-plugin@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@types/compression-webpack-plugin/-/compression-webpack-plugin-2.0.1.tgz#4db78c398c8e973077cc530014d6513f1c693951" + integrity sha512-40oKg2aByfUPShpYBkldYwOcO34yaqOIPdlUlR1+F3MFl2WfpqYq2LFKOcgjU70d1r1L8r99XHkxYdhkGajHSw== + dependencies: + "@types/webpack" "*" + "@types/cookiejar@*": version "2.1.0" resolved "https://registry.yarnpkg.com/@types/cookiejar/-/cookiejar-2.1.0.tgz#4b7daf2c51696cfc70b942c11690528229d1a1ce" @@ -4350,10 +4370,10 @@ resolved "https://registry.yarnpkg.com/@types/lru-cache/-/lru-cache-5.1.0.tgz#57f228f2b80c046b4a1bd5cac031f81f207f4f03" integrity sha512-RaE0B+14ToE4l6UqdarKPnXwVDuigfFv+5j9Dze/Nqr23yyuqdNvzcZi3xB+3Agvi5R4EOgAksfv3lXX4vBt9w== -"@types/mapbox-gl@^1.8.1": - version "1.8.1" - resolved "https://registry.yarnpkg.com/@types/mapbox-gl/-/mapbox-gl-1.8.1.tgz#dbc12da1324d5bdb3dbf71b90b77cac17994a1a3" - integrity sha512-DdT/YzpGiYITkj2cUwyqPilPbtZURr1E0vZX0KTyyeNP0t0bxNyKoXo0seAcvUd2MsMgFYwFQh1WRC3x2V0kKQ== +"@types/mapbox-gl@^1.9.1": + version "1.9.1" + resolved "https://registry.yarnpkg.com/@types/mapbox-gl/-/mapbox-gl-1.9.1.tgz#78b62f8a1ead78bc525a4c1db84bb71fa0fcc579" + integrity sha512-5LS/fljbGjCPfjtOK5+pz8TT0PL4bBXTnN/PDbPtTQMqQdY/KWTWE4jRPuo0fL5wctd543DCptEUTydn+JK+gA== dependencies: "@types/geojson" "*" @@ -5446,7 +5466,7 @@ abortcontroller-polyfill@^1.4.0: resolved "https://registry.yarnpkg.com/abortcontroller-polyfill/-/abortcontroller-polyfill-1.4.0.tgz#0d5eb58e522a461774af8086414f68e1dda7a6c4" integrity sha512-3ZFfCRfDzx3GFjO6RAkYx81lPGpUS20ISxux9gLxuKnqafNcFQo59+IoZqpO2WvQlyc287B62HDnDdNYRmlvWA== -accept@3.x.x: +accept@3.0.2, accept@3.x.x: version "3.0.2" resolved "https://registry.yarnpkg.com/accept/-/accept-3.0.2.tgz#83e41cec7e1149f3fd474880423873db6c6cc9ac" integrity sha512-bghLXFkCOsC1Y2TZ51etWfKDs6q249SAoHTZVfzWWdlZxoij+mgkj9AmUJWQpDY48TfnrTDIe43Xem4zdMe7mQ== @@ -9493,6 +9513,18 @@ compressible@~2.0.16: dependencies: mime-db ">= 1.40.0 < 2" +compression-webpack-plugin@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/compression-webpack-plugin/-/compression-webpack-plugin-3.1.0.tgz#9f510172a7b5fae5aad3b670652e8bd7997aeeca" + integrity sha512-iqTHj3rADN4yHwXMBrQa/xrncex/uEQy8QHlaTKxGchT/hC0SdlJlmL/5eRqffmWq2ep0/Romw6Ld39JjTR/ug== + dependencies: + cacache "^13.0.1" + find-cache-dir "^3.0.0" + neo-async "^2.5.0" + schema-utils "^2.6.1" + serialize-javascript "^2.1.2" + webpack-sources "^1.0.1" + compression@^1.7.4: version "1.7.4" resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f" @@ -9539,6 +9571,16 @@ concat-stream@~1.5.0: readable-stream "~2.0.0" typedarray "~0.0.5" +concat-stream@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-2.0.0.tgz#414cf5af790a48c60ab9be4527d56d5e41133cb1" + integrity sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A== + dependencies: + buffer-from "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.0.2" + typedarray "^0.0.6" + conf@^1.1.2, conf@^1.3.1: version "1.4.0" resolved "https://registry.yarnpkg.com/conf/-/conf-1.4.0.tgz#1ea66c9d7a9b601674a5bb9d2b8dc3c726625e67" @@ -10355,7 +10397,7 @@ css@2.X, css@^2.0.0, css@^2.2.1, css@^2.2.3, css@^2.2.4: source-map-resolve "^0.5.2" urix "^0.1.0" -csscolorparser@~1.0.2: +csscolorparser@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/csscolorparser/-/csscolorparser-1.0.3.tgz#b34f391eea4da8f3e98231e2ccd8df9c041f171b" integrity sha1-s085HupNqPPpgjHizNjfnAQfFxs= @@ -14586,10 +14628,10 @@ github-username@^3.0.0: dependencies: gh-got "^5.0.0" -gl-matrix@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/gl-matrix/-/gl-matrix-3.0.0.tgz#888301ac7650e148c3865370e13ec66d08a8381f" - integrity sha512-PD4mVH/C/Zs64kOozeFnKY8ybhgwxXXQYGWdB4h68krAHknWJgk9uKOn6z8YElh5//vs++90pb6csrTIDWnexA== +gl-matrix@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/gl-matrix/-/gl-matrix-3.3.0.tgz#232eef60b1c8b30a28cbbe75b2caf6c48fd6358b" + integrity sha512-COb7LDz+SXaHtl/h4LeaFcNdJdAQSDeVqjiIihSXNrkWObZLhDI4hIkZC11Aeqp7bcE72clzB0BnDXr2SmslRA== glob-all@^3.1.0, glob-all@^3.2.1: version "3.2.1" @@ -20091,33 +20133,33 @@ mapbox-gl-draw-rectangle-mode@^1.0.4: resolved "https://registry.yarnpkg.com/mapbox-gl-draw-rectangle-mode/-/mapbox-gl-draw-rectangle-mode-1.0.4.tgz#42987d68872a5fb5cc5d76d3375ee20cd8bab8f7" integrity sha512-BdF6nwEK2p8n9LQoMPzBO8LhddW1fe+d5vK8HQIei+4VcRnUbKNsEj7Z15FsJxCHzsc2BQKXbESx5GaE8x0imQ== -mapbox-gl@^1.9.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/mapbox-gl/-/mapbox-gl-1.9.0.tgz#53e3e13c99483f362b07a8a763f2d61d580255a5" - integrity sha512-PKpoiB2pPUMrqFfBJpt/oA8On3zcp0adEoDS2YIC2RA6o4EZ9Sq2NPZocb64y7ra3mLUvEb7ps1pLVlPMh6y7w== +mapbox-gl@^1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/mapbox-gl/-/mapbox-gl-1.10.0.tgz#c33e74d1f328e820e245ff8ed7b5dbbbc4be204f" + integrity sha512-SrJXcR9s5yEsPuW2kKKumA1KqYW9RrL8j7ZcIh6glRQ/x3lwNMfwz/UEJAJcVNgeX+fiwzuBoDIdeGB/vSkZLQ== dependencies: - "@mapbox/geojson-rewind" "^0.4.0" + "@mapbox/geojson-rewind" "^0.5.0" "@mapbox/geojson-types" "^1.0.2" "@mapbox/jsonlint-lines-primitives" "^2.0.2" - "@mapbox/mapbox-gl-supported" "^1.4.0" + "@mapbox/mapbox-gl-supported" "^1.5.0" "@mapbox/point-geometry" "^0.1.0" - "@mapbox/tiny-sdf" "^1.1.0" + "@mapbox/tiny-sdf" "^1.1.1" "@mapbox/unitbezier" "^0.0.0" "@mapbox/vector-tile" "^1.3.1" "@mapbox/whoots-js" "^3.1.0" - csscolorparser "~1.0.2" + csscolorparser "~1.0.3" earcut "^2.2.2" geojson-vt "^3.2.1" - gl-matrix "^3.0.0" + gl-matrix "^3.2.1" grid-index "^1.1.0" - minimist "0.0.8" + minimist "^1.2.5" murmurhash-js "^1.0.0" pbf "^3.2.1" potpack "^1.0.1" quickselect "^2.0.0" rw "^1.3.3" supercluster "^7.0.0" - tinyqueue "^2.0.0" + tinyqueue "^2.0.3" vt-pbf "^3.1.1" mapcap@^1.0.0: @@ -25121,6 +25163,15 @@ readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable string_decoder "~1.1.1" util-deprecate "~1.0.1" +readable-stream@^3.0.2: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + readable-stream@~1.1.0: version "1.1.14" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" @@ -29031,10 +29082,10 @@ tinymath@1.2.1: resolved "https://registry.yarnpkg.com/tinymath/-/tinymath-1.2.1.tgz#f97ed66c588cdbf3c19dfba2ae266ee323db7e47" integrity sha512-8CYutfuHR3ywAJus/3JUhaJogZap1mrUQGzNxdBiQDhP3H0uFdQenvaXvqI8lMehX4RsanRZzxVfjMBREFdQaA== -tinyqueue@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/tinyqueue/-/tinyqueue-2.0.2.tgz#b4fe66d28a5b503edb99c149f87910059782a0cc" - integrity sha512-1oUV+ZAQaeaf830ui/p5JZpzGBw46qs1pKHcfqIc6/QxYDQuEmcBLIhiT0xAxLnekz+qxQusubIYk4cAS8TB2A== +tinyqueue@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/tinyqueue/-/tinyqueue-2.0.3.tgz#64d8492ebf39e7801d7bd34062e29b45b2035f08" + integrity sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA== title-case@^2.1.0: version "2.1.1" @@ -31627,18 +31678,18 @@ webpack-merge@4.2.2, webpack-merge@^4.2.2: dependencies: lodash "^4.17.15" -webpack-sources@^1.1.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.3.0.tgz#2a28dcb9f1f45fe960d8f1493252b5ee6530fa85" - integrity sha512-OiVgSrbGu7NEnEvQJJgdSFPl2qWKkWq5lHMhgiToIiN9w34EBnjYzSYs+VbL5KoYiLNtFFa7BZIKxRED3I32pA== +webpack-sources@^1.0.1, webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack-sources@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933" + integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ== dependencies: source-list-map "^2.0.0" source-map "~0.6.1" -webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack-sources@^1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933" - integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ== +webpack-sources@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.3.0.tgz#2a28dcb9f1f45fe960d8f1493252b5ee6530fa85" + integrity sha512-OiVgSrbGu7NEnEvQJJgdSFPl2qWKkWq5lHMhgiToIiN9w34EBnjYzSYs+VbL5KoYiLNtFFa7BZIKxRED3I32pA== dependencies: source-list-map "^2.0.0" source-map "~0.6.1"