diff --git a/.eslintrc.js b/.eslintrc.js index a2b8ae7622d0b..b3a1274d1cbeb 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -96,7 +96,7 @@ module.exports = { }, }, { - files: ['x-pack/legacy/plugins/cross_cluster_replication/**/*.{js,ts,tsx}'], + files: ['x-pack/plugins/cross_cluster_replication/**/*.{js,ts,tsx}'], rules: { 'jsx-a11y/click-events-have-key-events': 'off', }, diff --git a/.sass-lint.yml b/.sass-lint.yml index 5c2c88a1dad5d..89735342a2d6f 100644 --- a/.sass-lint.yml +++ b/.sass-lint.yml @@ -9,6 +9,7 @@ files: - 'x-pack/legacy/plugins/canvas/**/*.s+(a|c)ss' - 'x-pack/plugins/triggers_actions_ui/**/*.s+(a|c)ss' - 'x-pack/plugins/lens/**/*.s+(a|c)ss' + - 'x-pack/plugins/cross_cluster_replication/**/*.s+(a|c)ss' - 'x-pack/legacy/plugins/maps/**/*.s+(a|c)ss' - 'x-pack/plugins/maps/**/*.s+(a|c)ss' ignore: diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.createsearchsource.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.createsearchsource.md deleted file mode 100644 index 5c5aa348eecdf..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.createsearchsource.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [createSearchSource](./kibana-plugin-plugins-data-public.createsearchsource.md) - -## createSearchSource variable - -Deserializes a json string and a set of referenced objects to a `SearchSource` instance. Use this method to re-create the search source serialized using `searchSource.serialize`. - -This function is a factory function that returns the actual utility when calling it with the required service dependency (index patterns contract). A pre-wired version is also exposed in the start contract of the data plugin as part of the search service - -Signature: - -```typescript -createSearchSource: (indexPatterns: Pick) => (searchSourceJson: string, references: SavedObjectReference[]) => Promise -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstart.actions.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstart.actions.md index 3e966caa30799..25ce6eaa688f8 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstart.actions.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstart.actions.md @@ -8,6 +8,7 @@ ```typescript actions: { - createFiltersFromEvent: typeof createFiltersFromEvent; + createFiltersFromValueClickAction: typeof createFiltersFromValueClickAction; + createFiltersFromRangeSelectAction: typeof createFiltersFromRangeSelectAction; }; ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstart.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstart.md index a623e91388fd6..4f43f10ce089e 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstart.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.datapublicpluginstart.md @@ -14,7 +14,7 @@ export interface DataPublicPluginStart | Property | Type | Description | | --- | --- | --- | -| [actions](./kibana-plugin-plugins-data-public.datapublicpluginstart.actions.md) | {
createFiltersFromEvent: typeof createFiltersFromEvent;
} | | +| [actions](./kibana-plugin-plugins-data-public.datapublicpluginstart.actions.md) | {
createFiltersFromValueClickAction: typeof createFiltersFromValueClickAction;
createFiltersFromRangeSelectAction: typeof createFiltersFromRangeSelectAction;
} | | | [autocomplete](./kibana-plugin-plugins-data-public.datapublicpluginstart.autocomplete.md) | AutocompleteStart | | | [fieldFormats](./kibana-plugin-plugins-data-public.datapublicpluginstart.fieldformats.md) | FieldFormatsStart | | | [indexPatterns](./kibana-plugin-plugins-data-public.datapublicpluginstart.indexpatterns.md) | IndexPatternsContract | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldformats.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldformats.md index 244633c3c4c9e..d39871b99f744 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldformats.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldformats.md @@ -10,7 +10,7 @@ fieldFormats: { FieldFormat: typeof FieldFormat; FieldFormatsRegistry: typeof FieldFormatsRegistry; - serialize: (agg: import("./search").AggConfig) => import("../../expressions/common").SerializedFieldFormat; + serialize: (agg: import("./search").AggConfig) => import("../../expressions").SerializedFieldFormat; DEFAULT_CONVERTER_COLOR: { range: string; regex: string; diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchsource.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchsource.md index a8154dff72a6a..4b9f6e3594dc5 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchsource.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchsource.md @@ -4,6 +4,8 @@ ## ISearchSource type +\* + Signature: ```typescript 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 bf29c883e4eb9..5ef317a57fb0e 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 @@ -21,7 +21,6 @@ | [RequestTimeoutError](./kibana-plugin-plugins-data-public.requesttimeouterror.md) | Class used to signify that a request timed out. Useful for applications to conditionally handle this type of error differently than other errors. | | [SearchError](./kibana-plugin-plugins-data-public.searcherror.md) | | | [SearchInterceptor](./kibana-plugin-plugins-data-public.searchinterceptor.md) | | -| [SearchSource](./kibana-plugin-plugins-data-public.searchsource.md) | | | [TimeHistory](./kibana-plugin-plugins-data-public.timehistory.md) | | ## Enumerations @@ -101,7 +100,6 @@ | [castEsToKbnFieldTypeName](./kibana-plugin-plugins-data-public.castestokbnfieldtypename.md) | Get the KbnFieldType name for an esType string | | [connectToQueryState](./kibana-plugin-plugins-data-public.connecttoquerystate.md) | Helper to setup two-way syncing of global data and a state container | | [createSavedQueryService](./kibana-plugin-plugins-data-public.createsavedqueryservice.md) | | -| [createSearchSource](./kibana-plugin-plugins-data-public.createsearchsource.md) | Deserializes a json string and a set of referenced objects to a SearchSource instance. Use this method to re-create the search source serialized using searchSource.serialize.This function is a factory function that returns the actual utility when calling it with the required service dependency (index patterns contract). A pre-wired version is also exposed in the start contract of the data plugin as part of the search service | | [ES\_SEARCH\_STRATEGY](./kibana-plugin-plugins-data-public.es_search_strategy.md) | | | [esFilters](./kibana-plugin-plugins-data-public.esfilters.md) | | | [esKuery](./kibana-plugin-plugins-data-public.eskuery.md) | | @@ -140,7 +138,7 @@ | [IpRangeKey](./kibana-plugin-plugins-data-public.iprangekey.md) | | | [ISearch](./kibana-plugin-plugins-data-public.isearch.md) | | | [ISearchGeneric](./kibana-plugin-plugins-data-public.isearchgeneric.md) | | -| [ISearchSource](./kibana-plugin-plugins-data-public.isearchsource.md) | | +| [ISearchSource](./kibana-plugin-plugins-data-public.isearchsource.md) | \* | | [MatchAllFilter](./kibana-plugin-plugins-data-public.matchallfilter.md) | | | [ParsedInterval](./kibana-plugin-plugins-data-public.parsedinterval.md) | | | [PhraseFilter](./kibana-plugin-plugins-data-public.phrasefilter.md) | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.search.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.search.md index 78ac05b9fd386..9a22339fd0530 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.search.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.search.md @@ -27,8 +27,9 @@ search: { InvalidEsCalendarIntervalError: typeof InvalidEsCalendarIntervalError; InvalidEsIntervalFormatError: typeof InvalidEsIntervalFormatError; isDateHistogramBucketAggConfig: typeof isDateHistogramBucketAggConfig; + isNumberType: (agg: import("./search").AggConfig) => boolean; isStringType: (agg: import("./search").AggConfig) => boolean; - isType: (type: string) => (agg: import("./search").AggConfig) => boolean; + isType: (...types: string[]) => (agg: import("./search").AggConfig) => boolean; isValidEsInterval: typeof isValidEsInterval; isValidInterval: typeof isValidInterval; parentPipelineType: string; diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource._constructor_.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource._constructor_.md deleted file mode 100644 index e0c9e77b313a5..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource._constructor_.md +++ /dev/null @@ -1,20 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchSource](./kibana-plugin-plugins-data-public.searchsource.md) > [(constructor)](./kibana-plugin-plugins-data-public.searchsource._constructor_.md) - -## SearchSource.(constructor) - -Constructs a new instance of the `SearchSource` class - -Signature: - -```typescript -constructor(fields?: SearchSourceFields); -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| fields | SearchSourceFields | | - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.create.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.create.md deleted file mode 100644 index b0a0201680ca8..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.create.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchSource](./kibana-plugin-plugins-data-public.searchsource.md) > [create](./kibana-plugin-plugins-data-public.searchsource.create.md) - -## SearchSource.create() method - -Signature: - -```typescript -create(): SearchSource; -``` -Returns: - -`SearchSource` - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.createchild.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.createchild.md deleted file mode 100644 index 3f17dc21cf514..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.createchild.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchSource](./kibana-plugin-plugins-data-public.searchsource.md) > [createChild](./kibana-plugin-plugins-data-public.searchsource.createchild.md) - -## SearchSource.createChild() method - -Signature: - -```typescript -createChild(options?: {}): SearchSource; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| options | {} | | - -Returns: - -`SearchSource` - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.createcopy.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.createcopy.md deleted file mode 100644 index f503a3dfc3299..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.createcopy.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchSource](./kibana-plugin-plugins-data-public.searchsource.md) > [createCopy](./kibana-plugin-plugins-data-public.searchsource.createcopy.md) - -## SearchSource.createCopy() method - -Signature: - -```typescript -createCopy(): SearchSource; -``` -Returns: - -`SearchSource` - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.destroy.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.destroy.md deleted file mode 100644 index 8a7cc5ee75d11..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.destroy.md +++ /dev/null @@ -1,17 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchSource](./kibana-plugin-plugins-data-public.searchsource.md) > [destroy](./kibana-plugin-plugins-data-public.searchsource.destroy.md) - -## SearchSource.destroy() method - -Completely destroy the SearchSource. {undefined} - -Signature: - -```typescript -destroy(): void; -``` -Returns: - -`void` - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.fetch.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.fetch.md deleted file mode 100644 index 208ce565fac13..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.fetch.md +++ /dev/null @@ -1,25 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchSource](./kibana-plugin-plugins-data-public.searchsource.md) > [fetch](./kibana-plugin-plugins-data-public.searchsource.fetch.md) - -## SearchSource.fetch() method - -Fetch this source and reject the returned Promise on error - - -Signature: - -```typescript -fetch(options?: FetchOptions): Promise; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| options | FetchOptions | | - -Returns: - -`Promise` - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getfield.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getfield.md deleted file mode 100644 index 98ba815696cf6..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getfield.md +++ /dev/null @@ -1,25 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchSource](./kibana-plugin-plugins-data-public.searchsource.md) > [getField](./kibana-plugin-plugins-data-public.searchsource.getfield.md) - -## SearchSource.getField() method - -Get fields from the fields - -Signature: - -```typescript -getField(field: K, recurse?: boolean): SearchSourceFields[K]; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| field | K | | -| recurse | boolean | | - -Returns: - -`SearchSourceFields[K]` - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getfields.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getfields.md deleted file mode 100644 index dce03e7e1a95c..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getfields.md +++ /dev/null @@ -1,49 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchSource](./kibana-plugin-plugins-data-public.searchsource.md) > [getFields](./kibana-plugin-plugins-data-public.searchsource.getfields.md) - -## SearchSource.getFields() method - -Signature: - -```typescript -getFields(): { - type?: string | undefined; - query?: import("../..").Query | undefined; - filter?: Filter | Filter[] | (() => Filter | Filter[] | undefined) | undefined; - sort?: Record | Record[] | undefined; - highlight?: any; - highlightAll?: boolean | undefined; - aggs?: any; - from?: number | undefined; - size?: number | undefined; - source?: string | boolean | string[] | undefined; - version?: boolean | undefined; - fields?: string | boolean | string[] | undefined; - index?: import("../..").IndexPattern | undefined; - searchAfter?: import("./types").EsQuerySearchAfter | undefined; - timeout?: string | undefined; - terminate_after?: number | undefined; - }; -``` -Returns: - -`{ - type?: string | undefined; - query?: import("../..").Query | undefined; - filter?: Filter | Filter[] | (() => Filter | Filter[] | undefined) | undefined; - sort?: Record | Record[] | undefined; - highlight?: any; - highlightAll?: boolean | undefined; - aggs?: any; - from?: number | undefined; - size?: number | undefined; - source?: string | boolean | string[] | undefined; - version?: boolean | undefined; - fields?: string | boolean | string[] | undefined; - index?: import("../..").IndexPattern | undefined; - searchAfter?: import("./types").EsQuerySearchAfter | undefined; - timeout?: string | undefined; - terminate_after?: number | undefined; - }` - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getid.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getid.md deleted file mode 100644 index 55aaa26ca62f3..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getid.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchSource](./kibana-plugin-plugins-data-public.searchsource.md) > [getId](./kibana-plugin-plugins-data-public.searchsource.getid.md) - -## SearchSource.getId() method - -Signature: - -```typescript -getId(): string; -``` -Returns: - -`string` - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getownfield.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getownfield.md deleted file mode 100644 index d5a133772264e..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getownfield.md +++ /dev/null @@ -1,24 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchSource](./kibana-plugin-plugins-data-public.searchsource.md) > [getOwnField](./kibana-plugin-plugins-data-public.searchsource.getownfield.md) - -## SearchSource.getOwnField() method - -Get the field from our own fields, don't traverse up the chain - -Signature: - -```typescript -getOwnField(field: K): SearchSourceFields[K]; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| field | K | | - -Returns: - -`SearchSourceFields[K]` - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getparent.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getparent.md deleted file mode 100644 index 14578f7949ba6..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getparent.md +++ /dev/null @@ -1,17 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchSource](./kibana-plugin-plugins-data-public.searchsource.md) > [getParent](./kibana-plugin-plugins-data-public.searchsource.getparent.md) - -## SearchSource.getParent() method - -Get the parent of this SearchSource {undefined\|searchSource} - -Signature: - -```typescript -getParent(): SearchSource | undefined; -``` -Returns: - -`SearchSource | undefined` - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getsearchrequestbody.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getsearchrequestbody.md deleted file mode 100644 index f3451c9391074..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getsearchrequestbody.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchSource](./kibana-plugin-plugins-data-public.searchsource.md) > [getSearchRequestBody](./kibana-plugin-plugins-data-public.searchsource.getsearchrequestbody.md) - -## SearchSource.getSearchRequestBody() method - -Signature: - -```typescript -getSearchRequestBody(): Promise; -``` -Returns: - -`Promise` - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.history.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.history.md deleted file mode 100644 index e77c9dac7239f..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.history.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchSource](./kibana-plugin-plugins-data-public.searchsource.md) > [history](./kibana-plugin-plugins-data-public.searchsource.history.md) - -## SearchSource.history property - -Signature: - -```typescript -history: SearchRequest[]; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.md deleted file mode 100644 index 5f2fc809a5590..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.md +++ /dev/null @@ -1,46 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchSource](./kibana-plugin-plugins-data-public.searchsource.md) - -## SearchSource class - -Signature: - -```typescript -export declare class SearchSource -``` - -## Constructors - -| Constructor | Modifiers | Description | -| --- | --- | --- | -| [(constructor)(fields)](./kibana-plugin-plugins-data-public.searchsource._constructor_.md) | | Constructs a new instance of the SearchSource class | - -## Properties - -| Property | Modifiers | Type | Description | -| --- | --- | --- | --- | -| [history](./kibana-plugin-plugins-data-public.searchsource.history.md) | | SearchRequest[] | | - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [create()](./kibana-plugin-plugins-data-public.searchsource.create.md) | | | -| [createChild(options)](./kibana-plugin-plugins-data-public.searchsource.createchild.md) | | | -| [createCopy()](./kibana-plugin-plugins-data-public.searchsource.createcopy.md) | | | -| [destroy()](./kibana-plugin-plugins-data-public.searchsource.destroy.md) | | Completely destroy the SearchSource. {undefined} | -| [fetch(options)](./kibana-plugin-plugins-data-public.searchsource.fetch.md) | | Fetch this source and reject the returned Promise on error | -| [getField(field, recurse)](./kibana-plugin-plugins-data-public.searchsource.getfield.md) | | Get fields from the fields | -| [getFields()](./kibana-plugin-plugins-data-public.searchsource.getfields.md) | | | -| [getId()](./kibana-plugin-plugins-data-public.searchsource.getid.md) | | | -| [getOwnField(field)](./kibana-plugin-plugins-data-public.searchsource.getownfield.md) | | Get the field from our own fields, don't traverse up the chain | -| [getParent()](./kibana-plugin-plugins-data-public.searchsource.getparent.md) | | Get the parent of this SearchSource {undefined\|searchSource} | -| [getSearchRequestBody()](./kibana-plugin-plugins-data-public.searchsource.getsearchrequestbody.md) | | | -| [onRequestStart(handler)](./kibana-plugin-plugins-data-public.searchsource.onrequeststart.md) | | Add a handler that will be notified whenever requests start | -| [serialize()](./kibana-plugin-plugins-data-public.searchsource.serialize.md) | | Serializes the instance to a JSON string and a set of referenced objects. Use this method to get a representation of the search source which can be stored in a saved object.The references returned by this function can be mixed with other references in the same object, however make sure there are no name-collisions. The references will be named kibanaSavedObjectMeta.searchSourceJSON.index and kibanaSavedObjectMeta.searchSourceJSON.filter[<number>].meta.index.Using createSearchSource, the instance can be re-created. | -| [setField(field, value)](./kibana-plugin-plugins-data-public.searchsource.setfield.md) | | | -| [setFields(newFields)](./kibana-plugin-plugins-data-public.searchsource.setfields.md) | | | -| [setParent(parent, options)](./kibana-plugin-plugins-data-public.searchsource.setparent.md) | | Set a searchSource that this source should inherit from | -| [setPreferredSearchStrategyId(searchStrategyId)](./kibana-plugin-plugins-data-public.searchsource.setpreferredsearchstrategyid.md) | | \*\*\* PUBLIC API \*\*\* | - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.onrequeststart.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.onrequeststart.md deleted file mode 100644 index 092d057c69196..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.onrequeststart.md +++ /dev/null @@ -1,24 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchSource](./kibana-plugin-plugins-data-public.searchsource.md) > [onRequestStart](./kibana-plugin-plugins-data-public.searchsource.onrequeststart.md) - -## SearchSource.onRequestStart() method - -Add a handler that will be notified whenever requests start - -Signature: - -```typescript -onRequestStart(handler: (searchSource: ISearchSource, options?: FetchOptions) => Promise): void; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| handler | (searchSource: ISearchSource, options?: FetchOptions) => Promise<unknown> | | - -Returns: - -`void` - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.serialize.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.serialize.md deleted file mode 100644 index 52d25dec01dfd..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.serialize.md +++ /dev/null @@ -1,27 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchSource](./kibana-plugin-plugins-data-public.searchsource.md) > [serialize](./kibana-plugin-plugins-data-public.searchsource.serialize.md) - -## SearchSource.serialize() method - -Serializes the instance to a JSON string and a set of referenced objects. Use this method to get a representation of the search source which can be stored in a saved object. - -The references returned by this function can be mixed with other references in the same object, however make sure there are no name-collisions. The references will be named `kibanaSavedObjectMeta.searchSourceJSON.index` and `kibanaSavedObjectMeta.searchSourceJSON.filter[].meta.index`. - -Using `createSearchSource`, the instance can be re-created. - -Signature: - -```typescript -serialize(): { - searchSourceJSON: string; - references: SavedObjectReference[]; - }; -``` -Returns: - -`{ - searchSourceJSON: string; - references: SavedObjectReference[]; - }` - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.setfield.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.setfield.md deleted file mode 100644 index 83b7c30281752..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.setfield.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchSource](./kibana-plugin-plugins-data-public.searchsource.md) > [setField](./kibana-plugin-plugins-data-public.searchsource.setfield.md) - -## SearchSource.setField() method - -Signature: - -```typescript -setField(field: K, value: SearchSourceFields[K]): this; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| field | K | | -| value | SearchSourceFields[K] | | - -Returns: - -`this` - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.setfields.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.setfields.md deleted file mode 100644 index fa9b265aa43b7..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.setfields.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchSource](./kibana-plugin-plugins-data-public.searchsource.md) > [setFields](./kibana-plugin-plugins-data-public.searchsource.setfields.md) - -## SearchSource.setFields() method - -Signature: - -```typescript -setFields(newFields: SearchSourceFields): this; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| newFields | SearchSourceFields | | - -Returns: - -`this` - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.setparent.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.setparent.md deleted file mode 100644 index 19bf10bec210f..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.setparent.md +++ /dev/null @@ -1,25 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchSource](./kibana-plugin-plugins-data-public.searchsource.md) > [setParent](./kibana-plugin-plugins-data-public.searchsource.setparent.md) - -## SearchSource.setParent() method - -Set a searchSource that this source should inherit from - -Signature: - -```typescript -setParent(parent?: ISearchSource, options?: SearchSourceOptions): this; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| parent | ISearchSource | | -| options | SearchSourceOptions | | - -Returns: - -`this` - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.setpreferredsearchstrategyid.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.setpreferredsearchstrategyid.md deleted file mode 100644 index 8d8dbce9e60f6..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.setpreferredsearchstrategyid.md +++ /dev/null @@ -1,24 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [SearchSource](./kibana-plugin-plugins-data-public.searchsource.md) > [setPreferredSearchStrategyId](./kibana-plugin-plugins-data-public.searchsource.setpreferredsearchstrategyid.md) - -## SearchSource.setPreferredSearchStrategyId() method - -\*\*\* PUBLIC API \*\*\* - -Signature: - -```typescript -setPreferredSearchStrategyId(searchStrategyId: string): void; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| searchStrategyId | string | | - -Returns: - -`void` - diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fieldformats.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fieldformats.md index 2b986aee508e2..11f18a195d271 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fieldformats.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fieldformats.md @@ -10,7 +10,7 @@ fieldFormats: { FieldFormatsRegistry: typeof FieldFormatsRegistry; FieldFormat: typeof FieldFormat; - serializeFieldFormat: (agg: import("../public/search").AggConfig) => import("../../expressions/common").SerializedFieldFormat; + serializeFieldFormat: (agg: import("../public/search").AggConfig) => import("../../expressions").SerializedFieldFormat; BoolFormat: typeof BoolFormat; BytesFormat: typeof BytesFormat; ColorFormat: typeof ColorFormat; diff --git a/docs/setup/docker.asciidoc b/docs/setup/docker.asciidoc index ddabce3d5b842..12ee96b21b0c7 100644 --- a/docs/setup/docker.asciidoc +++ b/docs/setup/docker.asciidoc @@ -94,7 +94,7 @@ Some example translations are shown here: **Environment Variable**:: **Kibana Setting** `SERVER_NAME`:: `server.name` `KIBANA_DEFAULTAPPID`:: `kibana.defaultAppId` -`XPACK_MONITORING_ENABLED`:: `xpack.monitoring.enabled` +`MONITORING_ENABLED`:: `monitoring.enabled` In general, any setting listed in <> can be configured with this technique. @@ -125,9 +125,9 @@ images: `server.name`:: `kibana` `server.host`:: `"0"` `elasticsearch.hosts`:: `http://elasticsearch:9200` -`xpack.monitoring.ui.container.elasticsearch.enabled`:: `true` +`monitoring.ui.container.elasticsearch.enabled`:: `true` -NOTE: The setting `xpack.monitoring.ui.container.elasticsearch.enabled` is not +NOTE: The setting `monitoring.ui.container.elasticsearch.enabled` is not defined in the `-oss` image. These settings are defined in the default `kibana.yml`. They can be overridden diff --git a/docs/user/alerting/action-types/email.asciidoc b/docs/user/alerting/action-types/email.asciidoc index be3623dd9e59c..794fc14005f2f 100644 --- a/docs/user/alerting/action-types/email.asciidoc +++ b/docs/user/alerting/action-types/email.asciidoc @@ -1,6 +1,6 @@ [role="xpack"] [[email-action-type]] -== Email action type +=== Email action The email action type uses the SMTP protocol to send mail message, using an integration of https://nodemailer.com/[Nodemailer]. Email message text is sent as both plain text and html text. @@ -10,11 +10,11 @@ The email action type uses the SMTP protocol to send mail message, using an inte Email connectors have the following configuration properties: -Name:: The name of the connector. The name is used to identify a connector in the management UI connector listing, or in the connector list when configuring an action. +Name:: The name of the connector. The name is used to identify a connector in the management UI connector listing, or in the connector list when configuring an action. Sender:: The from address for all emails sent with this connector, specified in `user@host-name` format. -Host:: Host name of the service provider. If you are using the <> setting, make sure this hostname is whitelisted. +Host:: Host name of the service provider. If you are using the <> setting, make sure this hostname is whitelisted. Port:: The port to connect to on the service provider. -Secure:: If true the connection will use TLS when connecting to the service provider. See https://nodemailer.com/smtp/#tls-options[nodemailer TLS documentation] for more information. +Secure:: If true the connection will use TLS when connecting to the service provider. See https://nodemailer.com/smtp/#tls-options[nodemailer TLS documentation] for more information. Username:: username for 'login' type authentication. Password:: password for 'login' type authentication. @@ -26,4 +26,4 @@ Email actions have the following configuration properties: To, CC, BCC:: Each is a list of addresses. Addresses can be specified in `user@host-name` format, or in `name ` format. One of To, CC, or BCC must contain an entry. Subject:: The subject line of the email. -Message:: The message text of the email. Markdown format is supported. \ No newline at end of file +Message:: The message text of the email. Markdown format is supported. diff --git a/docs/user/alerting/action-types/index.asciidoc b/docs/user/alerting/action-types/index.asciidoc index 75d9e57b1f212..625b8f704b7c6 100644 --- a/docs/user/alerting/action-types/index.asciidoc +++ b/docs/user/alerting/action-types/index.asciidoc @@ -1,6 +1,6 @@ [role="xpack"] [[index-action-type]] -== Index action type +=== Index action The index action type will index a document into {es}. @@ -21,4 +21,4 @@ Execution time field:: This field will be automatically set to the time the ale Index actions have the following properties: -Document:: The document to index in json format. \ No newline at end of file +Document:: The document to index in json format. diff --git a/docs/user/alerting/action-types/pagerduty.asciidoc b/docs/user/alerting/action-types/pagerduty.asciidoc index da34c6e0855d7..abdcc7d1ba524 100644 --- a/docs/user/alerting/action-types/pagerduty.asciidoc +++ b/docs/user/alerting/action-types/pagerduty.asciidoc @@ -1,6 +1,6 @@ [role="xpack"] [[pagerduty-action-type]] -== PagerDuty action type +=== PagerDuty action The PagerDuty action type uses the https://v2.developer.pagerduty.com/docs/events-api-v2[v2 Events API] to trigger, acknowledge, and resolve PagerDuty alerts. @@ -10,7 +10,7 @@ The PagerDuty action type uses the https://v2.developer.pagerduty.com/docs/event [float] [[pagerduty-benefits]] -=== PagerDuty + Elastic integration benefits +==== PagerDuty + Elastic integration benefits By integrating PagerDuty with alerts, you can: @@ -20,7 +20,7 @@ By integrating PagerDuty with alerts, you can: [float] [[pagerduty-how-it-works]] -==== How it works +===== How it works {kib} allows you to create alerts to notify you of a significant move in your dataset. @@ -28,7 +28,7 @@ You can create alerts for all your Observability, Security, and Elastic Stack us Alerts will trigger a new incident on the corresponding PagerDuty service. [float] -==== Requirements +===== Requirements In the `kibana.yml` configuration file, you must add the <>. This is required to encrypt parameters that must be secured, for example PagerDuty’s integration key. @@ -47,18 +47,17 @@ review the <> that are available to you. [float] [[pagerduty-support]] -==== Support +===== Support If you need help with this integration, get in touch with the {kib} team by visiting https://support.elastic.co[support.elastic.co] or by using the *Ask Elastic* option in the {kib} Help menu. You can also select the {kib} category at https://discuss.elastic.co/[discuss.elastic.co]. [float] [[pagerduty-integration-walkthrough]] -==== Integration with PagerDuty walkthrough +===== Integration with PagerDuty walkthrough -[float] [[pagerduty-in-pagerduty]] -===== In PagerDuty +*In PagerDuty* . From the *Configuration* menu, select *Services*. . Add an integration to a service: @@ -83,9 +82,8 @@ image::user/alerting/images/pagerduty-integration.png[PagerDuty Integrations tab . Save this key, as you will use it when you configure the integration with Elastic in the next section. -[float] [[pagerduty-in-elastic]] -===== In Elastic +*In Elastic* . Create a PagerDuty Connector in Kibana. You can: + @@ -117,7 +115,7 @@ https://v2.developer.pagerduty.com/v2/docs/send-an-event-events-api-v2[API v2 do [float] [[pagerduty-uninstall]] -==== How to uninstall +===== How to uninstall To remove a PagerDuty connector from an alert, simply remove it from the *Actions* section of that alert, using the remove (x) icon. This will disable the integration for the particular alert. @@ -129,7 +127,7 @@ This is an irreversible action and impacts all alerts that use this connector. [float] [[pagerduty-connector-configuration]] -=== Connector configuration +==== Connector configuration PagerDuty connectors have the following configuration properties: @@ -139,7 +137,7 @@ Routing Key:: A 32 character PagerDuty Integration Key for an integration on a [float] [[pagerduty-action-configuration]] -=== Action configuration +==== Action configuration PagerDuty actions have the following properties: diff --git a/docs/user/alerting/action-types/server-log.asciidoc b/docs/user/alerting/action-types/server-log.asciidoc index 4efbdf3bea099..8f888785626c9 100644 --- a/docs/user/alerting/action-types/server-log.asciidoc +++ b/docs/user/alerting/action-types/server-log.asciidoc @@ -1,6 +1,6 @@ [role="xpack"] [[server-log-action-type]] -== Server log action type +=== Server log action This action type writes and entry to the {kib} server log. @@ -18,4 +18,4 @@ Name:: The name of the connector. The name is used to identify a connector Server log actions have the following properties: -Message:: The message to log. \ No newline at end of file +Message:: The message to log. diff --git a/docs/user/alerting/action-types/slack.asciidoc b/docs/user/alerting/action-types/slack.asciidoc index a4bacbf162e46..c0965d65bfdbe 100644 --- a/docs/user/alerting/action-types/slack.asciidoc +++ b/docs/user/alerting/action-types/slack.asciidoc @@ -1,8 +1,8 @@ [role="xpack"] [[slack-action-type]] -== Slack action type +=== Slack action -The Slack action type uses https://api.slack.com/incoming-webhooks[Slack Incoming Webhooks]. +The Slack action type uses https://api.slack.com/incoming-webhooks[Slack Incoming Webhooks]. [float] [[slack-connector-configuration]] @@ -11,7 +11,7 @@ The Slack action type uses https://api.slack.com/incoming-webhooks[Slack Incomin Slack connectors have the following configuration properties: Name:: The name of the connector. The name is used to identify a connector in the management UI connector listing, or in the connector list when configuring an action. -Webhook URL:: The URL of the incoming webhook. See https://api.slack.com/messaging/webhooks#getting_started[Slack Incoming Webhooks] for instructions on generating this URL. If you are using the <> setting, make sure the hostname is whitelisted. +Webhook URL:: The URL of the incoming webhook. See https://api.slack.com/messaging/webhooks#getting_started[Slack Incoming Webhooks] for instructions on generating this URL. If you are using the <> setting, make sure the hostname is whitelisted. [float] [[slack-action-configuration]] @@ -19,4 +19,4 @@ Webhook URL:: The URL of the incoming webhook. See https://api.slack.com/messa Slack actions have the following properties: -Message:: The message text, converted to the `text` field in the Webhook JSON payload. Currently only the text field is supported. Markdown, images, and other advanced formatting are not yet supported. \ No newline at end of file +Message:: The message text, converted to the `text` field in the Webhook JSON payload. Currently only the text field is supported. Markdown, images, and other advanced formatting are not yet supported. diff --git a/docs/user/alerting/action-types/webhook.asciidoc b/docs/user/alerting/action-types/webhook.asciidoc index 8c211aa83af89..64bfa6a1d6364 100644 --- a/docs/user/alerting/action-types/webhook.asciidoc +++ b/docs/user/alerting/action-types/webhook.asciidoc @@ -1,6 +1,6 @@ [role="xpack"] [[webhook-action-type]] -== Webhook action type +=== Webhook action The Webhook action type uses https://github.com/axios/axios[axios] to send a POST or PUT request to a web service. @@ -11,7 +11,7 @@ The Webhook action type uses https://github.com/axios/axios[axios] to send a POS Webhook connectors have the following configuration properties: Name:: The name of the connector. The name is used to identify a connector in the management UI connector listing, or in the connector list when configuring an action. -URL:: The request URL. If you are using the <> setting, make sure the hostname is whitelisted. +URL:: The request URL. If you are using the <> setting, make sure the hostname is whitelisted. Method:: HTTP request method, either `post`(default) or `put`. Headers:: A set of key-value pairs sent as headers with the request User:: An optional username. If set, HTTP basic authentication is used. Currently only basic authentication is supported. @@ -23,4 +23,4 @@ Password:: An optional password. If set, HTTP basic authentication is used. Cur Webhook actions have the following properties: -Body:: A json payload sent to the request URL. \ No newline at end of file +Body:: A json payload sent to the request URL. diff --git a/docs/user/monitoring/cluster-alerts.asciidoc b/docs/user/monitoring/cluster-alerts.asciidoc index cfdc9e2037030..a58ccc7f7d68d 100644 --- a/docs/user/monitoring/cluster-alerts.asciidoc +++ b/docs/user/monitoring/cluster-alerts.asciidoc @@ -49,13 +49,13 @@ To receive email notifications for the Cluster Alerts: . Configure an email account as described in {ref}/actions-email.html#configuring-email[Configuring email accounts]. . Configure the -`xpack.monitoring.cluster_alerts.email_notifications.email_address` setting in +`monitoring.cluster_alerts.email_notifications.email_address` setting in `kibana.yml` with your email address. + -- TIP: If you have separate production and monitoring clusters and separate {kib} instances for those clusters, you must put the -`xpack.monitoring.cluster_alerts.email_notifications.email_address` setting in +`monitoring.cluster_alerts.email_notifications.email_address` setting in the {kib} instance that is associated with the production cluster. -- diff --git a/docs/user/monitoring/elasticsearch-details.asciidoc b/docs/user/monitoring/elasticsearch-details.asciidoc index c0e804672d298..93f809cfff650 100644 --- a/docs/user/monitoring/elasticsearch-details.asciidoc +++ b/docs/user/monitoring/elasticsearch-details.asciidoc @@ -164,4 +164,4 @@ image::user/monitoring/images/monitoring-elasticsearch-logs.jpg["Recent {es} log TIP: By default, up to 10 log entries are shown. You can show up to 50 log entries by changing the -<>. +<>. diff --git a/docs/user/monitoring/monitoring-kibana.asciidoc b/docs/user/monitoring/monitoring-kibana.asciidoc index d0f2bd6acd901..9aa10289d299b 100644 --- a/docs/user/monitoring/monitoring-kibana.asciidoc +++ b/docs/user/monitoring/monitoring-kibana.asciidoc @@ -61,8 +61,8 @@ For more information, see {ref}/monitoring-settings.html[Monitoring settings in and {ref}/cluster-update-settings.html[Cluster update settings]. -- -. Verify that `xpack.monitoring.enabled` and -`xpack.monitoring.kibana.collection.enabled` are set to `true` in the +. Verify that `monitoring.enabled` and +`monitoring.kibana.collection.enabled` are set to `true` in the `kibana.yml` file. These are the default values. For more information, see <>. diff --git a/docs/user/monitoring/monitoring-metricbeat.asciidoc b/docs/user/monitoring/monitoring-metricbeat.asciidoc index f03a2ce1525a4..61aeaf21d3a4b 100644 --- a/docs/user/monitoring/monitoring-metricbeat.asciidoc +++ b/docs/user/monitoring/monitoring-metricbeat.asciidoc @@ -25,10 +25,10 @@ Add the following setting in the {kib} configuration file (`kibana.yml`): [source,yaml] ---------------------------------- -xpack.monitoring.kibana.collection.enabled: false +monitoring.kibana.collection.enabled: false ---------------------------------- -Leave the `xpack.monitoring.enabled` set to its default value (`true`). +Leave the `monitoring.enabled` set to its default value (`true`). // end::disable-kibana-collection[] For more information, see <>. diff --git a/docs/user/monitoring/monitoring-troubleshooting.asciidoc b/docs/user/monitoring/monitoring-troubleshooting.asciidoc index 7e1d6f94f15fa..bdaa10990c3aa 100644 --- a/docs/user/monitoring/monitoring-troubleshooting.asciidoc +++ b/docs/user/monitoring/monitoring-troubleshooting.asciidoc @@ -52,7 +52,7 @@ The *Stack Monitoring* page in {kib} is empty. . Confirm that {kib} is seeking monitoring data from the appropriate {es} URL. By default, data is retrieved from the cluster specified in the `elasticsearch.hosts` setting in the `kibana.yml` file. If you want to retrieve it -from a different monitoring cluster, set `xpack.monitoring.elasticsearch.hosts`. +from a different monitoring cluster, set `monitoring.ui.elasticsearch.hosts`. See <>. . Confirm that there is monitoring data available at that URL. It is stored in diff --git a/docs/user/monitoring/viewing-metrics.asciidoc b/docs/user/monitoring/viewing-metrics.asciidoc index 11516e32400fb..0a5535e6e1a91 100644 --- a/docs/user/monitoring/viewing-metrics.asciidoc +++ b/docs/user/monitoring/viewing-metrics.asciidoc @@ -26,14 +26,14 @@ cluster and view them all through the same instance of {kib}. By default, data is retrieved from the cluster specified in the `elasticsearch.hosts` value in the `kibana.yml` file. If you want to retrieve it -from a different cluster, set `xpack.monitoring.elasticsearch.hosts`. +from a different cluster, set `monitoring.ui.elasticsearch.hosts`. To learn more about typical monitoring architectures, see {ref}/how-monitoring-works.html[How monitoring works] and {ref}/monitoring-production.html[Monitoring in a production environment]. -- -. Verify that `xpack.monitoring.ui.enabled` is set to `true`, which is the +. Verify that `monitoring.ui.enabled` is set to `true`, which is the default value, in the `kibana.yml` file. For more information, see <>. @@ -43,8 +43,8 @@ must provide a user ID and password so {kib} can retrieve the data. .. Create a user that has the `monitoring_user` {ref}/built-in-roles.html[built-in role] on the monitoring cluster. -.. Add the `xpack.monitoring.elasticsearch.username` and -`xpack.monitoring.elasticsearch.password` settings in the `kibana.yml` file. +.. Add the `monitoring.ui.elasticsearch.username` and +`monitoring.ui.elasticsearch.password` settings in the `kibana.yml` file. If these settings are omitted, {kib} uses the `elasticsearch.username` and `elasticsearch.password` setting values. For more information, see {kibana-ref}/using-kibana-with-security.html[Configuring security in {kib}]. diff --git a/docs/user/security/securing-communications/index.asciidoc b/docs/user/security/securing-communications/index.asciidoc index 97313c19f44cb..3bdc59b90b3fd 100644 --- a/docs/user/security/securing-communications/index.asciidoc +++ b/docs/user/security/securing-communications/index.asciidoc @@ -188,5 +188,5 @@ verification. For more information about this setting, see <>. + +* *Pin filters to global state* — When selected, all filters created by interacting with the inputs are automatically pinned. + +. Click *Update*. + [float] [[markdown-widget]] === Markdown diff --git a/package.json b/package.json index 21e9f67e6206a..a7729b6dab7a1 100644 --- a/package.json +++ b/package.json @@ -123,7 +123,7 @@ "@elastic/charts": "18.3.0", "@elastic/datemath": "5.0.3", "@elastic/ems-client": "7.8.0", - "@elastic/eui": "21.0.1", + "@elastic/eui": "22.3.0", "@elastic/filesaver": "1.1.2", "@elastic/good": "8.1.1-kibana2", "@elastic/numeral": "2.4.0", diff --git a/packages/kbn-optimizer/src/cli.ts b/packages/kbn-optimizer/src/cli.ts index dcb4dcd35698d..c0bb408d60d8f 100644 --- a/packages/kbn-optimizer/src/cli.ts +++ b/packages/kbn-optimizer/src/cli.ts @@ -44,6 +44,11 @@ run( throw createFlagError('expected --cache to have no value'); } + const includeCoreBundle = flags.core ?? true; + if (typeof includeCoreBundle !== 'boolean') { + throw createFlagError('expected --core to have no value'); + } + const dist = flags.dist ?? false; if (typeof dist !== 'boolean') { throw createFlagError('expected --dist to have no value'); @@ -87,6 +92,7 @@ run( profileWebpack, extraPluginScanDirs, inspectWorkers, + includeCoreBundle, }); await runOptimizer(config) @@ -95,9 +101,10 @@ run( }, { flags: { - boolean: ['watch', 'oss', 'examples', 'dist', 'cache', 'profile', 'inspect-workers'], + boolean: ['core', 'watch', 'oss', 'examples', 'dist', 'cache', 'profile', 'inspect-workers'], string: ['workers', 'scan-dir'], default: { + core: true, examples: true, cache: true, 'inspect-workers': true, @@ -107,6 +114,7 @@ run( --workers max number of workers to use --oss only build oss plugins --profile profile the webpack builds and write stats.json files to build outputs + --no-core disable generating the core bundle --no-cache disable the cache --no-examples don't build the example plugins --dist create bundles that are suitable for inclusion in the Kibana distributable diff --git a/packages/kbn-optimizer/src/common/bundle.ts b/packages/kbn-optimizer/src/common/bundle.ts index f1bc0965a46cc..7581b90d60af2 100644 --- a/packages/kbn-optimizer/src/common/bundle.ts +++ b/packages/kbn-optimizer/src/common/bundle.ts @@ -23,7 +23,7 @@ import { BundleCache } from './bundle_cache'; import { UnknownVals } from './ts_helpers'; import { includes, ascending, entriesToObject } from './array_helpers'; -const VALID_BUNDLE_TYPES = ['plugin' as const]; +const VALID_BUNDLE_TYPES = ['plugin' as const, 'entry' as const]; export interface BundleSpec { readonly type: typeof VALID_BUNDLE_TYPES[0]; diff --git a/packages/kbn-optimizer/src/optimizer/get_bundles.test.ts b/packages/kbn-optimizer/src/optimizer/get_plugin_bundles.test.ts similarity index 93% rename from packages/kbn-optimizer/src/optimizer/get_bundles.test.ts rename to packages/kbn-optimizer/src/optimizer/get_plugin_bundles.test.ts index 9d95d883d605c..36dc0ca64c6ca 100644 --- a/packages/kbn-optimizer/src/optimizer/get_bundles.test.ts +++ b/packages/kbn-optimizer/src/optimizer/get_plugin_bundles.test.ts @@ -19,13 +19,13 @@ import { createAbsolutePathSerializer } from '@kbn/dev-utils'; -import { getBundles } from './get_bundles'; +import { getPluginBundles } from './get_plugin_bundles'; expect.addSnapshotSerializer(createAbsolutePathSerializer('/repo')); -it('returns a bundle for each plugin', () => { +it('returns a bundle for core and each plugin', () => { expect( - getBundles( + getPluginBundles( [ { directory: '/repo/plugins/foo', diff --git a/packages/kbn-optimizer/src/optimizer/get_bundles.ts b/packages/kbn-optimizer/src/optimizer/get_plugin_bundles.ts similarity index 93% rename from packages/kbn-optimizer/src/optimizer/get_bundles.ts rename to packages/kbn-optimizer/src/optimizer/get_plugin_bundles.ts index 7cd7bf15317e0..4741cc3c30af7 100644 --- a/packages/kbn-optimizer/src/optimizer/get_bundles.ts +++ b/packages/kbn-optimizer/src/optimizer/get_plugin_bundles.ts @@ -23,7 +23,7 @@ import { Bundle } from '../common'; import { KibanaPlatformPlugin } from './kibana_platform_plugins'; -export function getBundles(plugins: KibanaPlatformPlugin[], repoRoot: string) { +export function getPluginBundles(plugins: KibanaPlatformPlugin[], repoRoot: string) { return plugins .filter(p => p.isUiPlugin) .map( diff --git a/packages/kbn-optimizer/src/optimizer/optimizer_config.test.ts b/packages/kbn-optimizer/src/optimizer/optimizer_config.test.ts index cc564dd4a8387..d4152133f289d 100644 --- a/packages/kbn-optimizer/src/optimizer/optimizer_config.test.ts +++ b/packages/kbn-optimizer/src/optimizer/optimizer_config.test.ts @@ -19,7 +19,7 @@ jest.mock('./assign_bundles_to_workers.ts'); jest.mock('./kibana_platform_plugins.ts'); -jest.mock('./get_bundles.ts'); +jest.mock('./get_plugin_bundles.ts'); import Path from 'path'; import Os from 'os'; @@ -90,6 +90,7 @@ describe('OptimizerConfig::parseOptions()', () => { Object { "cache": true, "dist": false, + "includeCoreBundle": false, "inspectWorkers": false, "maxWorkerCount": 2, "pluginPaths": Array [], @@ -114,6 +115,7 @@ describe('OptimizerConfig::parseOptions()', () => { Object { "cache": false, "dist": false, + "includeCoreBundle": false, "inspectWorkers": false, "maxWorkerCount": 2, "pluginPaths": Array [], @@ -138,6 +140,7 @@ describe('OptimizerConfig::parseOptions()', () => { Object { "cache": true, "dist": false, + "includeCoreBundle": false, "inspectWorkers": false, "maxWorkerCount": 2, "pluginPaths": Array [], @@ -164,6 +167,7 @@ describe('OptimizerConfig::parseOptions()', () => { Object { "cache": true, "dist": false, + "includeCoreBundle": false, "inspectWorkers": false, "maxWorkerCount": 2, "pluginPaths": Array [], @@ -187,6 +191,7 @@ describe('OptimizerConfig::parseOptions()', () => { Object { "cache": true, "dist": false, + "includeCoreBundle": false, "inspectWorkers": false, "maxWorkerCount": 2, "pluginPaths": Array [], @@ -210,6 +215,7 @@ describe('OptimizerConfig::parseOptions()', () => { Object { "cache": true, "dist": false, + "includeCoreBundle": false, "inspectWorkers": false, "maxWorkerCount": 100, "pluginPaths": Array [], @@ -230,6 +236,7 @@ describe('OptimizerConfig::parseOptions()', () => { Object { "cache": false, "dist": false, + "includeCoreBundle": false, "inspectWorkers": false, "maxWorkerCount": 100, "pluginPaths": Array [], @@ -250,6 +257,7 @@ describe('OptimizerConfig::parseOptions()', () => { Object { "cache": false, "dist": false, + "includeCoreBundle": false, "inspectWorkers": false, "maxWorkerCount": 100, "pluginPaths": Array [], @@ -271,6 +279,7 @@ describe('OptimizerConfig::parseOptions()', () => { Object { "cache": false, "dist": false, + "includeCoreBundle": false, "inspectWorkers": false, "maxWorkerCount": 100, "pluginPaths": Array [], @@ -292,6 +301,7 @@ describe('OptimizerConfig::parseOptions()', () => { Object { "cache": true, "dist": false, + "includeCoreBundle": false, "inspectWorkers": false, "maxWorkerCount": 100, "pluginPaths": Array [], @@ -314,7 +324,7 @@ describe('OptimizerConfig::create()', () => { .assignBundlesToWorkers; const findKibanaPlatformPlugins: jest.Mock = jest.requireMock('./kibana_platform_plugins.ts') .findKibanaPlatformPlugins; - const getBundles: jest.Mock = jest.requireMock('./get_bundles.ts').getBundles; + const getPluginBundles: jest.Mock = jest.requireMock('./get_plugin_bundles.ts').getPluginBundles; beforeEach(() => { if ('mock' in OptimizerConfig.parseOptions) { @@ -326,7 +336,7 @@ describe('OptimizerConfig::create()', () => { { config: Symbol('worker config 2') }, ]); findKibanaPlatformPlugins.mockReturnValue(Symbol('new platform plugins')); - getBundles.mockReturnValue(Symbol('bundles')); + getPluginBundles.mockReturnValue([Symbol('bundle1'), Symbol('bundle2')]); jest.spyOn(OptimizerConfig, 'parseOptions').mockImplementation((): any => ({ cache: Symbol('parsed cache'), @@ -348,7 +358,10 @@ describe('OptimizerConfig::create()', () => { expect(config).toMatchInlineSnapshot(` OptimizerConfig { - "bundles": Symbol(bundles), + "bundles": Array [ + Symbol(bundle1), + Symbol(bundle2), + ], "cache": Symbol(parsed cache), "dist": Symbol(parsed dist), "inspectWorkers": Symbol(parsed inspect workers), @@ -383,7 +396,7 @@ describe('OptimizerConfig::create()', () => { } `); - expect(getBundles.mock).toMatchInlineSnapshot(` + expect(getPluginBundles.mock).toMatchInlineSnapshot(` Object { "calls": Array [ Array [ @@ -400,7 +413,10 @@ describe('OptimizerConfig::create()', () => { "results": Array [ Object { "type": "return", - "value": Symbol(bundles), + "value": Array [ + Symbol(bundle1), + Symbol(bundle2), + ], }, ], } diff --git a/packages/kbn-optimizer/src/optimizer/optimizer_config.ts b/packages/kbn-optimizer/src/optimizer/optimizer_config.ts index 7e1514058446b..d6336cf867470 100644 --- a/packages/kbn-optimizer/src/optimizer/optimizer_config.ts +++ b/packages/kbn-optimizer/src/optimizer/optimizer_config.ts @@ -23,7 +23,7 @@ import Os from 'os'; import { Bundle, WorkerConfig } from '../common'; import { findKibanaPlatformPlugins, KibanaPlatformPlugin } from './kibana_platform_plugins'; -import { getBundles } from './get_bundles'; +import { getPluginBundles } from './get_plugin_bundles'; function pickMaxWorkerCount(dist: boolean) { // don't break if cpus() returns nothing, or an empty array @@ -60,6 +60,9 @@ interface Options { pluginScanDirs?: string[]; /** absolute paths that should be added to the default scan dirs */ extraPluginScanDirs?: string[]; + + /** flag that causes the core bundle to be built along with plugins */ + includeCoreBundle?: boolean; } interface ParsedOptions { @@ -72,6 +75,7 @@ interface ParsedOptions { pluginPaths: string[]; pluginScanDirs: string[]; inspectWorkers: boolean; + includeCoreBundle: boolean; } export class OptimizerConfig { @@ -83,6 +87,7 @@ export class OptimizerConfig { const profileWebpack = !!options.profileWebpack; const inspectWorkers = !!options.inspectWorkers; const cache = options.cache !== false && !process.env.KBN_OPTIMIZER_NO_CACHE; + const includeCoreBundle = !!options.includeCoreBundle; const repoRoot = options.repoRoot; if (!Path.isAbsolute(repoRoot)) { @@ -134,13 +139,28 @@ export class OptimizerConfig { pluginScanDirs, pluginPaths, inspectWorkers, + includeCoreBundle, }; } static create(inputOptions: Options) { const options = OptimizerConfig.parseOptions(inputOptions); const plugins = findKibanaPlatformPlugins(options.pluginScanDirs, options.pluginPaths); - const bundles = getBundles(plugins, options.repoRoot); + const bundles = [ + ...(options.includeCoreBundle + ? [ + new Bundle({ + type: 'entry', + id: 'core', + entry: './public/entry_point', + sourceRoot: options.repoRoot, + contextDir: Path.resolve(options.repoRoot, 'src/core'), + outputDir: Path.resolve(options.repoRoot, 'src/core/target/public'), + }), + ] + : []), + ...getPluginBundles(plugins, options.repoRoot), + ]; return new OptimizerConfig( bundles, diff --git a/packages/kbn-optimizer/src/worker/webpack.config.ts b/packages/kbn-optimizer/src/worker/webpack.config.ts index 2f81d92ec923c..bb58bd0a3f005 100644 --- a/packages/kbn-optimizer/src/worker/webpack.config.ts +++ b/packages/kbn-optimizer/src/worker/webpack.config.ts @@ -104,7 +104,7 @@ export function getWebpackConfig(bundle: Bundle, worker: WorkerConfig) { output: { path: bundle.outputDir, - filename: '[name].plugin.js', + filename: `[name].${bundle.type}.js`, publicPath: PUBLIC_PATH_PLACEHOLDER, devtoolModuleFilenameTemplate: info => `/${bundle.type}:${bundle.id}/${Path.relative( diff --git a/packages/kbn-ui-shared-deps/package.json b/packages/kbn-ui-shared-deps/package.json index c8614b1df9d5d..3aa01179c00f3 100644 --- a/packages/kbn-ui-shared-deps/package.json +++ b/packages/kbn-ui-shared-deps/package.json @@ -10,7 +10,7 @@ }, "dependencies": { "@elastic/charts": "18.3.0", - "@elastic/eui": "21.0.1", + "@elastic/eui": "22.3.0", "@kbn/i18n": "1.0.0", "abortcontroller-polyfill": "^1.4.0", "angular": "^1.7.9", diff --git a/src/cli/cluster/run_kbn_optimizer.ts b/src/cli/cluster/run_kbn_optimizer.ts index 7752d4a45ab65..b811fc1f6b294 100644 --- a/src/cli/cluster/run_kbn_optimizer.ts +++ b/src/cli/cluster/run_kbn_optimizer.ts @@ -34,6 +34,7 @@ export function runKbnOptimizer(opts: Record, config: LegacyConfig) const optimizerConfig = OptimizerConfig.create({ repoRoot: REPO_ROOT, watch: true, + includeCoreBundle: true, oss: !!opts.oss, examples: !!opts.runExamples, pluginPaths: config.get('plugins.paths'), diff --git a/src/core/public/core.css b/src/core/public/_core.scss similarity index 100% rename from src/core/public/core.css rename to src/core/public/_core.scss diff --git a/src/core/public/application/ui/app_router.tsx b/src/core/public/application/ui/app_router.tsx index 61c8bc3cadae5..4c135c5769067 100644 --- a/src/core/public/application/ui/app_router.tsx +++ b/src/core/public/application/ui/app_router.tsx @@ -21,7 +21,7 @@ import React, { FunctionComponent, useMemo } from 'react'; import { Route, RouteComponentProps, Router, Switch } from 'react-router-dom'; import { History } from 'history'; import { Observable } from 'rxjs'; -import { useObservable } from 'react-use'; +import useObservable from 'react-use/lib/useObservable'; import { AppLeaveHandler, AppStatus, Mounter } from '../types'; import { AppContainer } from './app_container'; diff --git a/src/core/public/core_system.test.ts b/src/core/public/core_system.test.ts index 94fa74f4bd861..a42719417a2b1 100644 --- a/src/core/public/core_system.test.ts +++ b/src/core/public/core_system.test.ts @@ -59,7 +59,6 @@ const defaultCoreSystemParams = { warnLegacyBrowsers: true, }, } as any, - requireLegacyFiles: jest.fn(), }; beforeEach(() => { @@ -104,19 +103,22 @@ describe('constructor', () => { }); }); - it('passes requireLegacyFiles, useLegacyTestHarness, and a dom element to LegacyPlatformService', () => { + it('passes required params to LegacyPlatformService', () => { const requireLegacyFiles = { requireLegacyFiles: true }; - const useLegacyTestHarness = { useLegacyTestHarness: true }; + const requireLegacyBootstrapModule = { requireLegacyBootstrapModule: true }; + const requireNewPlatformShimModule = { requireNewPlatformShimModule: true }; createCoreSystem({ requireLegacyFiles, - useLegacyTestHarness, + requireLegacyBootstrapModule, + requireNewPlatformShimModule, }); expect(LegacyPlatformServiceConstructor).toHaveBeenCalledTimes(1); expect(LegacyPlatformServiceConstructor).toHaveBeenCalledWith({ requireLegacyFiles, - useLegacyTestHarness, + requireLegacyBootstrapModule, + requireNewPlatformShimModule, }); }); diff --git a/src/core/public/core_system.ts b/src/core/public/core_system.ts index 5c10d89459128..e58114b69dcc1 100644 --- a/src/core/public/core_system.ts +++ b/src/core/public/core_system.ts @@ -17,8 +17,6 @@ * under the License. */ -import './core.css'; - import { CoreId } from '../server'; import { PackageInfo, EnvironmentMode } from '../server/types'; import { CoreSetup, CoreStart } from '.'; @@ -50,8 +48,9 @@ interface Params { rootDomElement: HTMLElement; browserSupportsCsp: boolean; injectedMetadata: InjectedMetadataParams['injectedMetadata']; - requireLegacyFiles: LegacyPlatformParams['requireLegacyFiles']; - useLegacyTestHarness?: LegacyPlatformParams['useLegacyTestHarness']; + requireLegacyFiles?: LegacyPlatformParams['requireLegacyFiles']; + requireLegacyBootstrapModule?: LegacyPlatformParams['requireLegacyBootstrapModule']; + requireNewPlatformShimModule?: LegacyPlatformParams['requireNewPlatformShimModule']; } /** @internal */ @@ -111,7 +110,8 @@ export class CoreSystem { browserSupportsCsp, injectedMetadata, requireLegacyFiles, - useLegacyTestHarness, + requireLegacyBootstrapModule, + requireNewPlatformShimModule, } = params; this.rootDomElement = rootDomElement; @@ -145,7 +145,8 @@ export class CoreSystem { this.legacy = new LegacyPlatformService({ requireLegacyFiles, - useLegacyTestHarness, + requireLegacyBootstrapModule, + requireNewPlatformShimModule, }); } diff --git a/src/core/public/entry_point.ts b/src/core/public/entry_point.ts new file mode 100644 index 0000000000000..9461acccf30b9 --- /dev/null +++ b/src/core/public/entry_point.ts @@ -0,0 +1,59 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * This is the entry point used to boot the frontend when serving a application + * that lives in the Kibana Platform. + * + * Any changes to this file should be kept in sync with + * src/legacy/ui/ui_bundles/app_entry_template.js + */ + +import './index.scss'; +import { i18n } from '@kbn/i18n'; +import { CoreSystem } from './core_system'; + +const injectedMetadata = JSON.parse( + document.querySelector('kbn-injected-metadata')!.getAttribute('data')! +); + +if (process.env.IS_KIBANA_DISTRIBUTABLE !== 'true' && process.env.ELASTIC_APM_ACTIVE === 'true') { + // @ts-ignore + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { init } = require('@elastic/apm-rum'); + init(injectedMetadata.vars.apmConfig); +} + +i18n + .load(injectedMetadata.i18n.translationsUrl) + .catch(e => e) + .then(async i18nError => { + const coreSystem = new CoreSystem({ + injectedMetadata, + rootDomElement: document.body, + browserSupportsCsp: !(window as any).__kbnCspNotEnforced__, + }); + + const setup = await coreSystem.setup(); + if (i18nError && setup) { + setup.fatalErrors.add(i18nError); + } + + await coreSystem.start(); + }); diff --git a/src/core/public/index.scss b/src/core/public/index.scss index 86f2efdff7702..4be46899cff67 100644 --- a/src/core/public/index.scss +++ b/src/core/public/index.scss @@ -1,11 +1,11 @@ -// Functions need to be first, since we use them in our variables and mixin definitions -@import '@elastic/eui/src/global_styling/functions/index'; - -// Variables come next, and are used in some mixins -@import '@elastic/eui/src/global_styling/variables/index'; - -// Mixins provide generic code expansion through helpers -@import '@elastic/eui/src/global_styling/mixins/index'; +// This file is built by both the legacy and KP build systems so we need to +// import this explicitly +@import '../../legacy/ui/public/styles/_styling_constants'; +@import './core'; @import './chrome/index'; @import './overlays/index'; +@import './rendering/index'; + +// Global styles need to be migrated +@import '../../legacy/ui/public/styles/_legacy/_index'; diff --git a/src/core/public/legacy/__snapshots__/legacy_service.test.ts.snap b/src/core/public/legacy/__snapshots__/legacy_service.test.ts.snap deleted file mode 100644 index 97629fdd1add5..0000000000000 --- a/src/core/public/legacy/__snapshots__/legacy_service.test.ts.snap +++ /dev/null @@ -1,35 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`#start() load order useLegacyTestHarness = false loads ui/modules before ui/chrome, and both before legacy files 1`] = ` -Array [ - "ui/new_platform", - "ui/chrome", - "legacy files", -] -`; - -exports[`#start() load order useLegacyTestHarness = true loads ui/modules before ui/test_harness, and both before legacy files 1`] = ` -Array [ - "ui/new_platform", - "ui/test_harness", - "legacy files", -] -`; - -exports[`#stop() destroys the angular scope and empties the targetDomElement if angular is bootstrapped to targetDomElement 1`] = ` -
-`; - -exports[`#stop() does nothing if angular was not bootstrapped to targetDomElement 1`] = ` -
- - -

- this should not be removed -

- - -
-`; diff --git a/src/core/public/legacy/legacy_service.test.ts b/src/core/public/legacy/legacy_service.test.ts index c3de645c6b17e..fa29320aab4e6 100644 --- a/src/core/public/legacy/legacy_service.test.ts +++ b/src/core/public/legacy/legacy_service.test.ts @@ -19,34 +19,6 @@ import angular from 'angular'; -const mockLoadOrder: string[] = []; - -const mockUiNewPlatformSetup = jest.fn(); -const mockUiNewPlatformStart = jest.fn(); -jest.mock('ui/new_platform', () => { - mockLoadOrder.push('ui/new_platform'); - return { - __setup__: mockUiNewPlatformSetup, - __start__: mockUiNewPlatformStart, - }; -}); - -const mockUiChromeBootstrap = jest.fn(); -jest.mock('ui/chrome', () => { - mockLoadOrder.push('ui/chrome'); - return { - bootstrap: mockUiChromeBootstrap, - }; -}); - -const mockUiTestHarnessBootstrap = jest.fn(); -jest.mock('ui/test_harness', () => { - mockLoadOrder.push('ui/test_harness'); - return { - bootstrap: mockUiTestHarnessBootstrap, - }; -}); - import { chromeServiceMock } from '../chrome/chrome_service.mock'; import { fatalErrorsServiceMock } from '../fatal_errors/fatal_errors_service.mock'; import { httpServiceMock } from '../http/http_service.mock'; @@ -69,10 +41,24 @@ const injectedMetadataSetup = injectedMetadataServiceMock.createSetupContract(); const notificationsSetup = notificationServiceMock.createSetupContract(); const uiSettingsSetup = uiSettingsServiceMock.createSetupContract(); +const mockLoadOrder: string[] = []; +const mockUiNewPlatformSetup = jest.fn(); +const mockUiNewPlatformStart = jest.fn(); +const mockUiChromeBootstrap = jest.fn(); const defaultParams = { requireLegacyFiles: jest.fn(() => { mockLoadOrder.push('legacy files'); }), + requireLegacyBootstrapModule: jest.fn(() => { + mockLoadOrder.push('ui/chrome'); + return { + bootstrap: mockUiChromeBootstrap, + }; + }), + requireNewPlatformShimModule: jest.fn(() => ({ + __setup__: mockUiNewPlatformSetup, + __start__: mockUiNewPlatformStart, + })), }; const defaultSetupDeps = { @@ -128,7 +114,7 @@ afterEach(() => { describe('#setup()', () => { describe('default', () => { - it('initializes ui/new_platform with core APIs', () => { + it('initializes new platform shim module with core APIs', () => { const legacyPlatform = new LegacyPlatformService({ ...defaultParams, }); @@ -138,6 +124,21 @@ describe('#setup()', () => { expect(mockUiNewPlatformSetup).toHaveBeenCalledTimes(1); expect(mockUiNewPlatformSetup).toHaveBeenCalledWith(expect.any(Object), {}); }); + + it('throws error if requireNewPlatformShimModule is undefined', () => { + const legacyPlatform = new LegacyPlatformService({ + ...defaultParams, + requireNewPlatformShimModule: undefined, + }); + + expect(() => { + legacyPlatform.setup(defaultSetupDeps); + }).toThrowErrorMatchingInlineSnapshot( + `"requireNewPlatformShimModule must be specified when rendering a legacy application"` + ); + + expect(mockUiNewPlatformSetup).not.toHaveBeenCalled(); + }); }); }); @@ -171,6 +172,21 @@ describe('#start()', () => { expect(mockUiNewPlatformStart).toHaveBeenCalledWith(expect.any(Object), {}); }); + it('throws error if requireNewPlatformShimeModule is undefined', () => { + const legacyPlatform = new LegacyPlatformService({ + ...defaultParams, + requireNewPlatformShimModule: undefined, + }); + + expect(() => { + legacyPlatform.start(defaultStartDeps); + }).toThrowErrorMatchingInlineSnapshot( + `"requireNewPlatformShimModule must be specified when rendering a legacy application"` + ); + + expect(mockUiNewPlatformStart).not.toHaveBeenCalled(); + }); + it('resolves getStartServices with core and plugin APIs', async () => { const legacyPlatform = new LegacyPlatformService({ ...defaultParams, @@ -185,67 +201,35 @@ describe('#start()', () => { expect(pluginsStart).toBe(defaultStartDeps.plugins); }); - describe('useLegacyTestHarness = false', () => { - it('passes the targetDomElement to ui/chrome', () => { - const legacyPlatform = new LegacyPlatformService({ - ...defaultParams, - }); + it('passes the targetDomElement to legacy bootstrap module', () => { + const legacyPlatform = new LegacyPlatformService({ + ...defaultParams, + }); - legacyPlatform.setup(defaultSetupDeps); - legacyPlatform.start(defaultStartDeps); + legacyPlatform.setup(defaultSetupDeps); + legacyPlatform.start(defaultStartDeps); - expect(mockUiTestHarnessBootstrap).not.toHaveBeenCalled(); - expect(mockUiChromeBootstrap).toHaveBeenCalledTimes(1); - expect(mockUiChromeBootstrap).toHaveBeenCalledWith(defaultStartDeps.targetDomElement); - }); + expect(mockUiChromeBootstrap).toHaveBeenCalledTimes(1); + expect(mockUiChromeBootstrap).toHaveBeenCalledWith(defaultStartDeps.targetDomElement); }); - describe('useLegacyTestHarness = true', () => { - it('passes the targetDomElement to ui/test_harness', () => { + describe('load order', () => { + it('loads ui/modules before ui/chrome, and both before legacy files', () => { const legacyPlatform = new LegacyPlatformService({ ...defaultParams, - useLegacyTestHarness: true, }); + expect(mockLoadOrder).toEqual([]); + legacyPlatform.setup(defaultSetupDeps); legacyPlatform.start(defaultStartDeps); - expect(mockUiChromeBootstrap).not.toHaveBeenCalled(); - expect(mockUiTestHarnessBootstrap).toHaveBeenCalledTimes(1); - expect(mockUiTestHarnessBootstrap).toHaveBeenCalledWith(defaultStartDeps.targetDomElement); - }); - }); - - describe('load order', () => { - describe('useLegacyTestHarness = false', () => { - it('loads ui/modules before ui/chrome, and both before legacy files', () => { - const legacyPlatform = new LegacyPlatformService({ - ...defaultParams, - }); - - expect(mockLoadOrder).toEqual([]); - - legacyPlatform.setup(defaultSetupDeps); - legacyPlatform.start(defaultStartDeps); - - expect(mockLoadOrder).toMatchSnapshot(); - }); - }); - - describe('useLegacyTestHarness = true', () => { - it('loads ui/modules before ui/test_harness, and both before legacy files', () => { - const legacyPlatform = new LegacyPlatformService({ - ...defaultParams, - useLegacyTestHarness: true, - }); - - expect(mockLoadOrder).toEqual([]); - - legacyPlatform.setup(defaultSetupDeps); - legacyPlatform.start(defaultStartDeps); - - expect(mockLoadOrder).toMatchSnapshot(); - }); + expect(mockLoadOrder).toMatchInlineSnapshot(` + Array [ + "ui/chrome", + "legacy files", + ] + `); }); }); }); @@ -262,7 +246,17 @@ describe('#stop()', () => { }); legacyPlatform.stop(); - expect(targetDomElement).toMatchSnapshot(); + expect(targetDomElement).toMatchInlineSnapshot(` +
+ + +

+ this should not be removed +

+ + +
+ `); }); it('destroys the angular scope and empties the targetDomElement if angular is bootstrapped to targetDomElement', async () => { @@ -291,7 +285,11 @@ describe('#stop()', () => { legacyPlatform.start({ ...defaultStartDeps, targetDomElement }); legacyPlatform.stop(); - expect(targetDomElement).toMatchSnapshot(); + expect(targetDomElement).toMatchInlineSnapshot(` +
+ `); expect(scopeDestroySpy).toHaveBeenCalledTimes(1); }); }); diff --git a/src/core/public/legacy/legacy_service.ts b/src/core/public/legacy/legacy_service.ts index 39ca7bdf54b7c..01837ba6f5940 100644 --- a/src/core/public/legacy/legacy_service.ts +++ b/src/core/public/legacy/legacy_service.ts @@ -25,8 +25,12 @@ import { LegacyCoreSetup, LegacyCoreStart, MountPoint } from '../'; /** @internal */ export interface LegacyPlatformParams { - requireLegacyFiles: () => void; - useLegacyTestHarness?: boolean; + requireLegacyFiles?: () => void; + requireLegacyBootstrapModule?: () => BootstrapModule; + requireNewPlatformShimModule?: () => { + __setup__: (legacyCore: LegacyCoreSetup, plugins: Record) => void; + __start__: (legacyCore: LegacyCoreStart, plugins: Record) => void; + }; } interface SetupDeps { @@ -92,7 +96,13 @@ export class LegacyPlatformService { // Inject parts of the new platform into parts of the legacy platform // so that legacy APIs/modules can mimic their new platform counterparts if (core.injectedMetadata.getLegacyMode()) { - require('ui/new_platform').__setup__(legacyCore, plugins); + if (!this.params.requireNewPlatformShimModule) { + throw new Error( + `requireNewPlatformShimModule must be specified when rendering a legacy application` + ); + } + + this.params.requireNewPlatformShimModule().__setup__(legacyCore, plugins); } } @@ -131,16 +141,29 @@ export class LegacyPlatformService { this.startDependencies$.next([legacyCore, plugins, {}]); + if (!this.params.requireNewPlatformShimModule) { + throw new Error( + `requireNewPlatformShimModule must be specified when rendering a legacy application` + ); + } + if (!this.params.requireLegacyBootstrapModule) { + throw new Error( + `requireLegacyBootstrapModule must be specified when rendering a legacy application` + ); + } + // Inject parts of the new platform into parts of the legacy platform // so that legacy APIs/modules can mimic their new platform counterparts - require('ui/new_platform').__start__(legacyCore, plugins); + this.params.requireNewPlatformShimModule().__start__(legacyCore, plugins); // Load the bootstrap module before loading the legacy platform files so that // the bootstrap module can modify the environment a bit first - this.bootstrapModule = this.loadBootstrapModule(); + this.bootstrapModule = this.params.requireLegacyBootstrapModule(); // require the files that will tie into the legacy platform - this.params.requireLegacyFiles(); + if (this.params.requireLegacyFiles) { + this.params.requireLegacyFiles(); + } if (!this.bootstrapModule) { throw new Error('Bootstrap module must be loaded before `start`'); @@ -172,20 +195,6 @@ export class LegacyPlatformService { // clear the inner html of the root angular element this.targetDomElement.textContent = ''; } - - private loadBootstrapModule(): BootstrapModule { - if (this.params.useLegacyTestHarness) { - // wrapped in NODE_ENV check so the `ui/test_harness` module - // is not included in the distributable - if (process.env.IS_KIBANA_DISTRIBUTABLE !== 'true') { - return require('ui/test_harness'); - } - - throw new Error('tests bundle is not available in the distributable'); - } - - return require('ui/chrome'); - } } const notSupported = (methodName: string) => (...args: any[]) => { diff --git a/src/legacy/ui/public/chrome/directives/_kbn_chrome.scss b/src/core/public/rendering/_base.scss similarity index 96% rename from src/legacy/ui/public/chrome/directives/_kbn_chrome.scss rename to src/core/public/rendering/_base.scss index b29a83848d291..ff28fc75e367d 100644 --- a/src/legacy/ui/public/chrome/directives/_kbn_chrome.scss +++ b/src/core/public/rendering/_base.scss @@ -13,7 +13,7 @@ display: flex; flex-flow: column nowrap; position: absolute; - left: $kbnGlobalNavClosedWidth; + left: 0; top: 0; right: 0; bottom: 0; diff --git a/src/core/public/rendering/_index.scss b/src/core/public/rendering/_index.scss new file mode 100644 index 0000000000000..c8567498b42ec --- /dev/null +++ b/src/core/public/rendering/_index.scss @@ -0,0 +1 @@ +@import './base'; diff --git a/src/core/public/saved_objects/saved_objects_client.ts b/src/core/public/saved_objects/saved_objects_client.ts index afc77806afb91..d26cadecb184a 100644 --- a/src/core/public/saved_objects/saved_objects_client.ts +++ b/src/core/public/saved_objects/saved_objects_client.ts @@ -28,12 +28,6 @@ import { SavedObjectsMigrationVersion, } from '../../server'; -// TODO: Migrate to an error modal powered by the NP? -import { - isAutoCreateIndexError, - showAutoCreateIndexErrorPage, - // eslint-disable-next-line @kbn/eslint/no-restricted-paths -} from '../../../legacy/ui/public/error_auto_create_index/error_auto_create_index'; import { SimpleSavedObject } from './simple_saved_object'; import { HttpFetchOptions, HttpSetup } from '../http'; @@ -226,7 +220,9 @@ export class SavedObjectsClient { .then(resp => this.createSavedObject(resp)) .catch((error: object) => { if (isAutoCreateIndexError(error)) { - showAutoCreateIndexErrorPage(); + window.location.assign( + this.http.basePath.prepend('/app/kibana#/error/action.auto_create_index') + ); } throw error; @@ -472,3 +468,9 @@ const renameKeys = , U extends Record ...{ [keysMap[key] || key]: obj[key] }, }; }, {}); + +const isAutoCreateIndexError = (error: any) => { + return ( + error?.res?.status === 503 && error?.body?.attributes?.code === 'ES_AUTO_CREATE_INDEX_ERROR' + ); +}; diff --git a/src/core/server/config/deprecation/core_deprecations.ts b/src/core/server/config/deprecation/core_deprecations.ts index f0b21adf62ff7..9e098c06ba155 100644 --- a/src/core/server/config/deprecation/core_deprecations.ts +++ b/src/core/server/config/deprecation/core_deprecations.ts @@ -128,56 +128,6 @@ export const coreDeprecationProvider: ConfigDeprecationProvider = ({ renameFromRoot('optimize.lazyHost', 'optimize.watchHost'), renameFromRoot('optimize.lazyPrebuild', 'optimize.watchPrebuild'), renameFromRoot('optimize.lazyProxyTimeout', 'optimize.watchProxyTimeout'), - // Monitoring renames - // TODO: Remove these from here once the monitoring plugin is migrated to NP - renameFromRoot('xpack.monitoring.enabled', 'monitoring.enabled'), - renameFromRoot('xpack.monitoring.ui.enabled', 'monitoring.ui.enabled'), - renameFromRoot( - 'xpack.monitoring.kibana.collection.enabled', - 'monitoring.kibana.collection.enabled' - ), - renameFromRoot('xpack.monitoring.max_bucket_size', 'monitoring.ui.max_bucket_size'), - renameFromRoot('xpack.monitoring.min_interval_seconds', 'monitoring.ui.min_interval_seconds'), - renameFromRoot( - 'xpack.monitoring.show_license_expiration', - 'monitoring.ui.show_license_expiration' - ), - renameFromRoot( - 'xpack.monitoring.ui.container.elasticsearch.enabled', - 'monitoring.ui.container.elasticsearch.enabled' - ), - renameFromRoot( - 'xpack.monitoring.ui.container.logstash.enabled', - 'monitoring.ui.container.logstash.enabled' - ), - renameFromRoot( - 'xpack.monitoring.tests.cloud_detector.enabled', - 'monitoring.tests.cloud_detector.enabled' - ), - renameFromRoot( - 'xpack.monitoring.kibana.collection.interval', - 'monitoring.kibana.collection.interval' - ), - renameFromRoot('xpack.monitoring.elasticsearch.hosts', 'monitoring.ui.elasticsearch.hosts'), - renameFromRoot('xpack.monitoring.elasticsearch.username', 'monitoring.ui.elasticsearch.username'), - renameFromRoot('xpack.monitoring.elasticsearch.password', 'monitoring.ui.elasticsearch.password'), - renameFromRoot( - 'xpack.monitoring.xpack_api_polling_frequency_millis', - 'monitoring.xpack_api_polling_frequency_millis' - ), - renameFromRoot( - 'xpack.monitoring.cluster_alerts.email_notifications.enabled', - 'monitoring.cluster_alerts.email_notifications.enabled' - ), - renameFromRoot( - 'xpack.monitoring.cluster_alerts.email_notifications.email_address', - 'monitoring.cluster_alerts.email_notifications.email_address' - ), - renameFromRoot('xpack.monitoring.ccs.enabled', 'monitoring.ui.ccs.enabled'), - renameFromRoot( - 'xpack.monitoring.elasticsearch.logFetchCount', - 'monitoring.ui.elasticsearch.logFetchCount' - ), configPathDeprecation, dataPathDeprecation, rewriteBasePathDeprecation, diff --git a/src/core/server/index.ts b/src/core/server/index.ts index ef57fae159d7e..86192245bd2d1 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -230,6 +230,7 @@ export { SavedObjectsMigrationLogger, SavedObjectsRawDoc, SavedObjectSanitizedDoc, + SavedObjectUnsanitizedDoc, SavedObjectsRepositoryFactory, SavedObjectsResolveImportErrorsOptions, SavedObjectsSchema, diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 7ca5c75f19e8f..6369720ada2c3 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -1679,8 +1679,6 @@ export interface SavedObjectMigrationContext { log: SavedObjectsMigrationLogger; } -// Warning: (ae-forgotten-export) The symbol "SavedObjectUnsanitizedDoc" needs to be exported by the entry point index.d.ts -// // @public export type SavedObjectMigrationFn = (doc: SavedObjectUnsanitizedDoc, context: SavedObjectMigrationContext) => SavedObjectUnsanitizedDoc; @@ -2314,6 +2312,9 @@ export class SavedObjectTypeRegistry { registerType(type: SavedObjectsType): void; } +// @public +export type SavedObjectUnsanitizedDoc = SavedObjectDoc & Partial; + // @public export type ScopeableRequest = KibanaRequest | LegacyRequest | FakeRequest; diff --git a/src/dev/build/tasks/build_kibana_platform_plugins.js b/src/dev/build/tasks/build_kibana_platform_plugins.js index 101d6bd15fc10..79cd698e4782c 100644 --- a/src/dev/build/tasks/build_kibana_platform_plugins.js +++ b/src/dev/build/tasks/build_kibana_platform_plugins.js @@ -29,6 +29,7 @@ export const BuildKibanaPlatformPluginsTask = { examples: false, watch: false, dist: true, + includeCoreBundle: true, }); await runOptimizer(optimizerConfig) diff --git a/src/dev/build/tasks/nodejs_modules/clean_client_modules_on_dll_task.js b/src/dev/build/tasks/nodejs_modules/clean_client_modules_on_dll_task.js index 52928d6e47fc4..8e8d69a4dfefa 100644 --- a/src/dev/build/tasks/nodejs_modules/clean_client_modules_on_dll_task.js +++ b/src/dev/build/tasks/nodejs_modules/clean_client_modules_on_dll_task.js @@ -48,12 +48,12 @@ export const CleanClientModulesOnDLLTask = { ]; const discoveredLegacyCorePluginEntries = await globby([ `${baseDir}/src/legacy/core_plugins/*/index.js`, - // Small exception to load dynamically discovered functions for timelion plugin - `${baseDir}/src/legacy/core_plugins/timelion/server/*_functions/**/*.js`, `!${baseDir}/src/legacy/core_plugins/**/public`, ]); const discoveredPluginEntries = await globby([ `${baseDir}/src/plugins/*/server/index.js`, + // Small exception to load dynamically discovered functions for timelion plugin + `${baseDir}/src/plugins/vis_type_timelion/server/*_functions/**/*.js`, `!${baseDir}/src/plugins/**/public`, ]); const discoveredNewPlatformXpackPlugins = await globby([ diff --git a/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker b/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker index 38acfb15d3ece..eb7a121c2e64b 100755 --- a/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker +++ b/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker @@ -72,6 +72,22 @@ kibana_vars=( map.tilemap.options.minZoom map.tilemap.options.subdomains map.tilemap.url + monitoring.cluster_alerts.email_notifications.email_address + monitoring.enabled + monitoring.kibana.collection.enabled + monitoring.kibana.collection.interval + monitoring.ui.container.elasticsearch.enabled + monitoring.ui.container.logstash.enabled + monitoring.ui.elasticsearch.password + monitoring.ui.elasticsearch.pingTimeout + monitoring.ui.elasticsearch.hosts + monitoring.ui.elasticsearch.username + monitoring.ui.elasticsearch.logFetchCount + monitoring.ui.elasticsearch.ssl.certificateAuthorities + monitoring.ui.elasticsearch.ssl.verificationMode + monitoring.ui.enabled + monitoring.ui.max_bucket_size + monitoring.ui.min_interval_seconds newsfeed.enabled ops.interval path.data @@ -160,25 +176,6 @@ kibana_vars=( xpack.infra.sources.default.metricAlias xpack.license_management.enabled xpack.ml.enabled - xpack.monitoring.cluster_alerts.email_notifications.email_address - xpack.monitoring.elasticsearch.password - xpack.monitoring.elasticsearch.pingTimeout - xpack.monitoring.elasticsearch.hosts - xpack.monitoring.elasticsearch.username - xpack.monitoring.elasticsearch.logFetchCount - xpack.monitoring.elasticsearch.ssl.certificateAuthorities - xpack.monitoring.elasticsearch.ssl.verificationMode - xpack.monitoring.enabled - xpack.monitoring.kibana.collection.enabled - xpack.monitoring.kibana.collection.interval - xpack.monitoring.max_bucket_size - xpack.monitoring.min_interval_seconds - xpack.monitoring.node_resolver - xpack.monitoring.report_stats - xpack.monitoring.elasticsearch.pingTimeout - xpack.monitoring.ui.container.elasticsearch.enabled - xpack.monitoring.ui.container.logstash.enabled - xpack.monitoring.ui.enabled xpack.reporting.capture.browser.autoDownload xpack.reporting.capture.browser.chromium.disableSandbox xpack.reporting.capture.browser.chromium.inspect @@ -197,10 +194,14 @@ kibana_vars=( xpack.reporting.csv.checkForFormulas xpack.reporting.csv.escapeFormulaValues xpack.reporting.csv.enablePanelActionDownload + xpack.reporting.csv.useByteOrderMarkEncoding xpack.reporting.csv.maxSizeBytes xpack.reporting.csv.scroll.duration xpack.reporting.csv.scroll.size xpack.reporting.capture.maxAttempts + xpack.reporting.capture.timeouts.openUrl + xpack.reporting.capture.timeouts.waitForElements + xpack.reporting.capture.timeouts.renderComplete xpack.reporting.enabled xpack.reporting.encryptionKey xpack.reporting.index diff --git a/src/dev/build/tasks/os_packages/docker_generator/templates/kibana_yml.template.js b/src/dev/build/tasks/os_packages/docker_generator/templates/kibana_yml.template.js index 69df33f7f2e11..c80f9334cfaeb 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/templates/kibana_yml.template.js +++ b/src/dev/build/tasks/os_packages/docker_generator/templates/kibana_yml.template.js @@ -24,12 +24,12 @@ function generator({ imageFlavor }) { # # ** THIS IS AN AUTO-GENERATED FILE ** # - + # Default Kibana configuration for docker target server.name: kibana server.host: "0" elasticsearch.hosts: [ "http://elasticsearch:9200" ] - ${!imageFlavor ? 'xpack.monitoring.ui.container.elasticsearch.enabled: true' : ''} + ${!imageFlavor ? 'monitoring.ui.container.elasticsearch.enabled: true' : ''} `); } diff --git a/src/dev/precommit_hook/casing_check_config.js b/src/dev/precommit_hook/casing_check_config.js index 1b5110a61cbc4..a75a6997a8cb2 100644 --- a/src/dev/precommit_hook/casing_check_config.js +++ b/src/dev/precommit_hook/casing_check_config.js @@ -116,11 +116,6 @@ export const TEMPORARILY_IGNORED_PATHS = [ 'src/legacy/core_plugins/tile_map/public/__tests__/scaledCircleMarkers.png', 'src/legacy/core_plugins/tile_map/public/__tests__/shadedCircleMarkers.png', 'src/legacy/core_plugins/tile_map/public/__tests__/shadedGeohashGrid.png', - 'src/legacy/core_plugins/timelion/server/lib/asSorted.js', - 'src/legacy/core_plugins/timelion/server/lib/unzipPairs.js', - 'src/legacy/core_plugins/timelion/server/series_functions/__tests__/fixtures/bucketList.js', - 'src/legacy/core_plugins/timelion/server/series_functions/__tests__/fixtures/seriesList.js', - 'src/legacy/core_plugins/timelion/server/series_functions/__tests__/fixtures/tlConfig.js', 'src/fixtures/config_upgrade_from_4.0.0_to_4.0.1-snapshot.json', 'src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/terms/_seriesMultiple.js', 'src/core/server/core_app/assets/favicons/android-chrome-192x192.png', diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/__snapshots__/controls_tab.test.tsx.snap b/src/legacy/core_plugins/input_control_vis/public/components/editor/__snapshots__/controls_tab.test.tsx.snap index 249f42a6ebf3f..72cb399777b77 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/__snapshots__/controls_tab.test.tsx.snap +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/__snapshots__/controls_tab.test.tsx.snap @@ -40,6 +40,11 @@ exports[`renders ControlsTab 1`] = ` "timefilter": Object {}, }, }, + "search": Object { + "searchSource": Object { + "create": [MockFunction], + }, + }, }, } } @@ -96,6 +101,11 @@ exports[`renders ControlsTab 1`] = ` "timefilter": Object {}, }, }, + "search": Object { + "searchSource": Object { + "create": [MockFunction], + }, + }, }, } } diff --git a/src/legacy/core_plugins/input_control_vis/public/components/vis/__snapshots__/list_control.test.tsx.snap b/src/legacy/core_plugins/input_control_vis/public/components/vis/__snapshots__/list_control.test.tsx.snap index 59ae99260cecd..43e2af6d099e8 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/vis/__snapshots__/list_control.test.tsx.snap +++ b/src/legacy/core_plugins/input_control_vis/public/components/vis/__snapshots__/list_control.test.tsx.snap @@ -46,6 +46,7 @@ exports[`renders ListControl 1`] = ` placeholder="Select..." selectedOptions={Array []} singleSelection={false} + sortMatchesBy="none" /> `; diff --git a/src/legacy/core_plugins/input_control_vis/public/control/control.test.ts b/src/legacy/core_plugins/input_control_vis/public/control/control.test.ts index e76b199a0262c..a2d220c14a3f7 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/control.test.ts +++ b/src/legacy/core_plugins/input_control_vis/public/control/control.test.ts @@ -21,7 +21,6 @@ import expect from '@kbn/expect'; import { Control } from './control'; import { ControlParams } from '../editor_utils'; import { FilterManager as BaseFilterManager } from './filter_manager/filter_manager'; -import { SearchSource } from '../legacy_imports'; function createControlParams(id: string, label: string): ControlParams { return { @@ -51,18 +50,12 @@ class ControlMock extends Control { destroy() {} } -const mockKbnApi: SearchSource = {} as SearchSource; describe('hasChanged', () => { let control: ControlMock; beforeEach(() => { - control = new ControlMock( - createControlParams('3', 'control'), - mockFilterManager, - false, - mockKbnApi - ); + control = new ControlMock(createControlParams('3', 'control'), mockFilterManager, false); }); afterEach(() => { @@ -93,20 +86,17 @@ describe('ancestors', () => { grandParentControl = new ControlMock( createControlParams('1', 'grandparent control'), mockFilterManager, - false, - mockKbnApi + false ); parentControl = new ControlMock( createControlParams('2', 'parent control'), mockFilterManager, - false, - mockKbnApi + false ); childControl = new ControlMock( createControlParams('3', 'child control'), mockFilterManager, - false, - mockKbnApi + false ); }); diff --git a/src/legacy/core_plugins/input_control_vis/public/control/control.ts b/src/legacy/core_plugins/input_control_vis/public/control/control.ts index 6fddef777f73e..62e0090e466c0 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/control.ts +++ b/src/legacy/core_plugins/input_control_vis/public/control/control.ts @@ -23,7 +23,6 @@ import _ from 'lodash'; import { i18n } from '@kbn/i18n'; import { Filter } from '../../../../../plugins/data/public'; -import { SearchSource as SearchSourceClass } from '../legacy_imports'; import { ControlParams, ControlParamsOptions, CONTROL_TYPES } from '../editor_utils'; import { RangeFilterManager } from './filter_manager/range_filter_manager'; import { PhraseFilterManager } from './filter_manager/phrase_filter_manager'; @@ -61,8 +60,7 @@ export abstract class Control { constructor( public controlParams: ControlParams, public filterManager: FilterManager, - public useTimeFilter: boolean, - public SearchSource: SearchSourceClass + public useTimeFilter: boolean ) { this.id = controlParams.id; this.controlParams = controlParams; diff --git a/src/legacy/core_plugins/input_control_vis/public/control/create_search_source.ts b/src/legacy/core_plugins/input_control_vis/public/control/create_search_source.ts index f238a2287ecdb..8f86232f63be7 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/create_search_source.ts +++ b/src/legacy/core_plugins/input_control_vis/public/control/create_search_source.ts @@ -17,11 +17,16 @@ * under the License. */ -import { PhraseFilter, IndexPattern, TimefilterContract } from '../../../../../plugins/data/public'; -import { SearchSource as SearchSourceClass, SearchSourceFields } from '../legacy_imports'; +import { + SearchSourceFields, + PhraseFilter, + IndexPattern, + TimefilterContract, + DataPublicPluginStart, +} from '../../../../../plugins/data/public'; export function createSearchSource( - SearchSource: SearchSourceClass, + { create }: DataPublicPluginStart['search']['searchSource'], initialState: SearchSourceFields | null, indexPattern: IndexPattern, aggs: any, @@ -29,7 +34,8 @@ export function createSearchSource( filters: PhraseFilter[] = [], timefilter: TimefilterContract ) { - const searchSource = initialState ? new SearchSource(initialState) : new SearchSource(); + const searchSource = create(initialState || {}); + // Do not not inherit from rootSearchSource to avoid picking up time and globals searchSource.setParent(undefined); searchSource.setField('filter', () => { diff --git a/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.test.ts b/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.test.ts index e6426e5a4c69d..72070175a233c 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.test.ts +++ b/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.test.ts @@ -21,98 +21,49 @@ import { listControlFactory, ListControl } from './list_control_factory'; import { ControlParams, CONTROL_TYPES } from '../editor_utils'; import { getDepsMock, getSearchSourceMock } from '../test_utils'; -const MockSearchSource = getSearchSourceMock(); -const deps = getDepsMock(); - -jest.doMock('./create_search_source.ts', () => ({ - createSearchSource: MockSearchSource, -})); - -describe('hasValue', () => { - const controlParams: ControlParams = { - id: '1', - fieldName: 'myField', - options: {} as any, - type: CONTROL_TYPES.LIST, - label: 'test', - indexPattern: {} as any, - parent: 'parent', - }; - const useTimeFilter = false; - - let listControl: ListControl; - beforeEach(async () => { - listControl = await listControlFactory(controlParams, useTimeFilter, MockSearchSource, deps); +describe('listControlFactory', () => { + const searchSourceMock = getSearchSourceMock(); + const deps = getDepsMock({ + searchSource: { + create: searchSourceMock, + }, }); - test('should be false when control has no value', () => { - expect(listControl.hasValue()).toBe(false); - }); + describe('hasValue', () => { + const controlParams: ControlParams = { + id: '1', + fieldName: 'myField', + options: {} as any, + type: CONTROL_TYPES.LIST, + label: 'test', + indexPattern: {} as any, + parent: 'parent', + }; + const useTimeFilter = false; - test('should be true when control has value', () => { - listControl.set([{ value: 'selected option', label: 'selection option' }]); - expect(listControl.hasValue()).toBe(true); - }); + let listControl: ListControl; + beforeEach(async () => { + listControl = await listControlFactory(controlParams, useTimeFilter, deps); + }); - test('should be true when control has value that is the string "false"', () => { - listControl.set([{ value: 'false', label: 'selection option' }]); - expect(listControl.hasValue()).toBe(true); - }); -}); + test('should be false when control has no value', () => { + expect(listControl.hasValue()).toBe(false); + }); -describe('fetch', () => { - const controlParams: ControlParams = { - id: '1', - fieldName: 'myField', - options: {} as any, - type: CONTROL_TYPES.LIST, - label: 'test', - indexPattern: {} as any, - parent: 'parent', - }; - const useTimeFilter = false; - - let listControl: ListControl; - beforeEach(async () => { - listControl = await listControlFactory(controlParams, useTimeFilter, MockSearchSource, deps); - }); + test('should be true when control has value', () => { + listControl.set([{ value: 'selected option', label: 'selection option' }]); + expect(listControl.hasValue()).toBe(true); + }); - test('should pass in timeout parameters from injected vars', async () => { - await listControl.fetch(); - expect(MockSearchSource).toHaveBeenCalledWith({ - timeout: `1000ms`, - terminate_after: 100000, + test('should be true when control has value that is the string "false"', () => { + listControl.set([{ value: 'false', label: 'selection option' }]); + expect(listControl.hasValue()).toBe(true); }); }); - test('should set selectOptions to results of terms aggregation', async () => { - await listControl.fetch(); - expect(listControl.selectOptions).toEqual([ - 'Zurich Airport', - 'Xi an Xianyang International Airport', - ]); - }); -}); - -describe('fetch with ancestors', () => { - const controlParams: ControlParams = { - id: '1', - fieldName: 'myField', - options: {} as any, - type: CONTROL_TYPES.LIST, - label: 'test', - indexPattern: {} as any, - parent: 'parent', - }; - const useTimeFilter = false; - - let listControl: ListControl; - let parentControl; - beforeEach(async () => { - listControl = await listControlFactory(controlParams, useTimeFilter, MockSearchSource, deps); - - const parentControlParams: ControlParams = { - id: 'parent', + describe('fetch', () => { + const controlParams: ControlParams = { + id: '1', fieldName: 'myField', options: {} as any, type: CONTROL_TYPES.LIST, @@ -120,26 +71,72 @@ describe('fetch with ancestors', () => { indexPattern: {} as any, parent: 'parent', }; - parentControl = await listControlFactory( - parentControlParams, - useTimeFilter, - MockSearchSource, - deps - ); - parentControl.clear(); - listControl.setAncestors([parentControl]); - }); + const useTimeFilter = false; + + let listControl: ListControl; + beforeEach(async () => { + listControl = await listControlFactory(controlParams, useTimeFilter, deps); + }); - describe('ancestor does not have value', () => { - test('should disable control', async () => { + test('should pass in timeout parameters from injected vars', async () => { await listControl.fetch(); - expect(listControl.isEnabled()).toBe(false); + expect(searchSourceMock).toHaveBeenCalledWith({ + timeout: `1000ms`, + terminate_after: 100000, + }); }); - test('should reset lastAncestorValues', async () => { - listControl.lastAncestorValues = 'last ancestor value'; + test('should set selectOptions to results of terms aggregation', async () => { await listControl.fetch(); - expect(listControl.lastAncestorValues).toBeUndefined(); + expect(listControl.selectOptions).toEqual([ + 'Zurich Airport', + 'Xi an Xianyang International Airport', + ]); + }); + }); + + describe('fetch with ancestors', () => { + const controlParams: ControlParams = { + id: '1', + fieldName: 'myField', + options: {} as any, + type: CONTROL_TYPES.LIST, + label: 'test', + indexPattern: {} as any, + parent: 'parent', + }; + const useTimeFilter = false; + + let listControl: ListControl; + let parentControl; + beforeEach(async () => { + listControl = await listControlFactory(controlParams, useTimeFilter, deps); + + const parentControlParams: ControlParams = { + id: 'parent', + fieldName: 'myField', + options: {} as any, + type: CONTROL_TYPES.LIST, + label: 'test', + indexPattern: {} as any, + parent: 'parent', + }; + parentControl = await listControlFactory(parentControlParams, useTimeFilter, deps); + parentControl.clear(); + listControl.setAncestors([parentControl]); + }); + + describe('ancestor does not have value', () => { + test('should disable control', async () => { + await listControl.fetch(); + expect(listControl.isEnabled()).toBe(false); + }); + + test('should reset lastAncestorValues', async () => { + listControl.lastAncestorValues = 'last ancestor value'; + await listControl.fetch(); + expect(listControl.lastAncestorValues).toBeUndefined(); + }); }); }); }); diff --git a/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.ts b/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.ts index 8364c82efecdb..4b2b1d751ffc7 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.ts +++ b/src/legacy/core_plugins/input_control_vis/public/control/list_control_factory.ts @@ -19,14 +19,17 @@ import _ from 'lodash'; import { i18n } from '@kbn/i18n'; - -import { SearchSource as SearchSourceClass, SearchSourceFields } from '../legacy_imports'; import { Control, noValuesDisableMsg, noIndexPatternMsg } from './control'; import { PhraseFilterManager } from './filter_manager/phrase_filter_manager'; import { createSearchSource } from './create_search_source'; import { ControlParams } from '../editor_utils'; import { InputControlVisDependencies } from '../plugin'; -import { IFieldType, TimefilterContract } from '../../../../../plugins/data/public'; +import { + IFieldType, + TimefilterContract, + SearchSourceFields, + DataPublicPluginStart, +} from '../../../../../plugins/data/public'; function getEscapedQuery(query = '') { // https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-regexp-query.html#_standard_operators @@ -75,6 +78,7 @@ const termsAgg = ({ field, size, direction, query }: TermsAggArgs) => { export class ListControl extends Control { private getInjectedVar: InputControlVisDependencies['core']['injectedMetadata']['getInjectedVar']; private timefilter: TimefilterContract; + private searchSource: DataPublicPluginStart['search']['searchSource']; abortController?: AbortController; lastAncestorValues: any; @@ -86,12 +90,13 @@ export class ListControl extends Control { controlParams: ControlParams, filterManager: PhraseFilterManager, useTimeFilter: boolean, - SearchSource: SearchSourceClass, + searchSource: DataPublicPluginStart['search']['searchSource'], deps: InputControlVisDependencies ) { - super(controlParams, filterManager, useTimeFilter, SearchSource); + super(controlParams, filterManager, useTimeFilter); this.getInjectedVar = deps.core.injectedMetadata.getInjectedVar; this.timefilter = deps.data.query.timefilter.timefilter; + this.searchSource = searchSource; } fetch = async (query?: string) => { @@ -143,7 +148,7 @@ export class ListControl extends Control { query, }); const searchSource = createSearchSource( - this.SearchSource, + this.searchSource, initialSearchSourceState, indexPattern, aggs, @@ -202,7 +207,6 @@ export class ListControl extends Control { export async function listControlFactory( controlParams: ControlParams, useTimeFilter: boolean, - SearchSource: SearchSourceClass, deps: InputControlVisDependencies ) { const [, { data: dataPluginStart }] = await deps.core.getStartServices(); @@ -225,7 +229,7 @@ export async function listControlFactory( deps.data.query.filterManager ), useTimeFilter, - SearchSource, + dataPluginStart.search.searchSource, deps ); return listControl; diff --git a/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.test.ts b/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.test.ts index 32df9de8ac983..084c02e138a2d 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.test.ts +++ b/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.test.ts @@ -21,71 +21,77 @@ import { rangeControlFactory } from './range_control_factory'; import { ControlParams, CONTROL_TYPES } from '../editor_utils'; import { getDepsMock, getSearchSourceMock } from '../test_utils'; -const deps = getDepsMock(); +describe('rangeControlFactory', () => { + describe('fetch', () => { + const controlParams: ControlParams = { + id: '1', + fieldName: 'myNumberField', + options: {}, + type: CONTROL_TYPES.RANGE, + label: 'test', + indexPattern: {} as any, + parent: {} as any, + }; + const useTimeFilter = false; -describe('fetch', () => { - const controlParams: ControlParams = { - id: '1', - fieldName: 'myNumberField', - options: {}, - type: CONTROL_TYPES.RANGE, - label: 'test', - indexPattern: {} as any, - parent: {} as any, - }; - const useTimeFilter = false; + test('should set min and max from aggregation results', async () => { + const esSearchResponse = { + aggregations: { + maxAgg: { value: 100 }, + minAgg: { value: 10 }, + }, + }; + const searchSourceMock = getSearchSourceMock(esSearchResponse); + const deps = getDepsMock({ + searchSource: { + create: searchSourceMock, + }, + }); - test('should set min and max from aggregation results', async () => { - const esSearchResponse = { - aggregations: { - maxAgg: { value: 100 }, - minAgg: { value: 10 }, - }, - }; - const rangeControl = await rangeControlFactory( - controlParams, - useTimeFilter, - getSearchSourceMock(esSearchResponse), - deps - ); - await rangeControl.fetch(); + const rangeControl = await rangeControlFactory(controlParams, useTimeFilter, deps); + await rangeControl.fetch(); - expect(rangeControl.isEnabled()).toBe(true); - expect(rangeControl.min).toBe(10); - expect(rangeControl.max).toBe(100); - }); + expect(rangeControl.isEnabled()).toBe(true); + expect(rangeControl.min).toBe(10); + expect(rangeControl.max).toBe(100); + }); - test('should disable control when there are 0 hits', async () => { - // ES response when the query does not match any documents - const esSearchResponse = { - aggregations: { - maxAgg: { value: null }, - minAgg: { value: null }, - }, - }; - const rangeControl = await rangeControlFactory( - controlParams, - useTimeFilter, - getSearchSourceMock(esSearchResponse), - deps - ); - await rangeControl.fetch(); + test('should disable control when there are 0 hits', async () => { + // ES response when the query does not match any documents + const esSearchResponse = { + aggregations: { + maxAgg: { value: null }, + minAgg: { value: null }, + }, + }; + const searchSourceMock = getSearchSourceMock(esSearchResponse); + const deps = getDepsMock({ + searchSource: { + create: searchSourceMock, + }, + }); - expect(rangeControl.isEnabled()).toBe(false); - }); + const rangeControl = await rangeControlFactory(controlParams, useTimeFilter, deps); + await rangeControl.fetch(); + + expect(rangeControl.isEnabled()).toBe(false); + }); + + test('should disable control when response is empty', async () => { + // ES response for dashboardonly user who does not have read permissions on index is 200 (which is weird) + // and there is not aggregations key + const esSearchResponse = {}; + const searchSourceMock = getSearchSourceMock(esSearchResponse); + const deps = getDepsMock({ + searchSource: { + create: searchSourceMock, + }, + }); - test('should disable control when response is empty', async () => { - // ES response for dashboardonly user who does not have read permissions on index is 200 (which is weird) - // and there is not aggregations key - const esSearchResponse = {}; - const rangeControl = await rangeControlFactory( - controlParams, - useTimeFilter, - getSearchSourceMock(esSearchResponse), - deps - ); - await rangeControl.fetch(); + const rangeControl = await rangeControlFactory(controlParams, useTimeFilter, deps); + await rangeControl.fetch(); - expect(rangeControl.isEnabled()).toBe(false); + expect(rangeControl.isEnabled()).toBe(false); + }); }); }); diff --git a/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.ts b/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.ts index d9b43c9dff201..5f3c9994ef353 100644 --- a/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.ts +++ b/src/legacy/core_plugins/input_control_vis/public/control/range_control_factory.ts @@ -20,13 +20,16 @@ import _ from 'lodash'; import { i18n } from '@kbn/i18n'; -import { SearchSource as SearchSourceClass } from '../legacy_imports'; import { Control, noValuesDisableMsg, noIndexPatternMsg } from './control'; import { RangeFilterManager } from './filter_manager/range_filter_manager'; import { createSearchSource } from './create_search_source'; import { ControlParams } from '../editor_utils'; import { InputControlVisDependencies } from '../plugin'; -import { IFieldType, TimefilterContract } from '../.../../../../../../plugins/data/public'; +import { + IFieldType, + TimefilterContract, + DataPublicPluginStart, +} from '../.../../../../../../plugins/data/public'; const minMaxAgg = (field?: IFieldType) => { const aggBody: any = {}; @@ -52,6 +55,8 @@ const minMaxAgg = (field?: IFieldType) => { }; export class RangeControl extends Control { + private searchSource: DataPublicPluginStart['search']['searchSource']; + timefilter: TimefilterContract; abortController: any; min: any; @@ -61,11 +66,12 @@ export class RangeControl extends Control { controlParams: ControlParams, filterManager: RangeFilterManager, useTimeFilter: boolean, - SearchSource: SearchSourceClass, + searchSource: DataPublicPluginStart['search']['searchSource'], deps: InputControlVisDependencies ) { - super(controlParams, filterManager, useTimeFilter, SearchSource); + super(controlParams, filterManager, useTimeFilter); this.timefilter = deps.data.query.timefilter.timefilter; + this.searchSource = searchSource; } async fetch() { @@ -83,7 +89,7 @@ export class RangeControl extends Control { const fieldName = this.filterManager.fieldName; const aggs = minMaxAgg(indexPattern.fields.getByName(fieldName)); const searchSource = createSearchSource( - this.SearchSource, + this.searchSource, null, indexPattern, aggs, @@ -129,7 +135,6 @@ export class RangeControl extends Control { export async function rangeControlFactory( controlParams: ControlParams, useTimeFilter: boolean, - SearchSource: SearchSourceClass, deps: InputControlVisDependencies ): Promise { const [, { data: dataPluginStart }] = await deps.core.getStartServices(); @@ -144,7 +149,7 @@ export async function rangeControlFactory( deps.data.query.filterManager ), useTimeFilter, - SearchSource, + dataPluginStart.search.searchSource, deps ); } diff --git a/src/legacy/core_plugins/input_control_vis/public/input_control_fn.test.ts b/src/legacy/core_plugins/input_control_vis/public/input_control_fn.test.ts index acc214ed31180..d654acefd0550 100644 --- a/src/legacy/core_plugins/input_control_vis/public/input_control_fn.test.ts +++ b/src/legacy/core_plugins/input_control_vis/public/input_control_fn.test.ts @@ -22,8 +22,6 @@ import { createInputControlVisFn } from './input_control_fn'; // eslint-disable-next-line import { functionWrapper } from '../../../../plugins/expressions/common/expression_functions/specs/tests/utils'; -jest.mock('./legacy_imports.ts'); - describe('interpreter/functions#input_control_vis', () => { const fn = functionWrapper(createInputControlVisFn()); const visConfig = { @@ -48,8 +46,9 @@ describe('interpreter/functions#input_control_vis', () => { pinFilters: false, }; - it('returns an object with the correct structure', async () => { + test('returns an object with the correct structure', async () => { const actual = await fn(null, { visConfig: JSON.stringify(visConfig) }); + expect(actual).toMatchSnapshot(); }); }); diff --git a/src/legacy/core_plugins/input_control_vis/public/test_utils/get_deps_mock.tsx b/src/legacy/core_plugins/input_control_vis/public/test_utils/get_deps_mock.tsx index 78a4ef3a5597a..efd7d4c020854 100644 --- a/src/legacy/core_plugins/input_control_vis/public/test_utils/get_deps_mock.tsx +++ b/src/legacy/core_plugins/input_control_vis/public/test_utils/get_deps_mock.tsx @@ -19,6 +19,7 @@ import React from 'react'; import { InputControlVisDependencies } from '../plugin'; +import { getSearchSourceMock } from './get_search_service_mock'; const fields = [] as any; fields.push({ name: 'myField' } as any); @@ -26,13 +27,20 @@ fields.getByName = (name: any) => { return fields.find(({ name: n }: { name: string }) => n === name); }; -export const getDepsMock = (): InputControlVisDependencies => +export const getDepsMock = ({ + searchSource = { + create: getSearchSourceMock(), + }, +} = {}): InputControlVisDependencies => ({ core: { getStartServices: jest.fn().mockReturnValue([ null, { data: { + search: { + searchSource, + }, ui: { IndexPatternSelect: () => (
) as any, }, @@ -58,6 +66,11 @@ export const getDepsMock = (): InputControlVisDependencies => }, }, data: { + search: { + searchSource: { + create: getSearchSourceMock(), + }, + }, query: { filterManager: { fieldName: 'myField', diff --git a/src/legacy/core_plugins/input_control_vis/public/test_utils/get_search_service_mock.ts b/src/legacy/core_plugins/input_control_vis/public/test_utils/get_search_service_mock.ts index 94a460086e9da..24b7d7bcbb5c1 100644 --- a/src/legacy/core_plugins/input_control_vis/public/test_utils/get_search_service_mock.ts +++ b/src/legacy/core_plugins/input_control_vis/public/test_utils/get_search_service_mock.ts @@ -17,9 +17,7 @@ * under the License. */ -import { SearchSource } from '../legacy_imports'; - -export const getSearchSourceMock = (esSearchResponse?: any): SearchSource => +export const getSearchSourceMock = (esSearchResponse?: any) => jest.fn().mockImplementation(() => ({ setParent: jest.fn(), setField: jest.fn(), diff --git a/src/legacy/core_plugins/input_control_vis/public/vis_controller.tsx b/src/legacy/core_plugins/input_control_vis/public/vis_controller.tsx index c4a7d286850e3..818221353afbc 100644 --- a/src/legacy/core_plugins/input_control_vis/public/vis_controller.tsx +++ b/src/legacy/core_plugins/input_control_vis/public/vis_controller.tsx @@ -21,8 +21,6 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { I18nStart } from 'kibana/public'; -import { SearchSource } from './legacy_imports'; - import { InputControlVis } from './components/vis/input_control_vis'; import { getControlFactory } from './control/control_factory'; import { getLineageMap } from './lineage'; @@ -102,7 +100,8 @@ export const createInputControlVisController = (deps: InputControlVisDependencie const controlFactoryPromises = controlParamsList.map(controlParams => { const factory = getControlFactory(controlParams); - return factory(controlParams, this.visParams?.useTimeFilter, SearchSource, deps); + + return factory(controlParams, this.visParams?.useTimeFilter, deps); }); const controls = await Promise.all(controlFactoryPromises); diff --git a/src/legacy/core_plugins/kibana/index.js b/src/legacy/core_plugins/kibana/index.js index 989583742acd0..8465d71e1e998 100644 --- a/src/legacy/core_plugins/kibana/index.js +++ b/src/legacy/core_plugins/kibana/index.js @@ -21,7 +21,6 @@ import Fs from 'fs'; import { resolve } from 'path'; import { promisify } from 'util'; -import { migrations } from './migrations'; import { importApi } from './server/routes/api/import'; import { exportApi } from './server/routes/api/export'; import mappings from './mappings.json'; @@ -120,23 +119,6 @@ export default function(kibana) { ], savedObjectsManagement: { - dashboard: { - icon: 'dashboardApp', - defaultSearchField: 'title', - isImportableAndExportable: true, - getTitle(obj) { - return obj.attributes.title; - }, - getEditUrl(obj) { - return `/management/kibana/objects/savedDashboards/${encodeURIComponent(obj.id)}`; - }, - getInAppUrl(obj) { - return { - path: `/app/kibana#/dashboard/${encodeURIComponent(obj.id)}`, - uiCapabilitiesPath: 'dashboard.show', - }; - }, - }, url: { defaultSearchField: 'url', isImportableAndExportable: true, @@ -147,9 +129,6 @@ export default function(kibana) { }, savedObjectSchemas: { - 'sample-data-telemetry': { - isNamespaceAgnostic: true, - }, 'kql-telemetry': { isNamespaceAgnostic: true, }, @@ -177,8 +156,6 @@ export default function(kibana) { mappings, uiSettingDefaults: getUiSettingDefaults(), - - migrations, }, uiCapabilities: async function() { diff --git a/src/legacy/core_plugins/kibana/mappings.json b/src/legacy/core_plugins/kibana/mappings.json index af3f79588552b..febdf2cc3d649 100644 --- a/src/legacy/core_plugins/kibana/mappings.json +++ b/src/legacy/core_plugins/kibana/mappings.json @@ -1,58 +1,4 @@ { - "dashboard": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "optionsJSON": { - "type": "text" - }, - "panelsJSON": { - "type": "text" - }, - "refreshInterval": { - "properties": { - "display": { - "type": "keyword" - }, - "pause": { - "type": "boolean" - }, - "section": { - "type": "integer" - }, - "value": { - "type": "integer" - } - } - }, - "timeFrom": { - "type": "keyword" - }, - "timeRestore": { - "type": "boolean" - }, - "timeTo": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, "url": { "properties": { "accessCount": { @@ -91,15 +37,5 @@ "type": "long" } } - }, - "sample-data-telemetry": { - "properties": { - "installCount": { - "type": "long" - }, - "unInstallCount": { - "type": "long" - } - } } } diff --git a/src/legacy/core_plugins/kibana/migrations/migrations.js b/src/legacy/core_plugins/kibana/migrations/migrations.js deleted file mode 100644 index 029dbde555a4b..0000000000000 --- a/src/legacy/core_plugins/kibana/migrations/migrations.js +++ /dev/null @@ -1,106 +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 { get } from 'lodash'; -import { - migrateMatchAllQuery, - migrations730 as dashboardMigrations730, -} from '../public/dashboard/migrations'; - -function migrateIndexPattern(doc) { - const searchSourceJSON = get(doc, 'attributes.kibanaSavedObjectMeta.searchSourceJSON'); - if (typeof searchSourceJSON !== 'string') { - return; - } - let searchSource; - try { - searchSource = JSON.parse(searchSourceJSON); - } catch (e) { - // Let it go, the data is invalid and we'll leave it as is - return; - } - if (searchSource.index) { - searchSource.indexRefName = 'kibanaSavedObjectMeta.searchSourceJSON.index'; - doc.references.push({ - name: searchSource.indexRefName, - type: 'index-pattern', - id: searchSource.index, - }); - delete searchSource.index; - } - if (searchSource.filter) { - searchSource.filter.forEach((filterRow, i) => { - if (!filterRow.meta || !filterRow.meta.index) { - return; - } - filterRow.meta.indexRefName = `kibanaSavedObjectMeta.searchSourceJSON.filter[${i}].meta.index`; - doc.references.push({ - name: filterRow.meta.indexRefName, - type: 'index-pattern', - id: filterRow.meta.index, - }); - delete filterRow.meta.index; - }); - } - doc.attributes.kibanaSavedObjectMeta.searchSourceJSON = JSON.stringify(searchSource); -} - -export const migrations = { - dashboard: { - '6.7.2': migrateMatchAllQuery, - '7.0.0': doc => { - // Set new "references" attribute - doc.references = doc.references || []; - - // Migrate index pattern - migrateIndexPattern(doc); - // Migrate panels - const panelsJSON = get(doc, 'attributes.panelsJSON'); - if (typeof panelsJSON !== 'string') { - return doc; - } - let panels; - try { - panels = JSON.parse(panelsJSON); - } catch (e) { - // Let it go, the data is invalid and we'll leave it as is - return doc; - } - if (!Array.isArray(panels)) { - return doc; - } - panels.forEach((panel, i) => { - if (!panel.type || !panel.id) { - return; - } - panel.panelRefName = `panel_${i}`; - doc.references.push({ - name: `panel_${i}`, - type: panel.type, - id: panel.id, - }); - delete panel.type; - delete panel.id; - }); - doc.attributes.panelsJSON = JSON.stringify(panels); - return doc; - }, - '7.3.0': dashboardMigrations730, - }, -}; diff --git a/src/legacy/core_plugins/kibana/public/discover/build_services.ts b/src/legacy/core_plugins/kibana/public/discover/build_services.ts index c56e50f3b27ff..b987129a9a7ed 100644 --- a/src/legacy/core_plugins/kibana/public/discover/build_services.ts +++ b/src/legacy/core_plugins/kibana/public/discover/build_services.ts @@ -42,6 +42,7 @@ import { DocViewerComponent, SavedSearch, } from '../../../../../plugins/discover/public'; +import { SavedObjectKibanaServices } from '../../../../../plugins/saved_objects/public'; export interface DiscoverServices { addBasePath: (path: string) => string; @@ -65,12 +66,13 @@ export interface DiscoverServices { uiSettings: IUiSettingsClient; visualizations: VisualizationsStart; } + export async function buildServices( core: CoreStart, plugins: DiscoverStartPlugins, getHistory: () => History ): Promise { - const services = { + const services: SavedObjectKibanaServices = { savedObjectsClient: core.savedObjects.client, indexPatterns: plugins.data.indexPatterns, search: plugins.data.search, diff --git a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts index 156267bdfa87e..3aa552b1da07d 100644 --- a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts @@ -77,7 +77,6 @@ export { IndexPattern, indexPatterns, IFieldType, - SearchSource, ISearchSource, EsQuerySortValue, SortDirection, diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/_stubs.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/_stubs.js index f6ed570be2c37..acd609df203e3 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/_stubs.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/_stubs.js @@ -19,7 +19,6 @@ import sinon from 'sinon'; import moment from 'moment'; -import { SearchSource } from '../../../../../../../../../plugins/data/public'; export function createIndexPatternsStub() { return { @@ -46,17 +45,15 @@ export function createSearchSourceStub(hits, timeField) { }), }; - searchSourceStub.setParent = sinon - .stub(SearchSource.prototype, 'setParent') - .returns(searchSourceStub); - searchSourceStub.setField = sinon - .stub(SearchSource.prototype, 'setField') - .returns(searchSourceStub); - searchSourceStub.getField = sinon.stub(SearchSource.prototype, 'getField').callsFake(key => { + searchSourceStub.setParent = sinon.spy(() => searchSourceStub); + searchSourceStub.setField = sinon.spy(() => searchSourceStub); + + searchSourceStub.getField = sinon.spy(key => { const previousSetCall = searchSourceStub.setField.withArgs(key).lastCall; return previousSetCall ? previousSetCall.args[1] : null; }); - searchSourceStub.fetch = sinon.stub(SearchSource.prototype, 'fetch').callsFake(() => + + searchSourceStub.fetch = sinon.spy(() => Promise.resolve({ hits: { hits: searchSourceStub._stubHits, @@ -65,13 +62,6 @@ export function createSearchSourceStub(hits, timeField) { }) ); - searchSourceStub._restore = () => { - searchSourceStub.setParent.restore(); - searchSourceStub.setField.restore(); - searchSourceStub.getField.restore(); - searchSourceStub.fetch.restore(); - }; - return searchSourceStub; } @@ -81,8 +71,7 @@ export function createSearchSourceStub(hits, timeField) { export function createContextSearchSourceStub(hits, timeField = '@timestamp') { const searchSourceStub = createSearchSourceStub(hits, timeField); - searchSourceStub.fetch.restore(); - searchSourceStub.fetch = sinon.stub(SearchSource.prototype, 'fetch').callsFake(() => { + searchSourceStub.fetch = sinon.spy(() => { const timeField = searchSourceStub._stubTimeField; const lastQuery = searchSourceStub.setField.withArgs('query').lastCall.args[1]; const timeRange = lastQuery.query.constant_score.filter.range[timeField]; @@ -99,6 +88,7 @@ export function createContextSearchSourceStub(hits, timeField = '@timestamp') { moment(hit[timeField]).isSameOrBefore(timeRange.lte) ) .sort(sortFunction); + return Promise.resolve({ hits: { hits: filteredHits, diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/anchor.test.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/anchor.test.js index 0bc2cbacc1eee..757e74589555a 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/anchor.test.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/anchor.test.js @@ -31,10 +31,6 @@ describe('context app', function() { fetchAnchor = fetchAnchorProvider(createIndexPatternsStub(), searchSourceStub); }); - afterEach(() => { - searchSourceStub._restore(); - }); - it('should use the `fetch` method of the SearchSource', function() { return fetchAnchor('INDEX_PATTERN_ID', 'id', [ { '@timestamp': 'desc' }, diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/context.predecessors.test.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/context.predecessors.test.js index d6e91e57b22a8..ebd4af536aabd 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/context.predecessors.test.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/context.predecessors.test.js @@ -21,6 +21,7 @@ import moment from 'moment'; import * as _ from 'lodash'; import { createIndexPatternsStub, createContextSearchSourceStub } from './_stubs'; import { fetchContextProvider } from './context'; +import { setServices } from '../../../../kibana_services'; const MS_PER_DAY = 24 * 60 * 60 * 1000; const ANCHOR_TIMESTAMP = new Date(MS_PER_DAY).toJSON(); @@ -31,10 +32,21 @@ const ANCHOR_TIMESTAMP_3000 = new Date(MS_PER_DAY * 3000).toJSON(); describe('context app', function() { describe('function fetchPredecessors', function() { let fetchPredecessors; - let searchSourceStub; + let mockSearchSource; beforeEach(() => { - searchSourceStub = createContextSearchSourceStub([], '@timestamp', MS_PER_DAY * 8); + mockSearchSource = createContextSearchSourceStub([], '@timestamp', MS_PER_DAY * 8); + + setServices({ + data: { + search: { + searchSource: { + create: jest.fn().mockImplementation(() => mockSearchSource), + }, + }, + }, + }); + fetchPredecessors = ( indexPatternId, timeField, @@ -65,17 +77,13 @@ describe('context app', function() { }; }); - afterEach(() => { - searchSourceStub._restore(); - }); - it('should perform exactly one query when enough hits are returned', function() { - searchSourceStub._stubHits = [ - searchSourceStub._createStubHit(MS_PER_DAY * 3000 + 2), - searchSourceStub._createStubHit(MS_PER_DAY * 3000 + 1), - searchSourceStub._createStubHit(MS_PER_DAY * 3000), - searchSourceStub._createStubHit(MS_PER_DAY * 2000), - searchSourceStub._createStubHit(MS_PER_DAY * 1000), + mockSearchSource._stubHits = [ + mockSearchSource._createStubHit(MS_PER_DAY * 3000 + 2), + mockSearchSource._createStubHit(MS_PER_DAY * 3000 + 1), + mockSearchSource._createStubHit(MS_PER_DAY * 3000), + mockSearchSource._createStubHit(MS_PER_DAY * 2000), + mockSearchSource._createStubHit(MS_PER_DAY * 1000), ]; return fetchPredecessors( @@ -89,18 +97,18 @@ describe('context app', function() { 3, [] ).then(hits => { - expect(searchSourceStub.fetch.calledOnce).toBe(true); - expect(hits).toEqual(searchSourceStub._stubHits.slice(0, 3)); + expect(mockSearchSource.fetch.calledOnce).toBe(true); + expect(hits).toEqual(mockSearchSource._stubHits.slice(0, 3)); }); }); it('should perform multiple queries with the last being unrestricted when too few hits are returned', function() { - searchSourceStub._stubHits = [ - searchSourceStub._createStubHit(MS_PER_DAY * 3010), - searchSourceStub._createStubHit(MS_PER_DAY * 3002), - searchSourceStub._createStubHit(MS_PER_DAY * 3000), - searchSourceStub._createStubHit(MS_PER_DAY * 2998), - searchSourceStub._createStubHit(MS_PER_DAY * 2990), + mockSearchSource._stubHits = [ + mockSearchSource._createStubHit(MS_PER_DAY * 3010), + mockSearchSource._createStubHit(MS_PER_DAY * 3002), + mockSearchSource._createStubHit(MS_PER_DAY * 3000), + mockSearchSource._createStubHit(MS_PER_DAY * 2998), + mockSearchSource._createStubHit(MS_PER_DAY * 2990), ]; return fetchPredecessors( @@ -114,7 +122,7 @@ describe('context app', function() { 6, [] ).then(hits => { - const intervals = searchSourceStub.setField.args + const intervals = mockSearchSource.setField.args .filter(([property]) => property === 'query') .map(([, { query }]) => _.get(query, ['constant_score', 'filter', 'range', '@timestamp']) @@ -129,16 +137,16 @@ describe('context app', function() { expect(Object.keys(_.last(intervals))).toEqual(['format', 'gte']); expect(intervals.length).toBeGreaterThan(1); - expect(hits).toEqual(searchSourceStub._stubHits.slice(0, 3)); + expect(hits).toEqual(mockSearchSource._stubHits.slice(0, 3)); }); }); it('should perform multiple queries until the expected hit count is returned', function() { - searchSourceStub._stubHits = [ - searchSourceStub._createStubHit(MS_PER_DAY * 1700), - searchSourceStub._createStubHit(MS_PER_DAY * 1200), - searchSourceStub._createStubHit(MS_PER_DAY * 1100), - searchSourceStub._createStubHit(MS_PER_DAY * 1000), + mockSearchSource._stubHits = [ + mockSearchSource._createStubHit(MS_PER_DAY * 1700), + mockSearchSource._createStubHit(MS_PER_DAY * 1200), + mockSearchSource._createStubHit(MS_PER_DAY * 1100), + mockSearchSource._createStubHit(MS_PER_DAY * 1000), ]; return fetchPredecessors( @@ -152,7 +160,7 @@ describe('context app', function() { 3, [] ).then(hits => { - const intervals = searchSourceStub.setField.args + const intervals = mockSearchSource.setField.args .filter(([property]) => property === 'query') .map(([, { query }]) => _.get(query, ['constant_score', 'filter', 'range', '@timestamp']) @@ -163,7 +171,7 @@ describe('context app', function() { // should have stopped before reaching MS_PER_DAY * 1700 expect(moment(_.last(intervals).lte).valueOf()).toBeLessThan(MS_PER_DAY * 1700); expect(intervals.length).toBeGreaterThan(1); - expect(hits).toEqual(searchSourceStub._stubHits.slice(-3)); + expect(hits).toEqual(mockSearchSource._stubHits.slice(-3)); }); }); @@ -195,7 +203,7 @@ describe('context app', function() { 3, [] ).then(() => { - const setParentSpy = searchSourceStub.setParent; + const setParentSpy = mockSearchSource.setParent; expect(setParentSpy.alwaysCalledWith(undefined)).toBe(true); expect(setParentSpy.called).toBe(true); }); @@ -214,7 +222,7 @@ describe('context app', function() { [] ).then(() => { expect( - searchSourceStub.setField.calledWith('sort', [{ '@timestamp': 'asc' }, { _doc: 'asc' }]) + mockSearchSource.setField.calledWith('sort', [{ '@timestamp': 'asc' }, { _doc: 'asc' }]) ).toBe(true); }); }); diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/context.successors.test.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/context.successors.test.js index cc2b6d31cb43b..452d0cc9fd1a0 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/context.successors.test.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/context.successors.test.js @@ -21,6 +21,7 @@ import moment from 'moment'; import * as _ from 'lodash'; import { createIndexPatternsStub, createContextSearchSourceStub } from './_stubs'; +import { setServices } from '../../../../kibana_services'; import { fetchContextProvider } from './context'; @@ -32,10 +33,20 @@ const ANCHOR_TIMESTAMP_3000 = new Date(MS_PER_DAY * 3000).toJSON(); describe('context app', function() { describe('function fetchSuccessors', function() { let fetchSuccessors; - let searchSourceStub; + let mockSearchSource; beforeEach(() => { - searchSourceStub = createContextSearchSourceStub([], '@timestamp'); + mockSearchSource = createContextSearchSourceStub([], '@timestamp'); + + setServices({ + data: { + search: { + searchSource: { + create: jest.fn().mockImplementation(() => mockSearchSource), + }, + }, + }, + }); fetchSuccessors = ( indexPatternId, @@ -67,17 +78,13 @@ describe('context app', function() { }; }); - afterEach(() => { - searchSourceStub._restore(); - }); - it('should perform exactly one query when enough hits are returned', function() { - searchSourceStub._stubHits = [ - searchSourceStub._createStubHit(MS_PER_DAY * 5000), - searchSourceStub._createStubHit(MS_PER_DAY * 4000), - searchSourceStub._createStubHit(MS_PER_DAY * 3000), - searchSourceStub._createStubHit(MS_PER_DAY * 3000 - 1), - searchSourceStub._createStubHit(MS_PER_DAY * 3000 - 2), + mockSearchSource._stubHits = [ + mockSearchSource._createStubHit(MS_PER_DAY * 5000), + mockSearchSource._createStubHit(MS_PER_DAY * 4000), + mockSearchSource._createStubHit(MS_PER_DAY * 3000), + mockSearchSource._createStubHit(MS_PER_DAY * 3000 - 1), + mockSearchSource._createStubHit(MS_PER_DAY * 3000 - 2), ]; return fetchSuccessors( @@ -91,18 +98,18 @@ describe('context app', function() { 3, [] ).then(hits => { - expect(searchSourceStub.fetch.calledOnce).toBe(true); - expect(hits).toEqual(searchSourceStub._stubHits.slice(-3)); + expect(mockSearchSource.fetch.calledOnce).toBe(true); + expect(hits).toEqual(mockSearchSource._stubHits.slice(-3)); }); }); it('should perform multiple queries with the last being unrestricted when too few hits are returned', function() { - searchSourceStub._stubHits = [ - searchSourceStub._createStubHit(MS_PER_DAY * 3010), - searchSourceStub._createStubHit(MS_PER_DAY * 3002), - searchSourceStub._createStubHit(MS_PER_DAY * 3000), - searchSourceStub._createStubHit(MS_PER_DAY * 2998), - searchSourceStub._createStubHit(MS_PER_DAY * 2990), + mockSearchSource._stubHits = [ + mockSearchSource._createStubHit(MS_PER_DAY * 3010), + mockSearchSource._createStubHit(MS_PER_DAY * 3002), + mockSearchSource._createStubHit(MS_PER_DAY * 3000), + mockSearchSource._createStubHit(MS_PER_DAY * 2998), + mockSearchSource._createStubHit(MS_PER_DAY * 2990), ]; return fetchSuccessors( @@ -116,7 +123,7 @@ describe('context app', function() { 6, [] ).then(hits => { - const intervals = searchSourceStub.setField.args + const intervals = mockSearchSource.setField.args .filter(([property]) => property === 'query') .map(([, { query }]) => _.get(query, ['constant_score', 'filter', 'range', '@timestamp']) @@ -131,18 +138,18 @@ describe('context app', function() { expect(Object.keys(_.last(intervals))).toEqual(['format', 'lte']); expect(intervals.length).toBeGreaterThan(1); - expect(hits).toEqual(searchSourceStub._stubHits.slice(-3)); + expect(hits).toEqual(mockSearchSource._stubHits.slice(-3)); }); }); it('should perform multiple queries until the expected hit count is returned', function() { - searchSourceStub._stubHits = [ - searchSourceStub._createStubHit(MS_PER_DAY * 3000), - searchSourceStub._createStubHit(MS_PER_DAY * 3000 - 1), - searchSourceStub._createStubHit(MS_PER_DAY * 3000 - 2), - searchSourceStub._createStubHit(MS_PER_DAY * 2800), - searchSourceStub._createStubHit(MS_PER_DAY * 2200), - searchSourceStub._createStubHit(MS_PER_DAY * 1000), + mockSearchSource._stubHits = [ + mockSearchSource._createStubHit(MS_PER_DAY * 3000), + mockSearchSource._createStubHit(MS_PER_DAY * 3000 - 1), + mockSearchSource._createStubHit(MS_PER_DAY * 3000 - 2), + mockSearchSource._createStubHit(MS_PER_DAY * 2800), + mockSearchSource._createStubHit(MS_PER_DAY * 2200), + mockSearchSource._createStubHit(MS_PER_DAY * 1000), ]; return fetchSuccessors( @@ -156,7 +163,7 @@ describe('context app', function() { 4, [] ).then(hits => { - const intervals = searchSourceStub.setField.args + const intervals = mockSearchSource.setField.args .filter(([property]) => property === 'query') .map(([, { query }]) => _.get(query, ['constant_score', 'filter', 'range', '@timestamp']) @@ -168,7 +175,7 @@ describe('context app', function() { expect(moment(_.last(intervals).gte).valueOf()).toBeGreaterThan(MS_PER_DAY * 2200); expect(intervals.length).toBeGreaterThan(1); - expect(hits).toEqual(searchSourceStub._stubHits.slice(0, 4)); + expect(hits).toEqual(mockSearchSource._stubHits.slice(0, 4)); }); }); @@ -200,7 +207,7 @@ describe('context app', function() { 3, [] ).then(() => { - const setParentSpy = searchSourceStub.setParent; + const setParentSpy = mockSearchSource.setParent; expect(setParentSpy.alwaysCalledWith(undefined)).toBe(true); expect(setParentSpy.called).toBe(true); }); @@ -219,7 +226,7 @@ describe('context app', function() { [] ).then(() => { expect( - searchSourceStub.setField.calledWith('sort', [{ '@timestamp': 'desc' }, { _doc: 'desc' }]) + mockSearchSource.setField.calledWith('sort', [{ '@timestamp': 'desc' }, { _doc: 'desc' }]) ).toBe(true); }); }); diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/context.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/context.ts index 507f927c608e1..2760eec38755e 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/context.ts +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/context.ts @@ -27,8 +27,8 @@ import { Filter, IndexPatternsContract, IndexPattern, - SearchSource, } from '../../../../../../../../../plugins/data/public'; +import { getServices } from '../../../../kibana_services'; export type SurrDocType = 'successors' | 'predecessors'; export interface EsHitRecord { @@ -115,7 +115,10 @@ function fetchContextProvider(indexPatterns: IndexPatternsContract) { } async function createSearchSource(indexPattern: IndexPattern, filters: Filter[]) { - return new SearchSource() + const { data } = getServices(); + + return data.search.searchSource + .create() .setParent(undefined) .setField('index', indexPattern) .setField('filter', filters); diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query/actions.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query/actions.js index 9efddc5275069..efc230d2cd4ae 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query/actions.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/query/actions.js @@ -20,7 +20,7 @@ import _ from 'lodash'; import { i18n } from '@kbn/i18n'; import React from 'react'; -import { getServices, SearchSource } from '../../../../kibana_services'; +import { getServices } from '../../../../kibana_services'; import { fetchAnchorProvider } from '../api/anchor'; import { fetchContextProvider } from '../api/context'; @@ -29,8 +29,8 @@ import { FAILURE_REASONS, LOADING_STATUS } from './constants'; import { MarkdownSimple } from '../../../../../../../../../plugins/kibana_react/public'; export function QueryActionsProvider(Promise) { - const { filterManager, indexPatterns } = getServices(); - const fetchAnchor = fetchAnchorProvider(indexPatterns, new SearchSource()); + const { filterManager, indexPatterns, data } = getServices(); + const fetchAnchor = fetchAnchorProvider(indexPatterns, data.search.searchSource.create()); const { fetchSurroundingDocs } = fetchContextProvider(indexPatterns); const { setPredecessorCount, setQueryParameters, setSuccessorCount } = getQueryParameterActions( filterManager, diff --git a/src/legacy/core_plugins/kibana/public/discover/plugin.ts b/src/legacy/core_plugins/kibana/public/discover/plugin.ts index 42883abe98171..702331529b879 100644 --- a/src/legacy/core_plugins/kibana/public/discover/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/discover/plugin.ts @@ -147,6 +147,9 @@ export class DiscoverPlugin implements Plugin { 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 () => { diff --git a/src/legacy/core_plugins/tests_bundle/tests_entry_template.js b/src/legacy/core_plugins/tests_bundle/tests_entry_template.js index 3e3dc284671da..f075d8365c299 100644 --- a/src/legacy/core_plugins/tests_bundle/tests_entry_template.js +++ b/src/legacy/core_plugins/tests_bundle/tests_entry_template.js @@ -135,7 +135,16 @@ const coreSystem = new CoreSystem({ }, }, rootDomElement, - useLegacyTestHarness: true, + requireLegacyBootstrapModule: () => { + // wrapped in NODE_ENV check so the 'ui/test_harness' module + // is not included in the distributable + if (process.env.IS_KIBANA_DISTRIBUTABLE !== 'true') { + return require('ui/test_harness'); + } + + throw new Error('tests bundle is not available in the distributable'); + }, + requireNewPlatformShimModule: () => require('ui/new_platform'), requireLegacyFiles: () => { ${bundle.getRequires().join('\n ')} } diff --git a/src/legacy/core_plugins/vis_type_vislib/public/plugin.ts b/src/legacy/core_plugins/vis_type_vislib/public/plugin.ts index ef3f664252856..26800f8a1620e 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/plugin.ts @@ -108,6 +108,6 @@ export class VisTypeVislibPlugin implements Plugin { public start(core: CoreStart, { data }: VisTypeVislibPluginStartDependencies) { setFormatService(data.fieldFormats); - setDataActions({ createFiltersFromEvent: data.actions.createFiltersFromEvent }); + setDataActions(data.actions); } } diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/legend.test.tsx b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/legend.test.tsx index c378ae7b05b37..6bf66c2bdd788 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/legend.test.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/legend.test.tsx @@ -36,7 +36,9 @@ jest.mock('../../../legacy_imports', () => ({ })); jest.mock('../../../services', () => ({ - getDataActions: () => ({ createFiltersFromEvent: jest.fn().mockResolvedValue(['yes']) }), + getDataActions: () => ({ + createFiltersFromValueClickAction: jest.fn().mockResolvedValue(['yes']), + }), })); const vis = { diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/legend.tsx b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/legend.tsx index 2fe16bbfeb625..7eb25e3930718 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/legend.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/legend.tsx @@ -101,7 +101,7 @@ export class VisLegend extends PureComponent { return false; } - const filters = await getDataActions().createFiltersFromEvent(item.values); + const filters = await getDataActions().createFiltersFromValueClickAction({ data: item.values }); return Boolean(filters.length); }; diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/handler.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/handler.js index ecf67ee3e017c..f33ce0395af1f 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/handler.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/handler.js @@ -83,10 +83,21 @@ export class Handler { // memoize so that the same function is returned every time, // allowing us to remove/re-add the same function - this.getProxyHandler = _.memoize(function(event) { + this.getProxyHandler = _.memoize(function(eventType) { const self = this; - return function(e) { - self.vis.emit(event, e); + return function(eventPayload) { + switch (eventType) { + case 'brush': + const xRaw = _.get(eventPayload.data, 'series[0].values[0].xRaw'); + if (!xRaw) return; // not sure if this is possible? + return self.vis.emit(eventType, { + table: xRaw.table, + range: eventPayload.range, + column: xRaw.column, + }); + case 'click': + return self.vis.emit(eventType, eventPayload); + } }; }); diff --git a/src/legacy/ui/public/_index.scss b/src/legacy/ui/public/_index.scss index aaed52f8b120a..b36e62297cc23 100644 --- a/src/legacy/ui/public/_index.scss +++ b/src/legacy/ui/public/_index.scss @@ -9,7 +9,6 @@ // kbnChart__legend-isLoading @import './accessibility/index'; -@import './chrome/index'; @import './directives/index'; @import './error_auto_create_index/index'; @import './error_url_overflow/index'; diff --git a/src/legacy/ui/public/chrome/_index.scss b/src/legacy/ui/public/chrome/_index.scss deleted file mode 100644 index 7e6c3ebaccc5c..0000000000000 --- a/src/legacy/ui/public/chrome/_index.scss +++ /dev/null @@ -1,3 +0,0 @@ -@import './variables'; - -@import './directives/index'; diff --git a/src/legacy/ui/public/chrome/_variables.scss b/src/legacy/ui/public/chrome/_variables.scss deleted file mode 100644 index 5097fe4c9bfae..0000000000000 --- a/src/legacy/ui/public/chrome/_variables.scss +++ /dev/null @@ -1,4 +0,0 @@ -$kbnGlobalNavClosedWidth: 53px; -$kbnGlobalNavOpenWidth: 180px; -$kbnGlobalNavLogoHeight: 70px; -$kbnGlobalNavAppIconHeight: $euiSizeXXL + $euiSizeXS; diff --git a/src/legacy/ui/public/chrome/directives/_index.scss b/src/legacy/ui/public/chrome/directives/_index.scss deleted file mode 100644 index 4d00b02279116..0000000000000 --- a/src/legacy/ui/public/chrome/directives/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './kbn_chrome'; diff --git a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js index f14f26613ef01..271586bb8c582 100644 --- a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js +++ b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js @@ -377,7 +377,8 @@ export const npStart = { }, data: { actions: { - createFiltersFromEvent: Promise.resolve(['yes']), + createFiltersFromValueClickAction: Promise.resolve(['yes']), + createFiltersFromRangeSelectAction: sinon.fake(), }, autocomplete: { getProvider: sinon.fake(), diff --git a/src/legacy/ui/ui_bundles/app_entry_template.js b/src/legacy/ui/ui_bundles/app_entry_template.js index a1c3a153a196c..683fedd34316f 100644 --- a/src/legacy/ui/ui_bundles/app_entry_template.js +++ b/src/legacy/ui/ui_bundles/app_entry_template.js @@ -25,6 +25,9 @@ export const appEntryTemplate = bundle => ` * * This is programmatically created and updated, do not modify * + * Any changes to this file should be kept in sync with + * src/core/public/entry_point.ts + * * context: ${bundle.getContext()} */ @@ -45,7 +48,9 @@ i18n.load(injectedMetadata.i18n.translationsUrl) browserSupportsCsp: !window.__kbnCspNotEnforced__, requireLegacyFiles: () => { ${bundle.getRequires().join('\n ')} - } + }, + requireLegacyBootstrapModule: () => require('ui/chrome'), + requireNewPlatformShimModule: () => require('ui/new_platform'), }); coreSystem diff --git a/src/legacy/ui/ui_bundles/ui_bundles_controller.js b/src/legacy/ui/ui_bundles/ui_bundles_controller.js index 7afa283af83e0..79112fd687e84 100644 --- a/src/legacy/ui/ui_bundles/ui_bundles_controller.js +++ b/src/legacy/ui/ui_bundles/ui_bundles_controller.js @@ -99,13 +99,6 @@ export class UiBundlesController { this._postLoaders = []; this._bundles = []; - // create a bundle for core-only with no modules - this.add({ - id: 'core', - modules: [], - template: appEntryTemplate, - }); - // create a bundle for each uiApp for (const uiApp of uiApps) { this.add({ diff --git a/src/legacy/ui/ui_render/bootstrap/template.js.hbs b/src/legacy/ui/ui_render/bootstrap/template.js.hbs index 1093153edbbf7..7250fa4fc9eca 100644 --- a/src/legacy/ui/ui_render/bootstrap/template.js.hbs +++ b/src/legacy/ui/ui_render/bootstrap/template.js.hbs @@ -69,26 +69,16 @@ if (window.__kbnStrictCsp__ && window.__kbnCspNotEnforced__) { } load([ - {{#each sharedJsDepFilenames}} - '{{../regularBundlePath}}/kbn-ui-shared-deps/{{this}}', - {{/each}} - '{{regularBundlePath}}/kbn-ui-shared-deps/{{sharedJsFilename}}', - '{{dllBundlePath}}/vendors_runtime.bundle.dll.js', - {{#each dllJsChunks}} + {{#each jsDependencyPaths}} '{{this}}', {{/each}} - '{{regularBundlePath}}/commons.bundle.js', - {{!-- '{{regularBundlePath}}/plugin/data/data.plugin.js', --}} - '{{regularBundlePath}}/plugin/kibanaUtils/kibanaUtils.plugin.js', - '{{regularBundlePath}}/plugin/esUiShared/esUiShared.plugin.js', - '{{regularBundlePath}}/plugin/kibanaReact/kibanaReact.plugin.js' ], function () { load([ - '{{regularBundlePath}}/{{appId}}.bundle.js', + '{{entryBundlePath}}', {{#each styleSheetPaths}} '{{this}}', {{/each}} - ]) + ]); }); - }; + } } diff --git a/src/legacy/ui/ui_render/ui_render_mixin.js b/src/legacy/ui/ui_render/ui_render_mixin.js index 0912d8683fc48..1e84405dd5153 100644 --- a/src/legacy/ui/ui_render/ui_render_mixin.js +++ b/src/legacy/ui/ui_render/ui_render_mixin.js @@ -103,41 +103,64 @@ export function uiRenderMixin(kbnServer, server, config) { const dllJsChunks = DllCompiler.getRawDllConfig().chunks.map( chunk => `${dllBundlePath}/vendors${chunk}.bundle.dll.js` ); + const styleSheetPaths = [ - ...dllStyleChunks, + ...(isCore ? [] : dllStyleChunks), `${basePath}/bundles/kbn-ui-shared-deps/${UiSharedDeps.baseCssDistFilename}`, ...(darkMode ? [ `${basePath}/bundles/kbn-ui-shared-deps/${UiSharedDeps.darkCssDistFilename}`, `${basePath}/node_modules/@kbn/ui-framework/dist/kui_dark.css`, + `${regularBundlePath}/dark_theme.style.css`, ] : [ `${basePath}/bundles/kbn-ui-shared-deps/${UiSharedDeps.lightCssDistFilename}`, `${basePath}/node_modules/@kbn/ui-framework/dist/kui_light.css`, + `${regularBundlePath}/light_theme.style.css`, ]), - `${regularBundlePath}/${darkMode ? 'dark' : 'light'}_theme.style.css`, `${regularBundlePath}/commons.style.css`, - ...(!isCore ? [`${regularBundlePath}/${app.getId()}.style.css`] : []), - ...kbnServer.uiExports.styleSheetPaths - .filter(path => path.theme === '*' || path.theme === (darkMode ? 'dark' : 'light')) - .map(path => - path.localPath.endsWith('.scss') - ? `${basePath}/built_assets/css/${path.publicPath}` - : `${basePath}/${path.publicPath}` - ) - .reverse(), + ...(isCore + ? [] + : [ + `${regularBundlePath}/${app.getId()}.style.css`, + ...kbnServer.uiExports.styleSheetPaths + .filter( + path => path.theme === '*' || path.theme === (darkMode ? 'dark' : 'light') + ) + .map(path => + path.localPath.endsWith('.scss') + ? `${basePath}/built_assets/css/${path.publicPath}` + : `${basePath}/${path.publicPath}` + ) + .reverse(), + ]), + ]; + + const jsDependencyPaths = [ + ...UiSharedDeps.jsDepFilenames.map( + filename => `${regularBundlePath}/kbn-ui-shared-deps/${filename}` + ), + `${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.jsFilename}`, + ...(isCore + ? [] + : [ + `${dllBundlePath}/vendors_runtime.bundle.dll.js`, + ...dllJsChunks, + `${regularBundlePath}/commons.bundle.js`, + ]), + `${regularBundlePath}/plugin/kibanaUtils/kibanaUtils.plugin.js`, + `${regularBundlePath}/plugin/esUiShared/esUiShared.plugin.js`, + `${regularBundlePath}/plugin/kibanaReact/kibanaReact.plugin.js`, ]; const bootstrap = new AppBootstrap({ templateData: { - appId: isCore ? 'core' : app.getId(), - regularBundlePath, - dllBundlePath, - dllJsChunks, - styleSheetPaths, - sharedJsFilename: UiSharedDeps.jsFilename, - sharedJsDepFilenames: UiSharedDeps.jsDepFilenames, darkMode, + jsDependencyPaths, + styleSheetPaths, + entryBundlePath: isCore + ? `${regularBundlePath}/core/core.entry.js` + : `${regularBundlePath}/${app.getId()}.bundle.js`, }, }); diff --git a/src/optimize/bundles_route/bundles_route.js b/src/optimize/bundles_route/bundles_route.js index 0c2e98b5acd63..530dabb9a46a3 100644 --- a/src/optimize/bundles_route/bundles_route.js +++ b/src/optimize/bundles_route/bundles_route.js @@ -17,11 +17,12 @@ * under the License. */ -import { isAbsolute, extname } from 'path'; +import { isAbsolute, extname, join } from 'path'; import LruCache from 'lru-cache'; import * as UiSharedDeps from '@kbn/ui-shared-deps'; import { createDynamicAssetResponse } from './dynamic_asset_response'; import { assertIsNpUiPluginPublicDirs } from '../np_ui_plugin_public_dirs'; +import { fromRoot } from '../../core/server/utils'; /** * Creates the routes that serves files from `bundlesPath` or from @@ -85,6 +86,12 @@ export function createBundlesRoute({ fileHashCache ) ), + buildRouteForBundles( + `${basePublicPath}/bundles/core/`, + `/bundles/core/`, + fromRoot(join('src', 'core', 'target', 'public')), + fileHashCache + ), buildRouteForBundles( `${basePublicPath}/bundles/`, '/bundles/', diff --git a/src/plugins/dashboard/public/bwc/types.ts b/src/plugins/dashboard/common/bwc/types.ts similarity index 93% rename from src/plugins/dashboard/public/bwc/types.ts rename to src/plugins/dashboard/common/bwc/types.ts index d5655e525e9bd..2427799345463 100644 --- a/src/plugins/dashboard/public/bwc/types.ts +++ b/src/plugins/dashboard/common/bwc/types.ts @@ -18,33 +18,28 @@ */ import { SavedObjectReference } from 'kibana/public'; -import { GridData } from '../application'; -export interface SavedObjectAttributes { +import { GridData } from '../'; + +interface SavedObjectAttributes { kibanaSavedObjectMeta: { searchSourceJSON: string; }; } -export interface Doc { +interface Doc { references: SavedObjectReference[]; attributes: Attributes; id: string; type: string; } -export interface DocPre700 { +interface DocPre700 { attributes: Attributes; id: string; type: string; } -export interface SavedObjectAttributes { - kibanaSavedObjectMeta: { - searchSourceJSON: string; - }; -} - interface DashboardAttributes extends SavedObjectAttributes { panelsJSON: string; description: string; @@ -55,8 +50,6 @@ interface DashboardAttributes extends SavedObjectAttributes { optionsJSON?: string; } -export type DashboardAttributes730ToLatest = DashboardAttributes; - interface DashboardAttributesTo720 extends SavedObjectAttributes { panelsJSON: string; description: string; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/migrations/index.ts b/src/plugins/dashboard/common/embeddable/types.ts similarity index 87% rename from src/legacy/core_plugins/kibana/public/dashboard/migrations/index.ts rename to src/plugins/dashboard/common/embeddable/types.ts index f333ce97d120f..eb76d73af7a58 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/migrations/index.ts +++ b/src/plugins/dashboard/common/embeddable/types.ts @@ -17,5 +17,10 @@ * under the License. */ -export { migrations730 } from './migrations_730'; -export { migrateMatchAllQuery } from './migrate_match_all_query'; +export interface GridData { + w: number; + h: number; + x: number; + y: number; + i: string; +} diff --git a/src/plugins/dashboard/common/index.ts b/src/plugins/dashboard/common/index.ts new file mode 100644 index 0000000000000..e3f3f629ae5d0 --- /dev/null +++ b/src/plugins/dashboard/common/index.ts @@ -0,0 +1,36 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { GridData } from './embeddable/types'; +export { + RawSavedDashboardPanel730ToLatest, + DashboardDoc730ToLatest, + DashboardDoc700To720, + DashboardDocPre700, +} from './bwc/types'; +export { + SavedDashboardPanelTo60, + SavedDashboardPanel610, + SavedDashboardPanel620, + SavedDashboardPanel630, + SavedDashboardPanel640To720, + SavedDashboardPanel730ToLatest, +} from './types'; + +export { migratePanelsTo730 } from './migrate_to_730_panels'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_to_730_panels.test.ts b/src/plugins/dashboard/common/migrate_to_730_panels.test.ts similarity index 97% rename from src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_to_730_panels.test.ts rename to src/plugins/dashboard/common/migrate_to_730_panels.test.ts index 4dd71fd8ee5f4..0867909225ddb 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_to_730_panels.test.ts +++ b/src/plugins/dashboard/common/migrate_to_730_panels.test.ts @@ -19,15 +19,12 @@ import { migratePanelsTo730 } from './migrate_to_730_panels'; import { RawSavedDashboardPanelTo60, - RawSavedDashboardPanel610, - RawSavedDashboardPanel620, RawSavedDashboardPanel630, RawSavedDashboardPanel640To720, - DEFAULT_PANEL_WIDTH, - DEFAULT_PANEL_HEIGHT, - SavedDashboardPanelTo60, - SavedDashboardPanel730ToLatest, -} from '../../../../../../plugins/dashboard/public'; + RawSavedDashboardPanel610, + RawSavedDashboardPanel620, +} from './bwc/types'; +import { SavedDashboardPanelTo60, SavedDashboardPanel730ToLatest } from './types'; test('6.0 migrates uiState, sort, scales, and gridData', async () => { const uiState = { @@ -96,8 +93,8 @@ test('6.0 migration gives default width and height when missing', () => { }, ]; const newPanels = migratePanelsTo730(panels, '8.0.0', true); - expect(newPanels[0].gridData.w).toBe(DEFAULT_PANEL_WIDTH); - expect(newPanels[0].gridData.h).toBe(DEFAULT_PANEL_HEIGHT); + expect(newPanels[0].gridData.w).toBe(24); + expect(newPanels[0].gridData.h).toBe(15); expect(newPanels[0].version).toBe('8.0.0'); }); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_to_730_panels.ts b/src/plugins/dashboard/common/migrate_to_730_panels.ts similarity index 97% rename from src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_to_730_panels.ts rename to src/plugins/dashboard/common/migrate_to_730_panels.ts index a19c861f092d5..b89345f0a872c 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_to_730_panels.ts +++ b/src/plugins/dashboard/common/migrate_to_730_panels.ts @@ -21,17 +21,19 @@ import semver from 'semver'; import uuid from 'uuid'; import { GridData, + SavedDashboardPanelTo60, + SavedDashboardPanel620, + SavedDashboardPanel630, + SavedDashboardPanel610, +} from './'; +import { RawSavedDashboardPanelTo60, RawSavedDashboardPanel630, RawSavedDashboardPanel640To720, RawSavedDashboardPanel730ToLatest, RawSavedDashboardPanel610, RawSavedDashboardPanel620, - SavedDashboardPanelTo60, - SavedDashboardPanel620, - SavedDashboardPanel630, - SavedDashboardPanel610, -} from '../../../../../../plugins/dashboard/public'; +} from './bwc/types'; const PANEL_HEIGHT_SCALE_FACTOR = 5; const PANEL_HEIGHT_SCALE_FACTOR_WITH_MARGINS = 4; @@ -92,7 +94,7 @@ function migratePre61PanelToLatest( ): RawSavedDashboardPanel730ToLatest { if (panel.col === undefined || panel.row === undefined) { throw new Error( - i18n.translate('kbn.dashboard.panel.unableToMigratePanelDataForSixOneZeroErrorMessage', { + i18n.translate('dashboard.panel.unableToMigratePanelDataForSixOneZeroErrorMessage', { defaultMessage: 'Unable to migrate panel data for "6.1.0" backwards compatibility, panel does not contain expected col and/or row fields', }) @@ -151,7 +153,7 @@ function migrate610PanelToLatest( (['w', 'x', 'h', 'y'] as Array).forEach(key => { if (panel.gridData[key] === undefined) { throw new Error( - i18n.translate('kbn.dashboard.panel.unableToMigratePanelDataForSixThreeZeroErrorMessage', { + i18n.translate('dashboard.panel.unableToMigratePanelDataForSixThreeZeroErrorMessage', { defaultMessage: 'Unable to migrate panel data for "6.3.0" backwards compatibility, panel does not contain expected field: {key}', values: { key }, diff --git a/src/plugins/dashboard/common/types.ts b/src/plugins/dashboard/common/types.ts new file mode 100644 index 0000000000000..7cc82a9173976 --- /dev/null +++ b/src/plugins/dashboard/common/types.ts @@ -0,0 +1,76 @@ +/* + * 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 { + RawSavedDashboardPanelTo60, + RawSavedDashboardPanel610, + RawSavedDashboardPanel620, + RawSavedDashboardPanel630, + RawSavedDashboardPanel640To720, + RawSavedDashboardPanel730ToLatest, +} from './bwc/types'; + +export type SavedDashboardPanel640To720 = Pick< + RawSavedDashboardPanel640To720, + Exclude +> & { + readonly id: string; + readonly type: string; +}; + +export type SavedDashboardPanel630 = Pick< + RawSavedDashboardPanel630, + Exclude +> & { + readonly id: string; + readonly type: string; +}; + +export type SavedDashboardPanel620 = Pick< + RawSavedDashboardPanel620, + Exclude +> & { + readonly id: string; + readonly type: string; +}; + +export type SavedDashboardPanel610 = Pick< + RawSavedDashboardPanel610, + Exclude +> & { + readonly id: string; + readonly type: string; +}; + +export type SavedDashboardPanelTo60 = Pick< + RawSavedDashboardPanelTo60, + Exclude +> & { + readonly id: string; + readonly type: string; +}; + +// id becomes optional starting in 7.3.0 +export type SavedDashboardPanel730ToLatest = Pick< + RawSavedDashboardPanel730ToLatest, + Exclude +> & { + readonly id?: string; + readonly type: string; +}; diff --git a/src/plugins/dashboard/kibana.json b/src/plugins/dashboard/kibana.json index 9bcd999c2dcc0..4cd8f3c7d981f 100644 --- a/src/plugins/dashboard/kibana.json +++ b/src/plugins/dashboard/kibana.json @@ -11,6 +11,6 @@ "savedObjects" ], "optionalPlugins": ["home", "share", "usageCollection"], - "server": false, + "server": true, "ui": true } diff --git a/src/plugins/dashboard/public/application/__snapshots__/dashboard_empty_screen.test.tsx.snap b/src/plugins/dashboard/public/application/__snapshots__/dashboard_empty_screen.test.tsx.snap index 7210879c5eacc..1bc85fa110ca0 100644 --- a/src/plugins/dashboard/public/application/__snapshots__/dashboard_empty_screen.test.tsx.snap +++ b/src/plugins/dashboard/public/application/__snapshots__/dashboard_empty_screen.test.tsx.snap @@ -304,13 +304,13 @@ exports[`DashboardEmptyScreen renders correctly with readonly mode 1`] = ` url="/plugins/kibana/home/assets/welcome_graphic_light_2x.png" >
@@ -998,13 +998,13 @@ exports[`DashboardEmptyScreen renders correctly without visualize paragraph 1`] url="/plugins/kibana/home/assets/welcome_graphic_light_2x.png" >
diff --git a/src/plugins/dashboard/public/application/application.ts b/src/plugins/dashboard/public/application/application.ts index 3134a5bfe2c67..a1696298117b0 100644 --- a/src/plugins/dashboard/public/application/application.ts +++ b/src/plugins/dashboard/public/application/application.ts @@ -38,12 +38,7 @@ import { EmbeddableStart } from '../../../embeddable/public'; import { NavigationPublicPluginStart as NavigationStart } from '../../../navigation/public'; import { DataPublicPluginStart } from '../../../data/public'; import { SharePluginStart } from '../../../share/public'; -import { - KibanaLegacyStart, - configureAppAngularModule, - createTopNavDirective, - createTopNavHelper, -} from '../../../kibana_legacy/public'; +import { KibanaLegacyStart, configureAppAngularModule } from '../../../kibana_legacy/public'; import { SavedObjectLoader } from '../../../saved_objects/public'; export interface RenderDeps { @@ -114,13 +109,11 @@ function mountDashboardApp(appBasePath: string, element: HTMLElement) { function createLocalAngularModule(core: AppMountContext['core'], navigation: NavigationStart) { createLocalI18nModule(); - createLocalTopNavModule(navigation); createLocalIconModule(); const dashboardAngularModule = angular.module(moduleName, [ ...thirdPartyAngularDependencies, 'app/dashboard/I18n', - 'app/dashboard/TopNav', 'app/dashboard/icon', ]); return dashboardAngularModule; @@ -132,13 +125,6 @@ function createLocalIconModule() { .directive('icon', reactDirective => reactDirective(EuiIcon)); } -function createLocalTopNavModule(navigation: NavigationStart) { - angular - .module('app/dashboard/TopNav', ['react']) - .directive('kbnTopNav', createTopNavDirective) - .directive('kbnTopNavHelper', createTopNavHelper(navigation.ui)); -} - function createLocalI18nModule() { angular .module('app/dashboard/I18n', []) diff --git a/src/plugins/dashboard/public/application/dashboard_app.html b/src/plugins/dashboard/public/application/dashboard_app.html index 3cf8932958b6d..87a5728ac2059 100644 --- a/src/plugins/dashboard/public/application/dashboard_app.html +++ b/src/plugins/dashboard/public/application/dashboard_app.html @@ -2,52 +2,7 @@ class="app-container dshAppContainer" ng-class="{'dshAppContainer--withMargins': model.useMargins}" > - - - - - - - - +

{{screenTitle}}

diff --git a/src/plugins/dashboard/public/application/dashboard_app.tsx b/src/plugins/dashboard/public/application/dashboard_app.tsx index 150cd8f8fcbb5..f101935b9288d 100644 --- a/src/plugins/dashboard/public/application/dashboard_app.tsx +++ b/src/plugins/dashboard/public/application/dashboard_app.tsx @@ -33,7 +33,6 @@ import { SavedObjectDashboard } from '../saved_dashboards'; export interface DashboardAppScope extends ng.IScope { dash: SavedObjectDashboard; appState: DashboardAppState; - screenTitle: string; model: { query: Query; filters: Filter[]; @@ -54,21 +53,7 @@ export interface DashboardAppScope extends ng.IScope { getShouldShowEditHelp: () => boolean; getShouldShowViewHelp: () => boolean; updateQueryAndFetch: ({ query, dateRange }: { query: Query; dateRange?: TimeRange }) => void; - onRefreshChange: ({ - isPaused, - refreshInterval, - }: { - isPaused: boolean; - refreshInterval: any; - }) => void; - onFiltersUpdated: (filters: Filter[]) => void; - onCancelApplyFilters: () => void; - onApplyFilters: (filters: Filter[]) => void; - onQuerySaved: (savedQuery: SavedQuery) => void; - onSavedQueryUpdated: (savedQuery: SavedQuery) => void; - onClearSavedQuery: () => void; topNavMenu: any; - showFilterBar: () => boolean; showAddPanel: any; showSaveQuery: boolean; kbnTopNav: any; diff --git a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx index 283fe9f0a83a4..fa2f06bfcdcdd 100644 --- a/src/plugins/dashboard/public/application/dashboard_app_controller.tsx +++ b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx @@ -21,12 +21,15 @@ import _, { uniq } from 'lodash'; import { i18n } from '@kbn/i18n'; import { EUI_MODAL_CANCEL_BUTTON } from '@elastic/eui'; import React from 'react'; +import ReactDOM from 'react-dom'; import angular from 'angular'; import { Subscription } from 'rxjs'; import { map } from 'rxjs/operators'; import { History } from 'history'; import { SavedObjectSaveOpts } from 'src/plugins/saved_objects/public'; +import { NavigationPublicPluginStart as NavigationStart } from 'src/plugins/navigation/public'; +import { TimeRange } from 'src/plugins/data/public'; import { DashboardEmptyScreen, DashboardEmptyScreenProps } from './dashboard_empty_screen'; import { @@ -87,6 +90,7 @@ export interface DashboardAppControllerDependencies extends RenderDeps { dashboardConfig: KibanaLegacyStart['dashboardConfig']; history: History; kbnUrlStateStorage: IKbnUrlStateStorage; + navigation: NavigationStart; } export class DashboardAppController { @@ -123,10 +127,13 @@ export class DashboardAppController { history, kbnUrlStateStorage, usageCollection, + navigation, }: DashboardAppControllerDependencies) { const filterManager = queryService.filterManager; const queryFilter = filterManager; const timefilter = queryService.timefilter.timefilter; + let showSearchBar = true; + let showQueryBar = true; let lastReloadRequestTime = 0; const dash = ($scope.dash = $route.current.locals.dash); @@ -243,6 +250,9 @@ export class DashboardAppController { } }; + const showFilterBar = () => + $scope.model.filters.length > 0 || !dashboardStateManager.getFullScreenMode(); + const getEmptyScreenProps = ( shouldShowEditHelp: boolean, isEmptyInReadOnlyMode: boolean @@ -310,7 +320,6 @@ export class DashboardAppController { refreshInterval: timefilter.getRefreshInterval(), }; $scope.panels = dashboardStateManager.getPanels(); - $scope.screenTitle = dashboardStateManager.getTitle(); }; updateState(); @@ -515,49 +524,8 @@ export class DashboardAppController { } }; - $scope.onRefreshChange = function({ isPaused, refreshInterval }) { - timefilter.setRefreshInterval({ - pause: isPaused, - value: refreshInterval ? refreshInterval : $scope.model.refreshInterval.value, - }); - }; - - $scope.onFiltersUpdated = filters => { - // The filters will automatically be set when the queryFilter emits an update event (see below) - queryFilter.setFilters(filters); - }; - - $scope.onQuerySaved = savedQuery => { - $scope.savedQuery = savedQuery; - }; - - $scope.onSavedQueryUpdated = savedQuery => { - $scope.savedQuery = { ...savedQuery }; - }; - - $scope.onClearSavedQuery = () => { - delete $scope.savedQuery; - dashboardStateManager.setSavedQueryId(undefined); - dashboardStateManager.applyFilters( - { - query: '', - language: - localStorage.get('kibana.userQueryLanguage') || uiSettings.get('search:queryLanguage'), - }, - queryFilter.getGlobalFilters() - ); - // Making this method sync broke the updates. - // Temporary fix, until we fix the complex state in this file. - setTimeout(() => { - queryFilter.setFilters(queryFilter.getGlobalFilters()); - }, 0); - }; - const updateStateFromSavedQuery = (savedQuery: SavedQuery) => { - const savedQueryFilters = savedQuery.attributes.filters || []; - const globalFilters = queryFilter.getGlobalFilters(); - const allFilters = [...globalFilters, ...savedQueryFilters]; - + const allFilters = filterManager.getFilters(); dashboardStateManager.applyFilters(savedQuery.attributes.query, allFilters); if (savedQuery.attributes.timefilter) { timefilter.setTime({ @@ -616,6 +584,48 @@ export class DashboardAppController { } ); + const onSavedQueryIdChange = (savedQueryId?: string) => { + dashboardStateManager.setSavedQueryId(savedQueryId); + }; + + const getNavBarProps = () => { + const isFullScreenMode = dashboardStateManager.getFullScreenMode(); + const screenTitle = dashboardStateManager.getTitle(); + return { + appName: 'dashboard', + config: $scope.isVisible ? $scope.topNavMenu : undefined, + className: isFullScreenMode ? 'kbnTopNavMenu-isFullScreen' : undefined, + screenTitle, + showSearchBar, + showQueryBar, + showFilterBar: showFilterBar(), + indexPatterns: $scope.indexPatterns, + showSaveQuery: $scope.showSaveQuery, + query: $scope.model.query, + savedQuery: $scope.savedQuery, + onSavedQueryIdChange, + savedQueryId: dashboardStateManager.getSavedQueryId(), + useDefaultBehaviors: true, + onQuerySubmit: (payload: { dateRange: TimeRange; query?: Query }): void => { + if (!payload.query) { + $scope.updateQueryAndFetch({ query: $scope.model.query, dateRange: payload.dateRange }); + } else { + $scope.updateQueryAndFetch({ query: payload.query, dateRange: payload.dateRange }); + } + }, + }; + }; + const dashboardNavBar = document.getElementById('dashboardChrome'); + const updateNavBar = () => { + ReactDOM.render(, dashboardNavBar); + }; + + const unmountNavBar = () => { + if (dashboardNavBar) { + ReactDOM.unmountComponentAtNode(dashboardNavBar); + } + }; + $scope.timefilterSubscriptions$ = new Subscription(); $scope.timefilterSubscriptions$.add( @@ -707,6 +717,8 @@ export class DashboardAppController { revertChangesAndExitEditMode(); } }); + + updateNavBar(); }; /** @@ -761,9 +773,6 @@ export class DashboardAppController { }); } - $scope.showFilterBar = () => - $scope.model.filters.length > 0 || !dashboardStateManager.getFullScreenMode(); - $scope.showAddPanel = () => { dashboardStateManager.setFullScreenMode(false); /* @@ -785,7 +794,11 @@ export class DashboardAppController { const navActions: { [key: string]: NavAction; } = {}; - navActions[TopNavIds.FULL_SCREEN] = () => dashboardStateManager.setFullScreenMode(true); + navActions[TopNavIds.FULL_SCREEN] = () => { + dashboardStateManager.setFullScreenMode(true); + showQueryBar = false; + updateNavBar(); + }; navActions[TopNavIds.EXIT_EDIT_MODE] = () => onChangeViewMode(ViewMode.VIEW); navActions[TopNavIds.ENTER_EDIT_MODE] = () => onChangeViewMode(ViewMode.EDIT); navActions[TopNavIds.SAVE] = () => { @@ -858,6 +871,7 @@ export class DashboardAppController { if ((response as { error: Error }).error) { dashboardStateManager.setTitle(currentTitle); } + updateNavBar(); return response; }); }; @@ -939,6 +953,9 @@ export class DashboardAppController { const visibleSubscription = chrome.getIsVisible$().subscribe(isVisible => { $scope.$evalAsync(() => { $scope.isVisible = isVisible; + showSearchBar = isVisible || showFilterBar(); + showQueryBar = !dashboardStateManager.getFullScreenMode() && isVisible; + updateNavBar(); }); }); @@ -949,9 +966,17 @@ export class DashboardAppController { navActions, dashboardConfig.getHideWriteControls() ); + updateNavBar(); + }); + + $scope.$watch('indexPatterns', () => { + updateNavBar(); }); $scope.$on('$destroy', () => { + // we have to unmount nav bar manually to make sure all internal subscriptions are unsubscribed + unmountNavBar(); + updateSubscription.unsubscribe(); stopSyncingQueryServiceStateWithUrl(); stopSyncingAppFilters(); diff --git a/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.tsx b/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.tsx index b15a813aff903..fb33649093c8d 100644 --- a/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.tsx +++ b/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.tsx @@ -29,9 +29,10 @@ import _ from 'lodash'; import React from 'react'; import { Subscription } from 'rxjs'; import ReactGridLayout, { Layout } from 'react-grid-layout'; +import { GridData } from '../../../../common'; import { ViewMode, EmbeddableChildPanel } from '../../../embeddable_plugin'; import { DASHBOARD_GRID_COLUMN_COUNT, DASHBOARD_GRID_HEIGHT } from '../dashboard_constants'; -import { DashboardPanelState, GridData } from '../types'; +import { DashboardPanelState } from '../types'; import { withKibana } from '../../../../../kibana_react/public'; import { DashboardContainerInput } from '../dashboard_container'; import { DashboardContainer, DashboardReactContextValue } from '../dashboard_container'; diff --git a/src/plugins/dashboard/public/application/embeddable/panel/dashboard_panel_placement.ts b/src/plugins/dashboard/public/application/embeddable/panel/dashboard_panel_placement.ts index 70a6c83418587..b95b7f394a27d 100644 --- a/src/plugins/dashboard/public/application/embeddable/panel/dashboard_panel_placement.ts +++ b/src/plugins/dashboard/public/application/embeddable/panel/dashboard_panel_placement.ts @@ -18,7 +18,8 @@ */ import { PanelNotFoundError } from '../../../embeddable_plugin'; -import { DashboardPanelState, GridData, DASHBOARD_GRID_COLUMN_COUNT } from '..'; +import { GridData } from '../../../../common'; +import { DashboardPanelState, DASHBOARD_GRID_COLUMN_COUNT } from '..'; export type PanelPlacementMethod = ( args: PlacementArgs diff --git a/src/plugins/dashboard/public/application/embeddable/types.ts b/src/plugins/dashboard/public/application/embeddable/types.ts index 6d0221cb10e8b..66cdd22ed6bd4 100644 --- a/src/plugins/dashboard/public/application/embeddable/types.ts +++ b/src/plugins/dashboard/public/application/embeddable/types.ts @@ -17,18 +17,11 @@ * under the License. */ import { SavedObjectEmbeddableInput } from 'src/plugins/embeddable/public'; +import { GridData } from '../../../common'; import { PanelState, EmbeddableInput } from '../../embeddable_plugin'; export type PanelId = string; export type SavedObjectId = string; -export interface GridData { - w: number; - h: number; - x: number; - y: number; - i: string; -} - export interface DashboardPanelState< TEmbeddableInput extends EmbeddableInput | SavedObjectEmbeddableInput = SavedObjectEmbeddableInput > extends PanelState { diff --git a/src/plugins/dashboard/public/application/lib/migrate_app_state.ts b/src/plugins/dashboard/public/application/lib/migrate_app_state.ts index 8f8de3663518a..f4d97578adebf 100644 --- a/src/plugins/dashboard/public/application/lib/migrate_app_state.ts +++ b/src/plugins/dashboard/public/application/lib/migrate_app_state.ts @@ -22,18 +22,16 @@ import { i18n } from '@kbn/i18n'; import { METRIC_TYPE } from '@kbn/analytics'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/public'; +import { DashboardAppState, SavedDashboardPanel } from '../../types'; import { - DashboardAppState, + migratePanelsTo730, SavedDashboardPanelTo60, SavedDashboardPanel730ToLatest, SavedDashboardPanel610, SavedDashboardPanel630, SavedDashboardPanel640To720, SavedDashboardPanel620, - SavedDashboardPanel, -} from '../../types'; -// should be moved in src/plugins/dashboard/common right after https://github.com/elastic/kibana/pull/61895 is merged -import { migratePanelsTo730 } from '../../../../../legacy/core_plugins/kibana/public/dashboard/migrations/migrate_to_730_panels'; +} from '../../../common'; /** * Attempts to migrate the state stored in the URL into the latest version of it. diff --git a/src/plugins/dashboard/public/application/test_helpers/get_saved_dashboard_mock.ts b/src/plugins/dashboard/public/application/test_helpers/get_saved_dashboard_mock.ts index 57c147ffe3588..ee59c68cce451 100644 --- a/src/plugins/dashboard/public/application/test_helpers/get_saved_dashboard_mock.ts +++ b/src/plugins/dashboard/public/application/test_helpers/get_saved_dashboard_mock.ts @@ -17,17 +17,19 @@ * under the License. */ -import { searchSourceMock } from '../../../../data/public/mocks'; +import { dataPluginMock } from '../../../../data/public/mocks'; import { SavedObjectDashboard } from '../../saved_dashboards'; export function getSavedDashboardMock( config?: Partial ): SavedObjectDashboard { + const searchSource = dataPluginMock.createStartContract(); + return { id: '123', title: 'my dashboard', panelsJSON: '[]', - searchSource: searchSourceMock, + searchSource: searchSource.search.searchSource.create(), copyOnSave: false, timeRestore: false, timeTo: 'now', diff --git a/src/plugins/dashboard/public/index.ts b/src/plugins/dashboard/public/index.ts index ca0ea0293b07c..44733499cdcba 100644 --- a/src/plugins/dashboard/public/index.ts +++ b/src/plugins/dashboard/public/index.ts @@ -20,29 +20,6 @@ import { PluginInitializerContext } from '../../../core/public'; import { DashboardPlugin } from './plugin'; -/** - * These types can probably be internal once all of dashboard app is migrated into this plugin. Right - * now, migrations are still in legacy land. - */ -export { - DashboardDoc730ToLatest, - DashboardDoc700To720, - RawSavedDashboardPanelTo60, - RawSavedDashboardPanel610, - RawSavedDashboardPanel620, - RawSavedDashboardPanel630, - RawSavedDashboardPanel640To720, - RawSavedDashboardPanel730ToLatest, - DashboardDocPre700, -} from './bwc'; -export { - SavedDashboardPanelTo60, - SavedDashboardPanel610, - SavedDashboardPanel620, - SavedDashboardPanel630, - SavedDashboardPanel730ToLatest, -} from './types'; - export { DashboardContainer, DashboardContainerInput, @@ -51,7 +28,6 @@ export { // Types below here can likely be made private when dashboard app moved into this NP plugin. DEFAULT_PANEL_WIDTH, DEFAULT_PANEL_HEIGHT, - GridData, } from './application'; export { DashboardConstants, createDashboardEditUrl } from './dashboard_constants'; diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx index 203c784d9df4e..5f6b67ee6ad20 100644 --- a/src/plugins/dashboard/public/plugin.tsx +++ b/src/plugins/dashboard/public/plugin.tsx @@ -251,6 +251,8 @@ export class DashboardPlugin localStorage: new Storage(localStorage), usageCollection, }; + // make sure the index pattern list is up to date + await dataStart.indexPatterns.clearCache(); const { renderApp } = await import('./application/application'); const unmount = renderApp(params.element, params.appBasePath, deps); return () => { diff --git a/src/plugins/dashboard/public/types.ts b/src/plugins/dashboard/public/types.ts index d96d2cdf75626..21c6bbc1bfc51 100644 --- a/src/plugins/dashboard/public/types.ts +++ b/src/plugins/dashboard/public/types.ts @@ -19,14 +19,7 @@ import { Query, Filter } from 'src/plugins/data/public'; import { SavedObject as SavedObjectType, SavedObjectAttributes } from 'src/core/public'; -import { - RawSavedDashboardPanelTo60, - RawSavedDashboardPanel610, - RawSavedDashboardPanel620, - RawSavedDashboardPanel630, - RawSavedDashboardPanel640To720, - RawSavedDashboardPanel730ToLatest, -} from './bwc'; +import { SavedDashboardPanel730ToLatest } from '../common'; import { ViewMode } from './embeddable_plugin'; export interface DashboardCapabilities { @@ -83,55 +76,6 @@ export type NavAction = (anchorElement?: any) => void; */ export type SavedDashboardPanel = SavedDashboardPanel730ToLatest; -// id becomes optional starting in 7.3.0 -export type SavedDashboardPanel730ToLatest = Pick< - RawSavedDashboardPanel730ToLatest, - Exclude -> & { - readonly id?: string; - readonly type: string; -}; - -export type SavedDashboardPanel640To720 = Pick< - RawSavedDashboardPanel640To720, - Exclude -> & { - readonly id: string; - readonly type: string; -}; - -export type SavedDashboardPanel630 = Pick< - RawSavedDashboardPanel630, - Exclude -> & { - readonly id: string; - readonly type: string; -}; - -export type SavedDashboardPanel620 = Pick< - RawSavedDashboardPanel620, - Exclude -> & { - readonly id: string; - readonly type: string; -}; - -export type SavedDashboardPanel610 = Pick< - RawSavedDashboardPanel610, - Exclude -> & { - readonly id: string; - readonly type: string; -}; - -export type SavedDashboardPanelTo60 = Pick< - RawSavedDashboardPanelTo60, - Exclude -> & { - readonly id: string; - readonly type: string; -}; - export interface DashboardAppState { panels: SavedDashboardPanel[]; fullScreenMode: boolean; diff --git a/src/plugins/dashboard/server/index.ts b/src/plugins/dashboard/server/index.ts new file mode 100644 index 0000000000000..9719586001c59 --- /dev/null +++ b/src/plugins/dashboard/server/index.ts @@ -0,0 +1,30 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { PluginInitializerContext } from '../../../core/server'; +import { DashboardPlugin } from './plugin'; + +// This exports static code and TypeScript types, +// as well as, Kibana Platform `plugin()` initializer. + +export function plugin(initializerContext: PluginInitializerContext) { + return new DashboardPlugin(initializerContext); +} + +export { DashboardPluginSetup, DashboardPluginStart } from './types'; diff --git a/src/legacy/core_plugins/kibana/migrations/types.ts b/src/plugins/dashboard/server/plugin.ts similarity index 51% rename from src/legacy/core_plugins/kibana/migrations/types.ts rename to src/plugins/dashboard/server/plugin.ts index 839f753670b20..5d1b66002e749 100644 --- a/src/legacy/core_plugins/kibana/migrations/types.ts +++ b/src/plugins/dashboard/server/plugin.ts @@ -17,24 +17,37 @@ * under the License. */ -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { SavedObjectReference } from '../../../../core/server'; +import { + PluginInitializerContext, + CoreSetup, + CoreStart, + Plugin, + Logger, +} from '../../../core/server'; -export interface SavedObjectAttributes { - kibanaSavedObjectMeta: { - searchSourceJSON: string; - }; -} +import { dashboardSavedObjectType } from './saved_objects'; -export interface Doc { - references: SavedObjectReference[]; - attributes: Attributes; - id: string; - type: string; -} +import { DashboardPluginSetup, DashboardPluginStart } from './types'; + +export class DashboardPlugin implements Plugin { + private readonly logger: Logger; + + constructor(initializerContext: PluginInitializerContext) { + this.logger = initializerContext.logger.get(); + } + + public setup(core: CoreSetup) { + this.logger.debug('dashboard: Setup'); + + core.savedObjects.registerType(dashboardSavedObjectType); + + return {}; + } + + public start(core: CoreStart) { + this.logger.debug('dashboard: Started'); + return {}; + } -export interface DocPre700 { - attributes: Attributes; - id: string; - type: string; + public stop() {} } diff --git a/src/plugins/dashboard/server/saved_objects/dashboard.ts b/src/plugins/dashboard/server/saved_objects/dashboard.ts new file mode 100644 index 0000000000000..65d5a4021f962 --- /dev/null +++ b/src/plugins/dashboard/server/saved_objects/dashboard.ts @@ -0,0 +1,67 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { SavedObjectsType } from 'kibana/server'; +import { dashboardSavedObjectTypeMigrations } from './dashboard_migrations'; + +export const dashboardSavedObjectType: SavedObjectsType = { + name: 'dashboard', + hidden: false, + namespaceType: 'single', + management: { + icon: 'dashboardApp', + defaultSearchField: 'title', + importableAndExportable: true, + getTitle(obj) { + return obj.attributes.title; + }, + getEditUrl(obj) { + return `/management/kibana/objects/savedDashboards/${encodeURIComponent(obj.id)}`; + }, + getInAppUrl(obj) { + return { + path: `/app/kibana#/dashboard/${encodeURIComponent(obj.id)}`, + uiCapabilitiesPath: 'dashboard.show', + }; + }, + }, + mappings: { + properties: { + description: { type: 'text' }, + hits: { type: 'integer' }, + kibanaSavedObjectMeta: { properties: { searchSourceJSON: { type: 'text' } } }, + optionsJSON: { type: 'text' }, + panelsJSON: { type: 'text' }, + refreshInterval: { + properties: { + display: { type: 'keyword' }, + pause: { type: 'boolean' }, + section: { type: 'integer' }, + value: { type: 'integer' }, + }, + }, + timeFrom: { type: 'keyword' }, + timeRestore: { type: 'boolean' }, + timeTo: { type: 'keyword' }, + title: { type: 'text' }, + version: { type: 'integer' }, + }, + }, + migrations: dashboardSavedObjectTypeMigrations, +}; diff --git a/src/legacy/core_plugins/kibana/migrations/migrations.test.js b/src/plugins/dashboard/server/saved_objects/dashboard_migrations.test.ts similarity index 95% rename from src/legacy/core_plugins/kibana/migrations/migrations.test.js rename to src/plugins/dashboard/server/saved_objects/dashboard_migrations.test.ts index b02081128c858..9829498118cc0 100644 --- a/src/legacy/core_plugins/kibana/migrations/migrations.test.js +++ b/src/plugins/dashboard/server/saved_objects/dashboard_migrations.test.ts @@ -17,14 +17,15 @@ * under the License. */ -import { migrations } from './migrations'; +import { SavedObjectUnsanitizedDoc } from 'kibana/server'; +import { dashboardSavedObjectTypeMigrations as migrations } from './dashboard_migrations'; describe('dashboard', () => { describe('7.0.0', () => { - const migration = migrations.dashboard['7.0.0']; + const migration = migrations['7.0.0']; test('skips error on empty object', () => { - expect(migration({})).toMatchInlineSnapshot(` + expect(migration({} as SavedObjectUnsanitizedDoc)).toMatchInlineSnapshot(` Object { "references": Array [], } @@ -329,7 +330,7 @@ Object { attributes: { panelsJSON: 123, }, - }; + } as SavedObjectUnsanitizedDoc; expect(migration(doc)).toMatchInlineSnapshot(` Object { "attributes": Object { @@ -347,7 +348,7 @@ Object { attributes: { panelsJSON: '{123abc}', }, - }; + } as SavedObjectUnsanitizedDoc; expect(migration(doc)).toMatchInlineSnapshot(` Object { "attributes": Object { @@ -365,7 +366,7 @@ Object { attributes: { panelsJSON: '{}', }, - }; + } as SavedObjectUnsanitizedDoc; expect(migration(doc)).toMatchInlineSnapshot(` Object { "attributes": Object { @@ -383,7 +384,7 @@ Object { attributes: { panelsJSON: '[{"id":"123"}]', }, - }; + } as SavedObjectUnsanitizedDoc; expect(migration(doc)).toMatchInlineSnapshot(` Object { "attributes": Object { @@ -401,7 +402,7 @@ Object { attributes: { panelsJSON: '[{"type":"visualization"}]', }, - }; + } as SavedObjectUnsanitizedDoc; expect(migration(doc)).toMatchInlineSnapshot(` Object { "attributes": Object { @@ -420,7 +421,7 @@ Object { panelsJSON: '[{"id":"1","type":"visualization","foo":true},{"id":"2","type":"visualization","bar":true}]', }, - }; + } as SavedObjectUnsanitizedDoc; const migratedDoc = migration(doc); expect(migratedDoc).toMatchInlineSnapshot(` Object { diff --git a/src/plugins/dashboard/server/saved_objects/dashboard_migrations.ts b/src/plugins/dashboard/server/saved_objects/dashboard_migrations.ts new file mode 100644 index 0000000000000..7c1d0568cd3d7 --- /dev/null +++ b/src/plugins/dashboard/server/saved_objects/dashboard_migrations.ts @@ -0,0 +1,117 @@ +/* + * 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 { get, flow } from 'lodash'; + +import { SavedObjectMigrationFn, SavedObjectUnsanitizedDoc } from 'kibana/server'; +import { migrations730 } from './migrations_730'; +import { migrateMatchAllQuery } from './migrate_match_all_query'; +import { DashboardDoc700To720 } from '../../common'; + +function migrateIndexPattern(doc: DashboardDoc700To720) { + const searchSourceJSON = get(doc, 'attributes.kibanaSavedObjectMeta.searchSourceJSON'); + if (typeof searchSourceJSON !== 'string') { + return; + } + let searchSource; + try { + searchSource = JSON.parse(searchSourceJSON); + } catch (e) { + // Let it go, the data is invalid and we'll leave it as is + return; + } + if (searchSource.index) { + searchSource.indexRefName = 'kibanaSavedObjectMeta.searchSourceJSON.index'; + doc.references.push({ + name: searchSource.indexRefName, + type: 'index-pattern', + id: searchSource.index, + }); + delete searchSource.index; + } + if (searchSource.filter) { + searchSource.filter.forEach((filterRow: any, i: number) => { + if (!filterRow.meta || !filterRow.meta.index) { + return; + } + filterRow.meta.indexRefName = `kibanaSavedObjectMeta.searchSourceJSON.filter[${i}].meta.index`; + doc.references.push({ + name: filterRow.meta.indexRefName, + type: 'index-pattern', + id: filterRow.meta.index, + }); + delete filterRow.meta.index; + }); + } + doc.attributes.kibanaSavedObjectMeta.searchSourceJSON = JSON.stringify(searchSource); +} + +const migrations700: SavedObjectMigrationFn = (doc): DashboardDoc700To720 => { + // Set new "references" attribute + doc.references = doc.references || []; + + // Migrate index pattern + migrateIndexPattern(doc as DashboardDoc700To720); + // Migrate panels + const panelsJSON = get(doc, 'attributes.panelsJSON'); + if (typeof panelsJSON !== 'string') { + return doc as DashboardDoc700To720; + } + let panels; + try { + panels = JSON.parse(panelsJSON); + } catch (e) { + // Let it go, the data is invalid and we'll leave it as is + return doc as DashboardDoc700To720; + } + if (!Array.isArray(panels)) { + return doc as DashboardDoc700To720; + } + panels.forEach((panel, i) => { + if (!panel.type || !panel.id) { + return; + } + panel.panelRefName = `panel_${i}`; + doc.references!.push({ + name: `panel_${i}`, + type: panel.type, + id: panel.id, + }); + delete panel.type; + delete panel.id; + }); + doc.attributes.panelsJSON = JSON.stringify(panels); + return doc as DashboardDoc700To720; +}; + +export const dashboardSavedObjectTypeMigrations = { + /** + * We need to have this migration twice, once with a version prior to 7.0.0 once with a version + * after it. The reason for that is, that this migration has been introduced once 7.0.0 was already + * released. Thus a user who already had 7.0.0 installed already got the 7.0.0 migrations below running, + * so we need a version higher than that. But this fix was backported to the 6.7 release, meaning if we + * would only have the 7.0.1 migration in here a user on the 6.7 release will migrate their saved objects + * to the 7.0.1 state, and thus when updating their Kibana to 7.0, will never run the 7.0.0 migrations introduced + * in that version. So we apply this twice, once with 6.7.2 and once with 7.0.1 while the backport to 6.7 + * only contained the 6.7.2 migration and not the 7.0.1 migration. + */ + '6.7.2': flow(migrateMatchAllQuery), + '7.0.0': flow<(doc: SavedObjectUnsanitizedDoc) => DashboardDoc700To720>(migrations700), + '7.3.0': flow(migrations730), +}; diff --git a/src/plugins/dashboard/public/bwc/index.ts b/src/plugins/dashboard/server/saved_objects/index.ts similarity index 93% rename from src/plugins/dashboard/public/bwc/index.ts rename to src/plugins/dashboard/server/saved_objects/index.ts index d8f7b5091eb8f..ca97b9d2a6b70 100644 --- a/src/plugins/dashboard/public/bwc/index.ts +++ b/src/plugins/dashboard/server/saved_objects/index.ts @@ -17,4 +17,4 @@ * under the License. */ -export * from './types'; +export { dashboardSavedObjectType } from './dashboard'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/migrations/is_dashboard_doc.ts b/src/plugins/dashboard/server/saved_objects/is_dashboard_doc.ts similarity index 70% rename from src/legacy/core_plugins/kibana/public/dashboard/migrations/is_dashboard_doc.ts rename to src/plugins/dashboard/server/saved_objects/is_dashboard_doc.ts index d8f8882a218dd..c9b35263a549f 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/migrations/is_dashboard_doc.ts +++ b/src/plugins/dashboard/server/saved_objects/is_dashboard_doc.ts @@ -17,8 +17,21 @@ * under the License. */ -import { DashboardDoc730ToLatest } from '../../../../../../plugins/dashboard/public'; -import { isDoc } from '../../../migrations/is_doc'; +import { SavedObjectUnsanitizedDoc } from 'kibana/server'; +import { DashboardDoc730ToLatest } from '../../common'; + +function isDoc( + doc: { [key: string]: unknown } | SavedObjectUnsanitizedDoc +): doc is SavedObjectUnsanitizedDoc { + return ( + typeof doc.id === 'string' && + typeof doc.type === 'string' && + doc.attributes !== null && + typeof doc.attributes === 'object' && + doc.references !== null && + typeof doc.references === 'object' + ); +} export function isDashboardDoc( doc: { [key: string]: unknown } | DashboardDoc730ToLatest diff --git a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_match_all_query.test.ts b/src/plugins/dashboard/server/saved_objects/migrate_match_all_query.test.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_match_all_query.test.ts rename to src/plugins/dashboard/server/saved_objects/migrate_match_all_query.test.ts diff --git a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_match_all_query.ts b/src/plugins/dashboard/server/saved_objects/migrate_match_all_query.ts similarity index 95% rename from src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_match_all_query.ts rename to src/plugins/dashboard/server/saved_objects/migrate_match_all_query.ts index 707aae9e5d4ac..5b8582bf821ef 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_match_all_query.ts +++ b/src/plugins/dashboard/server/saved_objects/migrate_match_all_query.ts @@ -19,7 +19,7 @@ import { SavedObjectMigrationFn } from 'kibana/server'; import { get } from 'lodash'; -import { DEFAULT_QUERY_LANGUAGE } from '../../../../../../plugins/data/common'; +import { DEFAULT_QUERY_LANGUAGE } from '../../../data/common'; export const migrateMatchAllQuery: SavedObjectMigrationFn = doc => { const searchSourceJSON = get(doc, 'attributes.kibanaSavedObjectMeta.searchSourceJSON'); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrations_730.test.ts b/src/plugins/dashboard/server/saved_objects/migrations_730.test.ts similarity index 91% rename from src/legacy/core_plugins/kibana/public/dashboard/migrations/migrations_730.test.ts rename to src/plugins/dashboard/server/saved_objects/migrations_730.test.ts index 5a4970897098d..aa744324428a4 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrations_730.test.ts +++ b/src/plugins/dashboard/server/saved_objects/migrations_730.test.ts @@ -17,20 +17,18 @@ * under the License. */ -import { migrations } from '../../../migrations'; +import { dashboardSavedObjectTypeMigrations as migrations } from './dashboard_migrations'; import { migrations730 } from './migrations_730'; -import { - DashboardDoc700To720, - DashboardDoc730ToLatest, - RawSavedDashboardPanel730ToLatest, - DashboardDocPre700, -} from '../../../../../../plugins/dashboard/public'; - -const mockLogger = { - warning: () => {}, - warn: () => {}, - debug: () => {}, - info: () => {}, +import { DashboardDoc700To720, DashboardDoc730ToLatest, DashboardDocPre700 } from '../../common'; +import { RawSavedDashboardPanel730ToLatest } from '../../common'; + +const mockContext = { + log: { + warning: () => {}, + warn: () => {}, + debug: () => {}, + info: () => {}, + }, }; test('dashboard migration 7.3.0 migrates filters to query on search source', () => { @@ -53,7 +51,7 @@ test('dashboard migration 7.3.0 migrates filters to query on search source', () '[{"id":"1","type":"visualization","foo":true},{"id":"2","type":"visualization","bar":true}]', }, }; - const newDoc = migrations730(doc, mockLogger); + const newDoc = migrations730(doc, mockContext); expect(newDoc).toMatchInlineSnapshot(` Object { @@ -97,8 +95,8 @@ test('dashboard migration 7.3.0 migrates filters to query on search source when }, }; - const doc700: DashboardDoc700To720 = migrations.dashboard['7.0.0'](doc, mockLogger); - const newDoc = migrations.dashboard['7.3.0'](doc700, mockLogger); + const doc700: DashboardDoc700To720 = migrations['7.0.0'](doc); + const newDoc = migrations['7.3.0'](doc700, mockContext); const parsedSearchSource = JSON.parse(newDoc.attributes.kibanaSavedObjectMeta.searchSourceJSON); expect(parsedSearchSource.filter.length).toBe(0); @@ -129,8 +127,8 @@ test('dashboard migration works when panelsJSON is missing panelIndex', () => { }, }; - const doc700: DashboardDoc700To720 = migrations.dashboard['7.0.0'](doc, mockLogger); - const newDoc = migrations.dashboard['7.3.0'](doc700, mockLogger); + const doc700: DashboardDoc700To720 = migrations['7.0.0'](doc); + const newDoc = migrations['7.3.0'](doc700, mockContext); const parsedSearchSource = JSON.parse(newDoc.attributes.kibanaSavedObjectMeta.searchSourceJSON); expect(parsedSearchSource.filter.length).toBe(0); @@ -159,7 +157,7 @@ test('dashboard migration 7.3.0 migrates panels', () => { }, }; - const newDoc = migrations730(doc, mockLogger) as DashboardDoc730ToLatest; + const newDoc = migrations730(doc, mockContext) as DashboardDoc730ToLatest; const newPanels = JSON.parse(newDoc.attributes.panelsJSON) as RawSavedDashboardPanel730ToLatest[]; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrations_730.ts b/src/plugins/dashboard/server/saved_objects/migrations_730.ts similarity index 79% rename from src/legacy/core_plugins/kibana/public/dashboard/migrations/migrations_730.ts rename to src/plugins/dashboard/server/saved_objects/migrations_730.ts index 56856f7b21303..e9d483f68a5da 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrations_730.ts +++ b/src/plugins/dashboard/server/saved_objects/migrations_730.ts @@ -16,26 +16,15 @@ * specific language governing permissions and limitations * under the License. */ -// This file should be moved to dashboard/server/ -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { SavedObjectsMigrationLogger } from 'src/core/server'; + import { inspect } from 'util'; -import { - DashboardDoc730ToLatest, - DashboardDoc700To720, -} from '../../../../../../plugins/dashboard/public'; +import { SavedObjectMigrationContext } from 'kibana/server'; +import { DashboardDoc730ToLatest } from '../../common'; import { isDashboardDoc } from './is_dashboard_doc'; import { moveFiltersToQuery } from './move_filters_to_query'; -import { migratePanelsTo730 } from './migrate_to_730_panels'; +import { migratePanelsTo730, DashboardDoc700To720 } from '../../common'; -export function migrations730( - doc: - | { - [key: string]: unknown; - } - | DashboardDoc700To720, - logger: SavedObjectsMigrationLogger -): DashboardDoc730ToLatest | { [key: string]: unknown } { +export const migrations730 = (doc: DashboardDoc700To720, { log }: SavedObjectMigrationContext) => { if (!isDashboardDoc(doc)) { // NOTE: we should probably throw an error here... but for now following suit and in the // case of errors, just returning the same document. @@ -48,7 +37,7 @@ export function migrations730( moveFiltersToQuery(searchSource) ); } catch (e) { - logger.warning( + log.warning( `Exception @ migrations730 while trying to migrate dashboard query filters!\n` + `${e.stack}\n` + `dashboard: ${inspect(doc, false, null)}` @@ -75,7 +64,7 @@ export function migrations730( delete doc.attributes.uiStateJSON; } catch (e) { - logger.warning( + log.warning( `Exception @ migrations730 while trying to migrate dashboard panels!\n` + `Error: ${e.stack}\n` + `dashboard: ${inspect(doc, false, null)}` @@ -84,4 +73,4 @@ export function migrations730( } return doc as DashboardDoc730ToLatest; -} +}; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/migrations/move_filters_to_query.test.ts b/src/plugins/dashboard/server/saved_objects/move_filters_to_query.test.ts similarity index 96% rename from src/legacy/core_plugins/kibana/public/dashboard/migrations/move_filters_to_query.test.ts rename to src/plugins/dashboard/server/saved_objects/move_filters_to_query.test.ts index 621983b1ca8a5..a06f64e0f0c40 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/migrations/move_filters_to_query.test.ts +++ b/src/plugins/dashboard/server/saved_objects/move_filters_to_query.test.ts @@ -17,8 +17,8 @@ * under the License. */ +import { esFilters, Filter } from 'src/plugins/data/public'; import { moveFiltersToQuery, Pre600FilterQuery } from './move_filters_to_query'; -import { esFilters, Filter } from '../../../../../../plugins/data/public'; const filter: Filter = { meta: { disabled: false, negate: false, alias: '' }, diff --git a/src/legacy/core_plugins/kibana/public/dashboard/migrations/move_filters_to_query.ts b/src/plugins/dashboard/server/saved_objects/move_filters_to_query.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/dashboard/migrations/move_filters_to_query.ts rename to src/plugins/dashboard/server/saved_objects/move_filters_to_query.ts diff --git a/src/legacy/core_plugins/input_control_vis/public/legacy_imports.ts b/src/plugins/dashboard/server/types.ts similarity index 71% rename from src/legacy/core_plugins/input_control_vis/public/legacy_imports.ts rename to src/plugins/dashboard/server/types.ts index 8c58ac2386da4..1151b06dbdab7 100644 --- a/src/legacy/core_plugins/input_control_vis/public/legacy_imports.ts +++ b/src/plugins/dashboard/server/types.ts @@ -17,10 +17,7 @@ * under the License. */ -import { Class } from '@kbn/utility-types'; -import { SearchSource as SearchSourceClass, ISearchSource } from '../../../../plugins/data/public'; - -export { SearchSourceFields } from '../../../../plugins/data/public'; - -export type SearchSource = Class; -export const SearchSource = SearchSourceClass; +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface DashboardPluginSetup {} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface DashboardPluginStart {} diff --git a/src/plugins/data/public/actions/filters/brush_event.test.ts b/src/plugins/data/public/actions/filters/create_filters_from_range_select.test.ts similarity index 58% rename from src/plugins/data/public/actions/filters/brush_event.test.ts rename to src/plugins/data/public/actions/filters/create_filters_from_range_select.test.ts index 60244354f06e4..5d21b395b994f 100644 --- a/src/plugins/data/public/actions/filters/brush_event.test.ts +++ b/src/plugins/data/public/actions/filters/create_filters_from_range_select.test.ts @@ -19,30 +19,34 @@ import moment from 'moment'; -import { onBrushEvent, BrushEvent } from './brush_event'; +import { createFiltersFromRangeSelectAction } from './create_filters_from_range_select'; -import { IndexPatternsContract } from '../../../public'; +import { IndexPatternsContract, RangeFilter } from '../../../public'; import { dataPluginMock } from '../../../public/mocks'; import { setIndexPatterns } from '../../../public/services'; import { mockDataServices } from '../../../public/search/aggs/test_helpers'; +import { TriggerContextMapping } from '../../../../ui_actions/public'; describe('brushEvent', () => { const DAY_IN_MS = 24 * 60 * 60 * 1000; const JAN_01_2014 = 1388559600000; - let baseEvent: BrushEvent; + let baseEvent: TriggerContextMapping['SELECT_RANGE_TRIGGER']['data']; + + const indexPattern = { + id: 'indexPatternId', + timeFieldName: 'time', + fields: { + getByName: () => undefined, + filter: () => [], + }, + }; const aggConfigs = [ { params: { field: {}, }, - getIndexPattern: () => ({ - timeFieldName: 'time', - fields: { - getByName: () => undefined, - filter: () => [], - }, - }), + getIndexPattern: () => indexPattern, }, ]; @@ -50,56 +54,37 @@ describe('brushEvent', () => { mockDataServices(); setIndexPatterns(({ ...dataPluginMock.createStartContract().indexPatterns, - get: async () => ({ - id: 'indexPatternId', - timeFieldName: 'time', - fields: { - getByName: () => undefined, - filter: () => [], - }, - }), + get: async () => indexPattern, } as unknown) as IndexPatternsContract); baseEvent = { - data: { - ordered: { - date: false, - }, - series: [ + column: 0, + table: { + type: 'kibana_datatable', + columns: [ { - values: [ - { - xRaw: { - column: 0, - table: { - columns: [ - { - id: '1', - meta: { - type: 'histogram', - indexPatternId: 'indexPatternId', - aggConfigParams: aggConfigs[0].params, - }, - }, - ], - }, - }, - }, - ], + id: '1', + name: '1', + meta: { + type: 'histogram', + indexPatternId: 'indexPatternId', + aggConfigParams: aggConfigs[0].params, + }, }, ], + rows: [], }, range: [], }; }); test('should be a function', () => { - expect(typeof onBrushEvent).toBe('function'); + expect(typeof createFiltersFromRangeSelectAction).toBe('function'); }); test('ignores event when data.xAxisField not provided', async () => { - const filter = await onBrushEvent(baseEvent); - expect(filter).toBeUndefined(); + const filter = await createFiltersFromRangeSelectAction(baseEvent); + expect(filter).toEqual([]); }); describe('handles an event when the x-axis field is a date field', () => { @@ -109,29 +94,29 @@ describe('brushEvent', () => { name: 'time', type: 'date', }; - baseEvent.data.ordered = { date: true }; }); afterAll(() => { baseEvent.range = []; - baseEvent.data.ordered = { date: false }; + aggConfigs[0].params.field = {}; }); test('by ignoring the event when range spans zero time', async () => { baseEvent.range = [JAN_01_2014, JAN_01_2014]; - const filter = await onBrushEvent(baseEvent); - expect(filter).toBeUndefined(); + const filter = await createFiltersFromRangeSelectAction(baseEvent); + expect(filter).toEqual([]); }); test('by updating the timefilter', async () => { baseEvent.range = [JAN_01_2014, JAN_01_2014 + DAY_IN_MS]; - const filter = await onBrushEvent(baseEvent); + const filter = await createFiltersFromRangeSelectAction(baseEvent); expect(filter).toBeDefined(); - if (filter) { - expect(filter.range.time.gte).toBe(new Date(JAN_01_2014).toISOString()); + if (filter.length) { + const rangeFilter = filter[0] as RangeFilter; + expect(rangeFilter.range.time.gte).toBe(new Date(JAN_01_2014).toISOString()); // Set to a baseline timezone for comparison. - expect(filter.range.time.lt).toBe(new Date(JAN_01_2014 + DAY_IN_MS).toISOString()); + expect(rangeFilter.range.time.lt).toBe(new Date(JAN_01_2014 + DAY_IN_MS).toISOString()); } }); }); @@ -142,26 +127,26 @@ describe('brushEvent', () => { name: 'anotherTimeField', type: 'date', }; - baseEvent.data.ordered = { date: true }; }); afterAll(() => { baseEvent.range = []; - baseEvent.data.ordered = { date: false }; + aggConfigs[0].params.field = {}; }); test('creates a new range filter', async () => { const rangeBegin = JAN_01_2014; const rangeEnd = rangeBegin + DAY_IN_MS; baseEvent.range = [rangeBegin, rangeEnd]; - const filter = await onBrushEvent(baseEvent); + const filter = await createFiltersFromRangeSelectAction(baseEvent); expect(filter).toBeDefined(); - if (filter) { - expect(filter.range.anotherTimeField.gte).toBe(moment(rangeBegin).toISOString()); - expect(filter.range.anotherTimeField.lt).toBe(moment(rangeEnd).toISOString()); - expect(filter.range.anotherTimeField).toHaveProperty( + if (filter.length) { + const rangeFilter = filter[0] as RangeFilter; + expect(rangeFilter.range.anotherTimeField.gte).toBe(moment(rangeBegin).toISOString()); + expect(rangeFilter.range.anotherTimeField.lt).toBe(moment(rangeEnd).toISOString()); + expect(rangeFilter.range.anotherTimeField).toHaveProperty( 'format', 'strict_date_optional_time' ); @@ -184,20 +169,21 @@ describe('brushEvent', () => { test('by ignoring the event when range does not span at least 2 values', async () => { baseEvent.range = [1]; - const filter = await onBrushEvent(baseEvent); - expect(filter).toBeUndefined(); + const filter = await createFiltersFromRangeSelectAction(baseEvent); + expect(filter).toEqual([]); }); test('by creating a new filter', async () => { baseEvent.range = [1, 2, 3, 4]; - const filter = await onBrushEvent(baseEvent); + const filter = await createFiltersFromRangeSelectAction(baseEvent); expect(filter).toBeDefined(); - if (filter) { - expect(filter.range.numberField.gte).toBe(1); - expect(filter.range.numberField.lt).toBe(4); - expect(filter.range.numberField).not.toHaveProperty('format'); + if (filter.length) { + const rangeFilter = filter[0] as RangeFilter; + expect(rangeFilter.range.numberField.gte).toBe(1); + expect(rangeFilter.range.numberField.lt).toBe(4); + expect(rangeFilter.range.numberField).not.toHaveProperty('format'); } }); }); diff --git a/src/plugins/data/public/actions/filters/brush_event.ts b/src/plugins/data/public/actions/filters/create_filters_from_range_select.ts similarity index 74% rename from src/plugins/data/public/actions/filters/brush_event.ts rename to src/plugins/data/public/actions/filters/create_filters_from_range_select.ts index 714f005fbeb6d..409614ca9c380 100644 --- a/src/plugins/data/public/actions/filters/brush_event.ts +++ b/src/plugins/data/public/actions/filters/create_filters_from_range_select.ts @@ -17,34 +17,18 @@ * under the License. */ -import { get, last } from 'lodash'; +import { last } from 'lodash'; import moment from 'moment'; import { esFilters, IFieldType, RangeFilterParams } from '../../../public'; import { getIndexPatterns } from '../../../public/services'; import { deserializeAggConfig } from '../../search/expressions/utils'; +import { RangeSelectTriggerContext } from '../../../../embeddable/public'; -export interface BrushEvent { - data: { - ordered: { - date: boolean; - }; - series: Array>; - }; - range: number[]; -} - -export async function onBrushEvent(event: BrushEvent) { - const isDate = get(event.data, 'ordered.date'); - const xRaw: Record = get(event.data, 'series[0].values[0].xRaw'); - - if (!xRaw) { - return; - } - - const column: Record = xRaw.table.columns[xRaw.column]; +export async function createFiltersFromRangeSelectAction(event: RangeSelectTriggerContext['data']) { + const column: Record = event.table.columns[event.column]; if (!column || !column.meta) { - return; + return []; } const indexPattern = await getIndexPatterns().get(column.meta.indexPatternId); @@ -55,16 +39,18 @@ export async function onBrushEvent(event: BrushEvent) { const field: IFieldType = aggConfig.params.field; if (!field || event.range.length <= 1) { - return; + return []; } const min = event.range[0]; const max = last(event.range); if (min === max) { - return; + return []; } + const isDate = field.type === 'date'; + const range: RangeFilterParams = { gte: isDate ? moment(min).toISOString() : min, lt: isDate ? moment(max).toISOString() : max, @@ -74,5 +60,5 @@ export async function onBrushEvent(event: BrushEvent) { range.format = 'strict_date_optional_time'; } - return esFilters.buildRangeFilter(field, range, indexPattern); + return esFilters.mapAndFlattenFilters([esFilters.buildRangeFilter(field, range, indexPattern)]); } diff --git a/src/plugins/data/public/actions/filters/create_filters_from_event.test.ts b/src/plugins/data/public/actions/filters/create_filters_from_value_click.test.ts similarity index 85% rename from src/plugins/data/public/actions/filters/create_filters_from_event.test.ts rename to src/plugins/data/public/actions/filters/create_filters_from_value_click.test.ts index 1ed09002816d1..a0e285c20d776 100644 --- a/src/plugins/data/public/actions/filters/create_filters_from_event.test.ts +++ b/src/plugins/data/public/actions/filters/create_filters_from_value_click.test.ts @@ -26,7 +26,8 @@ import { import { dataPluginMock } from '../../../public/mocks'; import { setIndexPatterns } from '../../../public/services'; import { mockDataServices } from '../../../public/search/aggs/test_helpers'; -import { createFiltersFromEvent, EventData } from './create_filters_from_event'; +import { createFiltersFromValueClickAction } from './create_filters_from_value_click'; +import { ValueClickTriggerContext } from '../../../../embeddable/public'; const mockField = { name: 'bytes', @@ -37,8 +38,8 @@ const mockField = { format: new fieldFormats.BytesFormat({}, (() => {}) as FieldFormatsGetConfigFn), }; -describe('createFiltersFromEvent', () => { - let dataPoints: EventData[]; +describe('createFiltersFromValueClick', () => { + let dataPoints: ValueClickTriggerContext['data']['data']; beforeEach(() => { dataPoints = [ @@ -86,7 +87,7 @@ describe('createFiltersFromEvent', () => { test('ignores event when value for rows is not provided', async () => { dataPoints[0].table.rows[0]['1-1'] = null; - const filters = await createFiltersFromEvent(dataPoints); + const filters = await createFiltersFromValueClickAction({ data: dataPoints }); expect(filters.length).toEqual(0); }); @@ -95,14 +96,14 @@ describe('createFiltersFromEvent', () => { if (dataPoints[0].table.columns[0].meta) { dataPoints[0].table.columns[0].meta.type = 'terms'; } - const filters = await createFiltersFromEvent(dataPoints); + const filters = await createFiltersFromValueClickAction({ data: dataPoints }); expect(filters.length).toEqual(1); expect(filters[0].query.match_phrase.bytes).toEqual('2048'); }); test('handles an event when aggregations type is not terms', async () => { - const filters = await createFiltersFromEvent(dataPoints); + const filters = await createFiltersFromValueClickAction({ data: dataPoints }); expect(filters.length).toEqual(1); diff --git a/src/plugins/data/public/actions/filters/create_filters_from_event.ts b/src/plugins/data/public/actions/filters/create_filters_from_value_click.ts similarity index 90% rename from src/plugins/data/public/actions/filters/create_filters_from_event.ts rename to src/plugins/data/public/actions/filters/create_filters_from_value_click.ts index e62945a592072..2b426813a98a4 100644 --- a/src/plugins/data/public/actions/filters/create_filters_from_event.ts +++ b/src/plugins/data/public/actions/filters/create_filters_from_value_click.ts @@ -21,13 +21,7 @@ import { KibanaDatatable } from '../../../../../plugins/expressions/public'; import { deserializeAggConfig } from '../../search/expressions'; import { esFilters, Filter } from '../../../public'; import { getIndexPatterns } from '../../../public/services'; - -export interface EventData { - table: Pick; - column: number; - row: number; - value: any; -} +import { ValueClickTriggerContext } from '../../../../embeddable/public'; /** * For terms aggregations on `__other__` buckets, this assembles a list of applicable filter @@ -39,7 +33,7 @@ export interface EventData { * @return {array} - array of terms to filter against */ const getOtherBucketFilterTerms = ( - table: EventData['table'], + table: Pick, columnIndex: number, rowIndex: number ) => { @@ -76,7 +70,11 @@ const getOtherBucketFilterTerms = ( * @param {string} cellValue - value of the current cell * @return {Filter[]|undefined} - list of filters to provide to queryFilter.addFilters() */ -const createFilter = async (table: EventData['table'], columnIndex: number, rowIndex: number) => { +const createFilter = async ( + table: Pick, + columnIndex: number, + rowIndex: number +) => { if (!table || !table.columns || !table.columns[columnIndex]) { return; } @@ -113,11 +111,14 @@ const createFilter = async (table: EventData['table'], columnIndex: number, rowI }; /** @public */ -export const createFiltersFromEvent = async (dataPoints: EventData[], negate?: boolean) => { +export const createFiltersFromValueClickAction = async ({ + data, + negate, +}: ValueClickTriggerContext['data']) => { const filters: Filter[] = []; await Promise.all( - dataPoints + data .filter(point => point) .map(async val => { const { table, column, row } = val; @@ -133,5 +134,5 @@ export const createFiltersFromEvent = async (dataPoints: EventData[], negate?: b }) ); - return filters; + return esFilters.mapAndFlattenFilters(filters); }; diff --git a/src/plugins/data/public/actions/index.ts b/src/plugins/data/public/actions/index.ts index cdb84ff13f25e..ef9014aafe82d 100644 --- a/src/plugins/data/public/actions/index.ts +++ b/src/plugins/data/public/actions/index.ts @@ -18,6 +18,7 @@ */ export { ACTION_GLOBAL_APPLY_FILTER, createFilterAction } from './apply_filter_action'; -export { createFiltersFromEvent } from './filters/create_filters_from_event'; +export { createFiltersFromValueClickAction } from './filters/create_filters_from_value_click'; +export { createFiltersFromRangeSelectAction } from './filters/create_filters_from_range_select'; export { selectRangeAction } from './select_range_action'; export { valueClickAction } from './value_click_action'; diff --git a/src/plugins/data/public/actions/select_range_action.ts b/src/plugins/data/public/actions/select_range_action.ts index 6e1f16a09e803..4882e8eafc0d3 100644 --- a/src/plugins/data/public/actions/select_range_action.ts +++ b/src/plugins/data/public/actions/select_range_action.ts @@ -23,19 +23,17 @@ import { IncompatibleActionError, ActionByType, } from '../../../../plugins/ui_actions/public'; -import { onBrushEvent } from './filters/brush_event'; +import { createFiltersFromRangeSelectAction } from './filters/create_filters_from_range_select'; +import { RangeSelectTriggerContext } from '../../../embeddable/public'; import { FilterManager, TimefilterContract, esFilters } from '..'; export const ACTION_SELECT_RANGE = 'ACTION_SELECT_RANGE'; -export interface SelectRangeActionContext { - data: any; - timeFieldName: string; -} +export type SelectRangeActionContext = RangeSelectTriggerContext; async function isCompatible(context: SelectRangeActionContext) { try { - return Boolean(await onBrushEvent(context.data)); + return Boolean(await createFiltersFromRangeSelectAction(context.data)); } catch { return false; } @@ -48,6 +46,7 @@ export function selectRangeAction( return createAction({ type: ACTION_SELECT_RANGE, id: ACTION_SELECT_RANGE, + getIconType: () => 'filter', getDisplayName: () => { return i18n.translate('data.filter.applyFilterActionTitle', { defaultMessage: 'Apply filter to current view', @@ -59,13 +58,7 @@ export function selectRangeAction( throw new IncompatibleActionError(); } - const filter = await onBrushEvent(data); - - if (!filter) { - return; - } - - const selectedFilters = esFilters.mapAndFlattenFilters([filter]); + const selectedFilters = await createFiltersFromRangeSelectAction(data); if (timeFieldName) { const { timeRangeFilter, restOfFilters } = esFilters.extractTimeFilter( diff --git a/src/plugins/data/public/actions/value_click_action.ts b/src/plugins/data/public/actions/value_click_action.ts index 01c32e27da07d..210a58b3f75aa 100644 --- a/src/plugins/data/public/actions/value_click_action.ts +++ b/src/plugins/data/public/actions/value_click_action.ts @@ -26,21 +26,17 @@ import { } from '../../../../plugins/ui_actions/public'; import { getOverlays, getIndexPatterns } from '../services'; import { applyFiltersPopover } from '../ui/apply_filters'; -import { createFiltersFromEvent } from './filters/create_filters_from_event'; +import { createFiltersFromValueClickAction } from './filters/create_filters_from_value_click'; +import { ValueClickTriggerContext } from '../../../embeddable/public'; import { Filter, FilterManager, TimefilterContract, esFilters } from '..'; export const ACTION_VALUE_CLICK = 'ACTION_VALUE_CLICK'; -export interface ValueClickActionContext { - data: any; - timeFieldName: string; -} +export type ValueClickActionContext = ValueClickTriggerContext; async function isCompatible(context: ValueClickActionContext) { try { - const filters: Filter[] = - (await createFiltersFromEvent(context.data.data || [context.data], context.data.negate)) || - []; + const filters: Filter[] = await createFiltersFromValueClickAction(context.data); return filters.length > 0; } catch { return false; @@ -54,23 +50,23 @@ export function valueClickAction( return createAction({ type: ACTION_VALUE_CLICK, id: ACTION_VALUE_CLICK, + getIconType: () => 'filter', getDisplayName: () => { return i18n.translate('data.filter.applyFilterActionTitle', { defaultMessage: 'Apply filter to current view', }); }, isCompatible, - execute: async ({ timeFieldName, data }: ValueClickActionContext) => { - if (!(await isCompatible({ timeFieldName, data }))) { + execute: async (context: ValueClickActionContext) => { + if (!(await isCompatible(context))) { throw new IncompatibleActionError(); } - const filters: Filter[] = - (await createFiltersFromEvent(data.data || [data], data.negate)) || []; + const filters: Filter[] = await createFiltersFromValueClickAction(context.data); - let selectedFilters: Filter[] = esFilters.mapAndFlattenFilters(filters); + let selectedFilters = filters; - if (selectedFilters.length > 1) { + if (filters.length > 1) { const indexPatterns = await Promise.all( filters.map(filter => { return getIndexPatterns().get(filter.meta.index!); @@ -102,9 +98,9 @@ export function valueClickAction( selectedFilters = await filterSelectionPromise; } - if (timeFieldName) { + if (context.timeFieldName) { const { timeRangeFilter, restOfFilters } = esFilters.extractTimeFilter( - timeFieldName, + context.timeFieldName, selectedFilters ); filterManager.addFilters(restOfFilters); diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index 05a4141483587..e1e2576b2a0e7 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -365,8 +365,6 @@ export { SearchResponse, SearchError, ISearchSource, - SearchSource, - createSearchSource, SearchSourceFields, EsQuerySortValue, SortDirection, diff --git a/src/plugins/data/public/mocks.ts b/src/plugins/data/public/mocks.ts index 2d43cae79ac98..a2df754786a68 100644 --- a/src/plugins/data/public/mocks.ts +++ b/src/plugins/data/public/mocks.ts @@ -45,7 +45,8 @@ const createStartContract = (): Start => { const queryStartMock = queryServiceMock.createStartContract(); return { actions: { - createFiltersFromEvent: jest.fn().mockResolvedValue(['yes']), + createFiltersFromValueClickAction: jest.fn().mockResolvedValue(['yes']), + createFiltersFromRangeSelectAction: jest.fn(), }, autocomplete: autocompleteMock, search: searchStartMock, @@ -67,7 +68,7 @@ const createStartContract = (): Start => { }; }; -export { searchSourceMock } from './search/mocks'; +export { createSearchSourceMock } from './search/mocks'; export { getCalculateAutoTimeExpression } from './search/aggs'; export const dataPluginMock = { diff --git a/src/plugins/data/public/plugin.ts b/src/plugins/data/public/plugin.ts index 1723545b32522..49ef1000d7993 100644 --- a/src/plugins/data/public/plugin.ts +++ b/src/plugins/data/public/plugin.ts @@ -24,13 +24,13 @@ import { Plugin, PackageInfo, } from 'src/core/public'; -import { Storage, IStorageWrapper } from '../../kibana_utils/public'; +import { Storage, IStorageWrapper, createStartServicesGetter } from '../../kibana_utils/public'; import { DataPublicPluginSetup, DataPublicPluginStart, DataSetupDependencies, DataStartDependencies, - GetInternalStartServicesFn, + InternalStartServices, } from './types'; import { AutocompleteService } from './autocomplete'; import { SearchService } from './search/search_service'; @@ -48,8 +48,6 @@ import { setQueryService, setSearchService, setUiSettings, - getFieldFormats, - getNotifications, } from './services'; import { createSearchBar } from './ui/search_bar/create_search_bar'; import { esaggs } from './search/expressions'; @@ -58,7 +56,12 @@ import { VALUE_CLICK_TRIGGER, APPLY_FILTER_TRIGGER, } from '../../ui_actions/public'; -import { ACTION_GLOBAL_APPLY_FILTER, createFilterAction, createFiltersFromEvent } from './actions'; +import { + ACTION_GLOBAL_APPLY_FILTER, + createFilterAction, + createFiltersFromValueClickAction, + createFiltersFromRangeSelectAction, +} from './actions'; import { ApplyGlobalFilterActionContext } from './actions/apply_filter_action'; import { selectRangeAction, @@ -99,15 +102,21 @@ export class DataPublicPlugin implements Plugin { + const { core: coreStart, self }: any = startServices(); + return { + fieldFormats: self.fieldFormats, + notifications: coreStart.notifications, + uiSettings: coreStart.uiSettings, + searchService: self.search, + injectedMetadata: coreStart.injectedMetadata, + }; + }; expressions.registerFunction(esaggs); - const getInternalStartServices: GetInternalStartServicesFn = () => ({ - fieldFormats: getFieldFormats(), - notifications: getNotifications(), - }); - const queryService = this.queryService.setup({ uiSettings: core.uiSettings, storage: this.storage, @@ -145,6 +154,7 @@ export class DataPublicPlugin implements Plugin({ timefilter: { timefil // @public (undocumented) export const createSavedQueryService: (savedObjectsClient: Pick) => SavedQueryService; -// @public -export const createSearchSource: (indexPatterns: Pick) => (searchSourceJson: string, references: SavedObjectReference[]) => Promise; - // Warning: (ae-missing-release-tag) "CustomFilter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -248,7 +245,8 @@ export interface DataPublicPluginSetup { export interface DataPublicPluginStart { // (undocumented) actions: { - createFiltersFromEvent: typeof createFiltersFromEvent; + createFiltersFromValueClickAction: typeof createFiltersFromValueClickAction; + createFiltersFromRangeSelectAction: typeof createFiltersFromRangeSelectAction; }; // Warning: (ae-forgotten-export) The symbol "AutocompleteStart" needs to be exported by the entry point index.d.ts // @@ -484,7 +482,7 @@ export type FieldFormatId = FIELD_FORMAT_IDS | string; export const fieldFormats: { FieldFormat: typeof FieldFormat; FieldFormatsRegistry: typeof FieldFormatsRegistry; - serialize: (agg: import("./search").AggConfig) => import("../../expressions/common").SerializedFieldFormat; + serialize: (agg: import("./search").AggConfig) => import("../../expressions").SerializedFieldFormat; DEFAULT_CONVERTER_COLOR: { range: string; regex: string; @@ -636,21 +634,21 @@ export type IAggType = AggType; // Warning: (ae-missing-release-tag) "IDataPluginServices" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface IDataPluginServices extends Partial { +export interface IDataPluginServices extends Partial { // (undocumented) appName: string; // (undocumented) data: DataPublicPluginStart; // (undocumented) - http: CoreStart['http']; + http: CoreStart_2['http']; // (undocumented) - notifications: CoreStart['notifications']; + notifications: CoreStart_2['notifications']; // (undocumented) - savedObjects: CoreStart['savedObjects']; + savedObjects: CoreStart_2['savedObjects']; // (undocumented) storage: IStorageWrapper; // (undocumented) - uiSettings: CoreStart['uiSettings']; + uiSettings: CoreStart_2['uiSettings']; } // Warning: (ae-missing-release-tag) "IEsSearchRequest" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -1098,7 +1096,7 @@ export type ISearch = // @public (undocumented) export interface ISearchContext { // (undocumented) - core: CoreStart_2; + core: CoreStart; // (undocumented) getSearchStrategy: (name: T) => TSearchStrategyProvider; } @@ -1116,7 +1114,7 @@ export interface ISearchOptions { signal?: AbortSignal; } -// Warning: (ae-missing-release-tag) "ISearchSource" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-forgotten-export) The symbol "SearchSource" needs to be exported by the entry point index.d.ts // // @public (undocumented) export type ISearchSource = Pick; @@ -1311,7 +1309,7 @@ export class Plugin implements Plugin_2; - getField(field: K, recurse?: boolean): SearchSourceFields[K]; - // (undocumented) - getFields(): { - type?: string | undefined; - query?: import("../..").Query | undefined; - filter?: Filter | Filter[] | (() => Filter | Filter[] | undefined) | undefined; - sort?: Record | Record[] | undefined; - highlight?: any; - highlightAll?: boolean | undefined; - aggs?: any; - from?: number | undefined; - size?: number | undefined; - source?: string | boolean | string[] | undefined; - version?: boolean | undefined; - fields?: string | boolean | string[] | undefined; - index?: import("../..").IndexPattern | undefined; - searchAfter?: import("./types").EsQuerySearchAfter | undefined; - timeout?: string | undefined; - terminate_after?: number | undefined; - }; - // (undocumented) - getId(): string; - getOwnField(field: K): SearchSourceFields[K]; - getParent(): SearchSource | undefined; - // (undocumented) - getSearchRequestBody(): Promise; - // (undocumented) - history: SearchRequest[]; - onRequestStart(handler: (searchSource: ISearchSource, options?: FetchOptions) => Promise): void; - serialize(): { - searchSourceJSON: string; - references: SavedObjectReference[]; - }; - // (undocumented) - setField(field: K, value: SearchSourceFields[K]): this; - // (undocumented) - setFields(newFields: SearchSourceFields): this; - // Warning: (ae-forgotten-export) The symbol "SearchSourceOptions" needs to be exported by the entry point index.d.ts - setParent(parent?: ISearchSource, options?: SearchSourceOptions): this; - setPreferredSearchStrategyId(searchStrategyId: string): void; -} - // Warning: (ae-missing-release-tag) "SearchSourceFields" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -1875,25 +1818,26 @@ export type TSearchStrategyProvider = (context: ISearc // src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "flattenHitWrapper" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "getRoutes" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "formatHitProvider" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:383:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:383:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:383:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:383:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:388:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:389:1 - (ae-forgotten-export) The symbol "convertDateRangeToString" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:391:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:400:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:401:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:402:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:406:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:407:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:410:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:411:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:414:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:381:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:381:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:381:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:381:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:386:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:387:1 - (ae-forgotten-export) The symbol "convertDateRangeToString" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:389:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:398:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:399:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:400:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:404:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:405:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:408:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:409:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:412: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 "createFiltersFromEvent" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/types.ts:60:5 - (ae-forgotten-export) The symbol "IndexPatternSelectProps" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/types.ts:52:5 - (ae-forgotten-export) The symbol "createFiltersFromValueClickAction" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/types.ts:53:5 - (ae-forgotten-export) The symbol "createFiltersFromRangeSelectAction" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/types.ts:61:5 - (ae-forgotten-export) The symbol "IndexPatternSelectProps" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/src/plugins/data/public/search/aggs/agg_params.test.ts b/src/plugins/data/public/search/aggs/agg_params.test.ts index 784be803e2644..e116bdca157ff 100644 --- a/src/plugins/data/public/search/aggs/agg_params.test.ts +++ b/src/plugins/data/public/search/aggs/agg_params.test.ts @@ -25,13 +25,15 @@ import { AggParamType } from '../aggs/param_types/agg'; import { fieldFormatsServiceMock } from '../../field_formats/mocks'; import { notificationServiceMock } from '../../../../../../src/core/public/mocks'; import { AggTypeDependencies } from './agg_type'; +import { InternalStartServices } from '../../types'; describe('AggParams class', () => { const aggTypesDependencies: AggTypeDependencies = { - getInternalStartServices: () => ({ - fieldFormats: fieldFormatsServiceMock.createStartContract(), - notifications: notificationServiceMock.createStartContract(), - }), + getInternalStartServices: () => + (({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + } as unknown) as InternalStartServices), }; describe('constructor args', () => { diff --git a/src/plugins/data/public/search/aggs/agg_type.test.ts b/src/plugins/data/public/search/aggs/agg_type.test.ts index 0c9e110c34ae6..369ae0ce0b3a5 100644 --- a/src/plugins/data/public/search/aggs/agg_type.test.ts +++ b/src/plugins/data/public/search/aggs/agg_type.test.ts @@ -21,19 +21,21 @@ import { AggType, AggTypeConfig, AggTypeDependencies } from './agg_type'; import { IAggConfig } from './agg_config'; import { fieldFormatsServiceMock } from '../../field_formats/mocks'; import { notificationServiceMock } from '../../../../../../src/core/public/mocks'; +import { InternalStartServices } from '../../types'; describe('AggType Class', () => { let dependencies: AggTypeDependencies; beforeEach(() => { dependencies = { - getInternalStartServices: () => ({ - fieldFormats: { - ...fieldFormatsServiceMock.createStartContract(), - getDefaultInstance: jest.fn(() => 'default') as any, - }, - notifications: notificationServiceMock.createStartContract(), - }), + getInternalStartServices: () => + (({ + fieldFormats: { + ...fieldFormatsServiceMock.createStartContract(), + getDefaultInstance: jest.fn(() => 'default') as any, + }, + notifications: notificationServiceMock.createStartContract(), + } as unknown) as InternalStartServices), }; }); diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/date_histogram.test.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/date_histogram.test.ts index 7778fcb36bcd6..bb73c8a39df19 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/date_histogram.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/date_histogram.test.ts @@ -32,6 +32,7 @@ import { RangeFilter } from '../../../../../common'; import { coreMock, notificationServiceMock } from '../../../../../../../core/public/mocks'; import { queryServiceMock } from '../../../../query/mocks'; import { fieldFormatsServiceMock } from '../../../../field_formats/mocks'; +import { InternalStartServices } from '../../../../types'; describe('AggConfig Filters', () => { describe('date_histogram', () => { @@ -47,10 +48,11 @@ describe('AggConfig Filters', () => { aggTypesDependencies = { uiSettings, query: queryServiceMock.createSetupContract(), - getInternalStartServices: () => ({ - fieldFormats: fieldFormatsServiceMock.createStartContract(), - notifications: notificationServiceMock.createStartContract(), - }), + getInternalStartServices: () => + (({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + } as unknown) as InternalStartServices), }; mockDataServices(); diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/date_range.test.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/date_range.test.ts index 4207fa92736f8..0d66d9cfcdca2 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/date_range.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/date_range.test.ts @@ -28,6 +28,7 @@ import { BUCKET_TYPES } from '../bucket_agg_types'; import { IBucketAggConfig } from '../bucket_agg_type'; import { coreMock, notificationServiceMock } from '../../../../../../../core/public/mocks'; import { fieldFormatsServiceMock } from '../../../../field_formats/mocks'; +import { InternalStartServices } from '../../../../types'; describe('AggConfig Filters', () => { describe('Date range', () => { @@ -38,10 +39,11 @@ describe('AggConfig Filters', () => { aggTypesDependencies = { uiSettings, - getInternalStartServices: () => ({ - fieldFormats: fieldFormatsServiceMock.createStartContract(), - notifications: notificationServiceMock.createStartContract(), - }), + getInternalStartServices: () => + (({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + } as unknown) as InternalStartServices), }; }); diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/filters.test.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/filters.test.ts index bf05f7463db6c..0fdb07cc4198a 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/filters.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/filters.test.ts @@ -24,6 +24,7 @@ import { mockAggTypesRegistry } from '../../test_helpers'; import { IBucketAggConfig } from '../bucket_agg_type'; import { coreMock, notificationServiceMock } from '../../../../../../../core/public/mocks'; import { fieldFormatsServiceMock } from '../../../../field_formats/mocks'; +import { InternalStartServices } from '../../../../types'; describe('AggConfig Filters', () => { describe('filters', () => { @@ -34,10 +35,11 @@ describe('AggConfig Filters', () => { aggTypesDependencies = { uiSettings, - getInternalStartServices: () => ({ - fieldFormats: fieldFormatsServiceMock.createStartContract(), - notifications: notificationServiceMock.createStartContract(), - }), + getInternalStartServices: () => + (({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + } as unknown) as InternalStartServices), }; }); diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/filters.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/filters.ts index 1999b759a23d0..72d2029a12b0d 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/filters.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/filters.ts @@ -28,6 +28,6 @@ export const createFilterFilters = (aggConfig: IBucketAggConfig, key: string) => const indexPattern = aggConfig.getIndexPattern(); if (filter && indexPattern && indexPattern.id) { - return buildQueryFilter(filter.query, indexPattern.id, key); + return buildQueryFilter(filter, indexPattern.id, key); } }; diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/ip_range.test.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/ip_range.test.ts index d85576a0ccb14..990adde5f8a0b 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/ip_range.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/ip_range.test.ts @@ -26,16 +26,18 @@ import { BUCKET_TYPES } from '../bucket_agg_types'; import { IBucketAggConfig } from '../bucket_agg_type'; import { fieldFormatsServiceMock } from '../../../../field_formats/mocks'; import { notificationServiceMock } from '../../../../../../../core/public/mocks'; +import { InternalStartServices } from '../../../../types'; describe('AggConfig Filters', () => { describe('IP range', () => { const fieldFormats = fieldFormatsServiceMock.createStartContract(); const typesRegistry = mockAggTypesRegistry([ getIpRangeBucketAgg({ - getInternalStartServices: () => ({ - fieldFormats, - notifications: notificationServiceMock.createStartContract(), - }), + getInternalStartServices: () => + (({ + fieldFormats, + notifications: notificationServiceMock.createStartContract(), + } as unknown) as InternalStartServices), }), ]); const getAggConfigs = (aggs: CreateAggConfigParams[]) => { diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/range.test.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/range.test.ts index cadd8e9fe13ed..564e7b4763c8d 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/range.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/range.test.ts @@ -26,6 +26,7 @@ import { BUCKET_TYPES } from '../bucket_agg_types'; import { IBucketAggConfig } from '../bucket_agg_type'; import { fieldFormatsServiceMock } from '../../../../field_formats/mocks'; import { notificationServiceMock } from '../../../../../../../core/public/mocks'; +import { InternalStartServices } from '../../../../types'; describe('AggConfig Filters', () => { describe('range', () => { @@ -33,10 +34,11 @@ describe('AggConfig Filters', () => { beforeEach(() => { aggTypesDependencies = { - getInternalStartServices: () => ({ - fieldFormats: fieldFormatsServiceMock.createStartContract(), - notifications: notificationServiceMock.createStartContract(), - }), + getInternalStartServices: () => + (({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + } as unknown) as InternalStartServices), }; mockDataServices(); diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/terms.test.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/terms.test.ts index d9ff63613b640..36e4bef025ef9 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/terms.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/terms.test.ts @@ -27,6 +27,7 @@ import { Filter, ExistsFilter } from '../../../../../common'; import { RangeBucketAggDependencies } from '../range'; import { fieldFormatsServiceMock } from '../../../../field_formats/mocks'; import { notificationServiceMock } from '../../../../../../../core/public/mocks'; +import { InternalStartServices } from '../../../../types'; describe('AggConfig Filters', () => { describe('terms', () => { @@ -34,10 +35,11 @@ describe('AggConfig Filters', () => { beforeEach(() => { aggTypesDependencies = { - getInternalStartServices: () => ({ - fieldFormats: fieldFormatsServiceMock.createStartContract(), - notifications: notificationServiceMock.createStartContract(), - }), + getInternalStartServices: () => + (({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + } as unknown) as InternalStartServices), }; }); diff --git a/src/plugins/data/public/search/aggs/buckets/date_range.test.ts b/src/plugins/data/public/search/aggs/buckets/date_range.test.ts index f78f0cce732e7..e1881c3bbc7f4 100644 --- a/src/plugins/data/public/search/aggs/buckets/date_range.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/date_range.test.ts @@ -23,6 +23,7 @@ import { AggConfigs } from '../agg_configs'; import { mockAggTypesRegistry } from '../test_helpers'; import { BUCKET_TYPES } from './bucket_agg_types'; import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; +import { InternalStartServices } from '../../../types'; describe('date_range params', () => { let aggTypesDependencies: DateRangeBucketAggDependencies; @@ -32,10 +33,11 @@ describe('date_range params', () => { aggTypesDependencies = { uiSettings, - getInternalStartServices: () => ({ - fieldFormats: fieldFormatsServiceMock.createStartContract(), - notifications: notificationServiceMock.createStartContract(), - }), + getInternalStartServices: () => + (({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + } as unknown) as InternalStartServices), }; }); diff --git a/src/plugins/data/public/search/aggs/buckets/filters.ts b/src/plugins/data/public/search/aggs/buckets/filters.ts index a42cb70a62b7d..fe013928bba65 100644 --- a/src/plugins/data/public/search/aggs/buckets/filters.ts +++ b/src/plugins/data/public/search/aggs/buckets/filters.ts @@ -107,7 +107,7 @@ export const getFiltersBucketAgg = ({ (typeof filter.input.query === 'string' ? filter.input.query : toAngularJSON(filter.input.query)); - filters[label] = { query }; + filters[label] = query; }, {} ); diff --git a/src/plugins/data/public/search/aggs/buckets/geo_hash.test.ts b/src/plugins/data/public/search/aggs/buckets/geo_hash.test.ts index 226faefe43482..877a817984dc6 100644 --- a/src/plugins/data/public/search/aggs/buckets/geo_hash.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/geo_hash.test.ts @@ -24,6 +24,7 @@ import { BUCKET_TYPES } from './bucket_agg_types'; import { notificationServiceMock } from '../../../../../../../src/core/public/mocks'; import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; import { BucketAggType, IBucketAggConfig } from './bucket_agg_type'; +import { InternalStartServices } from '../../../types'; describe('Geohash Agg', () => { let aggTypesDependencies: GeoHashBucketAggDependencies; @@ -31,10 +32,11 @@ describe('Geohash Agg', () => { beforeEach(() => { aggTypesDependencies = { - getInternalStartServices: () => ({ - fieldFormats: fieldFormatsServiceMock.createStartContract(), - notifications: notificationServiceMock.createStartContract(), - }), + getInternalStartServices: () => + (({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + } as unknown) as InternalStartServices), }; geoHashBucketAgg = getGeoHashBucketAgg(aggTypesDependencies); diff --git a/src/plugins/data/public/search/aggs/buckets/histogram.test.ts b/src/plugins/data/public/search/aggs/buckets/histogram.test.ts index a55c32951232a..4756669f5b4b3 100644 --- a/src/plugins/data/public/search/aggs/buckets/histogram.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/histogram.test.ts @@ -29,6 +29,7 @@ import { } from './histogram'; import { BucketAggType } from './bucket_agg_type'; import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; +import { InternalStartServices } from '../../../types'; describe('Histogram Agg', () => { let aggTypesDependencies: HistogramBucketAggDependencies; @@ -38,10 +39,11 @@ describe('Histogram Agg', () => { aggTypesDependencies = { uiSettings, - getInternalStartServices: () => ({ - fieldFormats: fieldFormatsServiceMock.createStartContract(), - notifications: notificationServiceMock.createStartContract(), - }), + getInternalStartServices: () => + (({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + } as unknown) as InternalStartServices), }; }); diff --git a/src/plugins/data/public/search/aggs/buckets/range.test.ts b/src/plugins/data/public/search/aggs/buckets/range.test.ts index 144d2b779e950..4c2d3af1ab734 100644 --- a/src/plugins/data/public/search/aggs/buckets/range.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/range.test.ts @@ -24,6 +24,7 @@ import { BUCKET_TYPES } from './bucket_agg_types'; import { FieldFormatsGetConfigFn, NumberFormat } from '../../../../common'; import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; import { notificationServiceMock } from '../../../../../../../src/core/public/mocks'; +import { InternalStartServices } from '../../../types'; const buckets = [ { @@ -50,10 +51,11 @@ describe('Range Agg', () => { beforeEach(() => { aggTypesDependencies = { - getInternalStartServices: () => ({ - fieldFormats: fieldFormatsServiceMock.createStartContract(), - notifications: notificationServiceMock.createStartContract(), - }), + getInternalStartServices: () => + (({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + } as unknown) as InternalStartServices), }; mockDataServices(); diff --git a/src/plugins/data/public/search/aggs/buckets/significant_terms.test.ts b/src/plugins/data/public/search/aggs/buckets/significant_terms.test.ts index d0ace5a50c28d..156f7f8108482 100644 --- a/src/plugins/data/public/search/aggs/buckets/significant_terms.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/significant_terms.test.ts @@ -26,6 +26,7 @@ import { } from './significant_terms'; import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; import { notificationServiceMock } from '../../../../../../../src/core/public/mocks'; +import { InternalStartServices } from '../../../types'; describe('Significant Terms Agg', () => { describe('order agg editor UI', () => { @@ -34,10 +35,11 @@ describe('Significant Terms Agg', () => { beforeEach(() => { aggTypesDependencies = { - getInternalStartServices: () => ({ - fieldFormats: fieldFormatsServiceMock.createStartContract(), - notifications: notificationServiceMock.createStartContract(), - }), + getInternalStartServices: () => + (({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + } as unknown) as InternalStartServices), }; }); diff --git a/src/plugins/data/public/search/aggs/index.test.ts b/src/plugins/data/public/search/aggs/index.test.ts index 419c3fdab1caf..382c5a10c2da5 100644 --- a/src/plugins/data/public/search/aggs/index.test.ts +++ b/src/plugins/data/public/search/aggs/index.test.ts @@ -24,6 +24,7 @@ import { isBucketAggType } from './buckets/bucket_agg_type'; import { isMetricAggType } from './metrics/metric_agg_type'; import { QueryStart } from '../../query'; import { FieldFormatsStart } from '../../field_formats'; +import { InternalStartServices } from '../../types'; describe('AggTypesComponent', () => { const coreSetup = coreMock.createSetup(); @@ -32,10 +33,11 @@ describe('AggTypesComponent', () => { const aggTypes = getAggTypes({ uiSettings: coreSetup.uiSettings, query: {} as QueryStart, - getInternalStartServices: () => ({ - notifications: coreStart.notifications, - fieldFormats: {} as FieldFormatsStart, - }), + getInternalStartServices: () => + (({ + notifications: coreStart.notifications, + fieldFormats: {} as FieldFormatsStart, + } as unknown) as InternalStartServices), }); const { buckets, metrics } = aggTypes; diff --git a/src/plugins/data/public/search/aggs/metrics/median.test.ts b/src/plugins/data/public/search/aggs/metrics/median.test.ts index de3ca646ead9e..71c48f04a3ca8 100644 --- a/src/plugins/data/public/search/aggs/metrics/median.test.ts +++ b/src/plugins/data/public/search/aggs/metrics/median.test.ts @@ -23,14 +23,16 @@ import { mockAggTypesRegistry } from '../test_helpers'; import { METRIC_TYPES } from './metric_agg_types'; import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; import { notificationServiceMock } from '../../../../../../../src/core/public/mocks'; +import { InternalStartServices } from '../../../types'; describe('AggTypeMetricMedianProvider class', () => { let aggConfigs: IAggConfigs; const aggTypesDependencies: MedianMetricAggDependencies = { - getInternalStartServices: () => ({ - fieldFormats: fieldFormatsServiceMock.createStartContract(), - notifications: notificationServiceMock.createStartContract(), - }), + getInternalStartServices: () => + (({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + } as unknown) as InternalStartServices), }; beforeEach(() => { diff --git a/src/plugins/data/public/search/aggs/metrics/parent_pipeline.test.ts b/src/plugins/data/public/search/aggs/metrics/parent_pipeline.test.ts index 3beb92a2fa000..f386034ea8a7b 100644 --- a/src/plugins/data/public/search/aggs/metrics/parent_pipeline.test.ts +++ b/src/plugins/data/public/search/aggs/metrics/parent_pipeline.test.ts @@ -25,14 +25,15 @@ import { AggConfigs } from '../agg_configs'; import { mockAggTypesRegistry } from '../test_helpers'; import { IMetricAggConfig, MetricAggType } from './metric_agg_type'; import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; -import { GetInternalStartServicesFn } from '../../../types'; +import { GetInternalStartServicesFn, InternalStartServices } from '../../../types'; import { notificationServiceMock } from '../../../../../../../src/core/public/mocks'; describe('parent pipeline aggs', function() { - const getInternalStartServices: GetInternalStartServicesFn = () => ({ - fieldFormats: fieldFormatsServiceMock.createStartContract(), - notifications: notificationServiceMock.createStartContract(), - }); + const getInternalStartServices: GetInternalStartServicesFn = () => + (({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + } as unknown) as InternalStartServices); const typesRegistry = mockAggTypesRegistry(); diff --git a/src/plugins/data/public/search/aggs/metrics/percentile_ranks.test.ts b/src/plugins/data/public/search/aggs/metrics/percentile_ranks.test.ts index 1b94ecd602075..d20530a17ca65 100644 --- a/src/plugins/data/public/search/aggs/metrics/percentile_ranks.test.ts +++ b/src/plugins/data/public/search/aggs/metrics/percentile_ranks.test.ts @@ -27,14 +27,16 @@ import { mockAggTypesRegistry } from '../test_helpers'; import { METRIC_TYPES } from './metric_agg_types'; import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; import { notificationServiceMock } from '../../../../../../../src/core/public/mocks'; +import { InternalStartServices } from '../../../types'; describe('AggTypesMetricsPercentileRanksProvider class', function() { let aggConfigs: IAggConfigs; const aggTypesDependencies: PercentileRanksMetricAggDependencies = { - getInternalStartServices: () => ({ - fieldFormats: fieldFormatsServiceMock.createStartContract(), - notifications: notificationServiceMock.createStartContract(), - }), + getInternalStartServices: () => + (({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + } as unknown) as InternalStartServices), }; beforeEach(() => { diff --git a/src/plugins/data/public/search/aggs/metrics/percentiles.test.ts b/src/plugins/data/public/search/aggs/metrics/percentiles.test.ts index 76da2fe3eb62c..0ac1e8417514c 100644 --- a/src/plugins/data/public/search/aggs/metrics/percentiles.test.ts +++ b/src/plugins/data/public/search/aggs/metrics/percentiles.test.ts @@ -27,14 +27,16 @@ import { mockAggTypesRegistry } from '../test_helpers'; import { METRIC_TYPES } from './metric_agg_types'; import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; import { notificationServiceMock } from '../../../../../../../src/core/public/mocks'; +import { InternalStartServices } from '../../../types'; describe('AggTypesMetricsPercentilesProvider class', () => { let aggConfigs: IAggConfigs; const aggTypesDependencies: PercentilesMetricAggDependencies = { - getInternalStartServices: () => ({ - fieldFormats: fieldFormatsServiceMock.createStartContract(), - notifications: notificationServiceMock.createStartContract(), - }), + getInternalStartServices: () => + (({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + } as unknown) as InternalStartServices), }; beforeEach(() => { diff --git a/src/plugins/data/public/search/aggs/metrics/sibling_pipeline.test.ts b/src/plugins/data/public/search/aggs/metrics/sibling_pipeline.test.ts index a47aa2c677ade..5e1834d3b4935 100644 --- a/src/plugins/data/public/search/aggs/metrics/sibling_pipeline.test.ts +++ b/src/plugins/data/public/search/aggs/metrics/sibling_pipeline.test.ts @@ -26,14 +26,15 @@ import { AggConfigs } from '../agg_configs'; import { IMetricAggConfig, MetricAggType } from './metric_agg_type'; import { mockAggTypesRegistry } from '../test_helpers'; import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; -import { GetInternalStartServicesFn } from '../../../types'; +import { GetInternalStartServicesFn, InternalStartServices } from '../../../types'; import { notificationServiceMock } from '../../../../../../../src/core/public/mocks'; describe('sibling pipeline aggs', () => { - const getInternalStartServices: GetInternalStartServicesFn = () => ({ - fieldFormats: fieldFormatsServiceMock.createStartContract(), - notifications: notificationServiceMock.createStartContract(), - }); + const getInternalStartServices: GetInternalStartServicesFn = () => + (({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + } as unknown) as InternalStartServices); const typesRegistry = mockAggTypesRegistry(); diff --git a/src/plugins/data/public/search/aggs/metrics/std_deviation.test.ts b/src/plugins/data/public/search/aggs/metrics/std_deviation.test.ts index d2370e1fed02c..536764b2bcf0b 100644 --- a/src/plugins/data/public/search/aggs/metrics/std_deviation.test.ts +++ b/src/plugins/data/public/search/aggs/metrics/std_deviation.test.ts @@ -27,13 +27,15 @@ import { mockAggTypesRegistry } from '../test_helpers'; import { METRIC_TYPES } from './metric_agg_types'; import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; import { notificationServiceMock } from '../../../../../../../src/core/public/mocks'; +import { InternalStartServices } from '../../../types'; describe('AggTypeMetricStandardDeviationProvider class', () => { const aggTypesDependencies: StdDeviationMetricAggDependencies = { - getInternalStartServices: () => ({ - fieldFormats: fieldFormatsServiceMock.createStartContract(), - notifications: notificationServiceMock.createStartContract(), - }), + getInternalStartServices: () => + (({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + } as unknown) as InternalStartServices), }; const typesRegistry = mockAggTypesRegistry([getStdDeviationMetricAgg(aggTypesDependencies)]); const getAggConfigs = (customLabel?: string) => { diff --git a/src/plugins/data/public/search/aggs/metrics/top_hit.test.ts b/src/plugins/data/public/search/aggs/metrics/top_hit.test.ts index 142b8e4c83301..617e458cf6243 100644 --- a/src/plugins/data/public/search/aggs/metrics/top_hit.test.ts +++ b/src/plugins/data/public/search/aggs/metrics/top_hit.test.ts @@ -25,15 +25,17 @@ import { IMetricAggConfig } from './metric_agg_type'; import { KBN_FIELD_TYPES } from '../../../../common'; import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; import { notificationServiceMock } from '../../../../../../../src/core/public/mocks'; +import { InternalStartServices } from '../../../types'; describe('Top hit metric', () => { let aggDsl: Record; let aggConfig: IMetricAggConfig; const aggTypesDependencies: TopHitMetricAggDependencies = { - getInternalStartServices: () => ({ - fieldFormats: fieldFormatsServiceMock.createStartContract(), - notifications: notificationServiceMock.createStartContract(), - }), + getInternalStartServices: () => + (({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + } as unknown) as InternalStartServices), }; const init = ({ diff --git a/src/plugins/data/public/search/aggs/param_types/field.test.ts b/src/plugins/data/public/search/aggs/param_types/field.test.ts index ea7931130b84a..2c51d9709f906 100644 --- a/src/plugins/data/public/search/aggs/param_types/field.test.ts +++ b/src/plugins/data/public/search/aggs/param_types/field.test.ts @@ -23,13 +23,15 @@ import { ES_FIELD_TYPES, KBN_FIELD_TYPES } from '../../../../common'; import { IAggConfig } from '../agg_config'; import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; import { notificationServiceMock } from '../../../../../../../src/core/public/mocks'; +import { InternalStartServices } from '../../../types'; describe('Field', () => { const fieldParamTypeDependencies: FieldParamTypeDependencies = { - getInternalStartServices: () => ({ - fieldFormats: fieldFormatsServiceMock.createStartContract(), - notifications: notificationServiceMock.createStartContract(), - }), + getInternalStartServices: () => + (({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + } as unknown) as InternalStartServices), }; const indexPattern = { diff --git a/src/plugins/data/public/search/aggs/test_helpers/mock_agg_types_registry.ts b/src/plugins/data/public/search/aggs/test_helpers/mock_agg_types_registry.ts index 2383affa2a8c5..3ff2fbf35ad7e 100644 --- a/src/plugins/data/public/search/aggs/test_helpers/mock_agg_types_registry.ts +++ b/src/plugins/data/public/search/aggs/test_helpers/mock_agg_types_registry.ts @@ -17,7 +17,6 @@ * under the License. */ -// eslint-disable-next-line @kbn/eslint/no-restricted-paths import { coreMock, notificationServiceMock } from '../../../../../../../src/core/public/mocks'; import { AggTypesRegistry, AggTypesRegistryStart } from '../agg_types_registry'; import { getAggTypes } from '../agg_types'; @@ -25,6 +24,7 @@ import { BucketAggType } from '../buckets/bucket_agg_type'; import { MetricAggType } from '../metrics/metric_agg_type'; import { queryServiceMock } from '../../../query/mocks'; import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; +import { InternalStartServices } from '../../../types'; /** * Testing utility which creates a new instance of AggTypesRegistry, @@ -53,14 +53,19 @@ export function mockAggTypesRegistry | MetricAggTyp } }); } else { - const core = coreMock.createSetup(); + const coreSetup = coreMock.createSetup(); + const coreStart = coreMock.createStart(); + const aggTypes = getAggTypes({ - uiSettings: core.uiSettings, + uiSettings: coreSetup.uiSettings, query: queryServiceMock.createSetupContract(), - getInternalStartServices: () => ({ - fieldFormats: fieldFormatsServiceMock.createStartContract(), - notifications: notificationServiceMock.createStartContract(), - }), + getInternalStartServices: () => + (({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + uiSettings: coreStart.uiSettings, + injectedMetadata: coreStart.injectedMetadata, + } as unknown) as InternalStartServices), }); aggTypes.buckets.forEach(type => registrySetup.registerBucket(type)); diff --git a/src/plugins/data/public/search/expressions/esaggs.ts b/src/plugins/data/public/search/expressions/esaggs.ts index 2341f4fe447db..087b83127079f 100644 --- a/src/plugins/data/public/search/expressions/esaggs.ts +++ b/src/plugins/data/public/search/expressions/esaggs.ts @@ -30,7 +30,7 @@ import { PersistedState } from '../../../../../plugins/visualizations/public'; import { Adapters } from '../../../../../plugins/inspector/public'; import { IAggConfigs } from '../aggs'; -import { ISearchSource, SearchSource } from '../search_source'; +import { ISearchSource } from '../search_source'; import { tabifyAggResponse } from '../tabify'; import { Filter, Query, serializeFieldFormat, TimeRange } from '../../../common'; import { FilterManager, getTime } from '../../query'; @@ -253,7 +253,8 @@ export const esaggs = (): ExpressionFunctionDefinition { test('Passes the additional arguments it is given to the search strategy', () => { const searchRequests = [{ _searchStrategyId: 0 }]; - const args = { searchService: {}, config: {}, esShardTimeout: 0 } as FetchHandlers; + const args = { legacySearchService: {}, config: {}, esShardTimeout: 0 } as FetchHandlers; callClient(searchRequests, [], args); diff --git a/src/plugins/data/public/search/legacy/default_search_strategy.test.ts b/src/plugins/data/public/search/legacy/default_search_strategy.test.ts index 835b02b3cd5c7..9e3d65a69bf02 100644 --- a/src/plugins/data/public/search/legacy/default_search_strategy.test.ts +++ b/src/plugins/data/public/search/legacy/default_search_strategy.test.ts @@ -62,10 +62,10 @@ describe('defaultSearchStrategy', function() { }, ], esShardTimeout: 0, - searchService, + legacySearchService: searchService.__LEGACY, }; - es = searchArgs.searchService.__LEGACY.esClient; + es = searchArgs.legacySearchService.esClient; }); test('does not send max_concurrent_shard_requests by default', async () => { diff --git a/src/plugins/data/public/search/legacy/default_search_strategy.ts b/src/plugins/data/public/search/legacy/default_search_strategy.ts index 1552410f9090c..3216803dcbfa2 100644 --- a/src/plugins/data/public/search/legacy/default_search_strategy.ts +++ b/src/plugins/data/public/search/legacy/default_search_strategy.ts @@ -32,11 +32,11 @@ export const defaultSearchStrategy: SearchStrategyProvider = { function msearch({ searchRequests, - searchService, + legacySearchService, config, esShardTimeout, }: SearchStrategySearchParams) { - const es = searchService.__LEGACY.esClient; + const es = legacySearchService.esClient; const inlineRequests = searchRequests.map(({ index, body, search_type: searchType }) => { const inlineHeader = { index: index.title || index, diff --git a/src/plugins/data/public/search/mocks.ts b/src/plugins/data/public/search/mocks.ts index cb1c625a72959..dd196074c8173 100644 --- a/src/plugins/data/public/search/mocks.ts +++ b/src/plugins/data/public/search/mocks.ts @@ -20,20 +20,19 @@ import { searchAggsSetupMock, searchAggsStartMock } from './aggs/mocks'; import { AggTypeFieldFilters } from './aggs/param_types/filter'; import { ISearchStart } from './types'; +import { searchSourceMock, createSearchSourceMock } from './search_source/mocks'; -export * from './search_source/mocks'; - -export const searchSetupMock = { +const searchSetupMock = { aggs: searchAggsSetupMock(), registerSearchStrategyContext: jest.fn(), registerSearchStrategyProvider: jest.fn(), }; -export const searchStartMock: jest.Mocked = { +const searchStartMock: jest.Mocked = { aggs: searchAggsStartMock(), setInterceptor: jest.fn(), search: jest.fn(), - createSearchSource: jest.fn(), + searchSource: searchSourceMock, __LEGACY: { AggConfig: jest.fn() as any, AggType: jest.fn(), @@ -48,3 +47,5 @@ export const searchStartMock: jest.Mocked = { }, }, }; + +export { searchSetupMock, searchStartMock, createSearchSourceMock }; diff --git a/src/plugins/data/public/search/search_service.ts b/src/plugins/data/public/search/search_service.ts index 916278a96659b..3f3266b5fe90f 100644 --- a/src/plugins/data/public/search/search_service.ts +++ b/src/plugins/data/public/search/search_service.ts @@ -20,13 +20,18 @@ import { Plugin, CoreSetup, CoreStart, PackageInfo } from '../../../../core/public'; import { SYNC_SEARCH_STRATEGY, syncSearchStrategyProvider } from './sync_search_strategy'; +import { + createSearchSourceFromJSON, + SearchSource, + SearchSourceDependencies, + SearchSourceFields, +} from './search_source'; import { ISearchSetup, ISearchStart, TSearchStrategyProvider, TSearchStrategiesMap } from './types'; import { TStrategyTypes } from './strategy_types'; import { getEsClient, LegacyApiCaller } from './legacy'; import { ES_SEARCH_STRATEGY, DEFAULT_SEARCH_STRATEGY } from '../../common/search'; import { esSearchStrategyProvider } from './es_search'; import { IndexPatternsContract } from '../index_patterns/index_patterns'; -import { createSearchSource } from './search_source'; import { QuerySetup } from '../query'; import { GetInternalStartServicesFn } from '../types'; import { SearchInterceptor } from './search_interceptor'; @@ -43,8 +48,8 @@ import { parentPipelineAggHelper, siblingPipelineAggHelper, } from './aggs'; - import { FieldFormatsStart } from '../field_formats'; +import { ISearchGeneric } from './i_search'; interface SearchServiceSetupDependencies { packageInfo: PackageInfo; @@ -52,9 +57,9 @@ interface SearchServiceSetupDependencies { getInternalStartServices: GetInternalStartServicesFn; } -interface SearchStartDependencies { - fieldFormats: FieldFormatsStart; +interface SearchServiceStartDependencies { indexPatterns: IndexPatternsContract; + fieldFormats: FieldFormatsStart; } /** @@ -117,10 +122,7 @@ export class SearchService implements Plugin { }; } - public start( - core: CoreStart, - { fieldFormats, indexPatterns }: SearchStartDependencies - ): ISearchStart { + public start(core: CoreStart, dependencies: SearchServiceStartDependencies): ISearchStart { /** * A global object that intercepts all searches and provides convenience methods for cancelling * all pending search requests, as well as getting the number of pending search requests. @@ -135,40 +137,54 @@ export class SearchService implements Plugin { const aggTypesStart = this.aggTypesRegistry.start(); + const search: ISearchGeneric = (request, options, strategyName) => { + const strategyProvider = this.getSearchStrategy(strategyName || DEFAULT_SEARCH_STRATEGY); + const searchStrategy = strategyProvider({ + core, + getSearchStrategy: this.getSearchStrategy, + }); + return this.searchInterceptor.search(searchStrategy.search as any, request, options); + }; + + const legacySearch = { + esClient: this.esClient!, + AggConfig, + AggType, + aggTypeFieldFilters, + FieldParamType, + MetricAggType, + parentPipelineAggHelper, + siblingPipelineAggHelper, + }; + + const searchSourceDependencies: SearchSourceDependencies = { + uiSettings: core.uiSettings, + injectedMetadata: core.injectedMetadata, + search, + legacySearch, + }; + return { aggs: { calculateAutoTimeExpression: getCalculateAutoTimeExpression(core.uiSettings), createAggConfigs: (indexPattern, configStates = [], schemas) => { return new AggConfigs(indexPattern, configStates, { + fieldFormats: dependencies.fieldFormats, typesRegistry: aggTypesStart, - fieldFormats, }); }, types: aggTypesStart, }, - search: (request, options, strategyName) => { - const strategyProvider = this.getSearchStrategy(strategyName || DEFAULT_SEARCH_STRATEGY); - const { search } = strategyProvider({ - core, - getSearchStrategy: this.getSearchStrategy, - }); - return this.searchInterceptor.search(search as any, request, options); + search, + searchSource: { + create: (fields?: SearchSourceFields) => new SearchSource(fields, searchSourceDependencies), + fromJSON: createSearchSourceFromJSON(dependencies.indexPatterns, searchSourceDependencies), }, setInterceptor: (searchInterceptor: SearchInterceptor) => { // TODO: should an intercepror have a destroy method? this.searchInterceptor = searchInterceptor; }, - createSearchSource: createSearchSource(indexPatterns), - __LEGACY: { - esClient: this.esClient!, - AggConfig, - AggType, - aggTypeFieldFilters, - FieldParamType, - MetricAggType, - parentPipelineAggHelper, - siblingPipelineAggHelper, - }, + __LEGACY: legacySearch, }; } diff --git a/src/plugins/data/public/search/search_source/create_search_source.test.ts b/src/plugins/data/public/search/search_source/create_search_source.test.ts index d49ce5a0d11f8..efa63b0722e28 100644 --- a/src/plugins/data/public/search/search_source/create_search_source.test.ts +++ b/src/plugins/data/public/search/search_source/create_search_source.test.ts @@ -16,30 +16,43 @@ * specific language governing permissions and limitations * under the License. */ -import { createSearchSource as createSearchSourceFactory } from './create_search_source'; +import { createSearchSourceFromJSON } from './create_search_source'; import { IIndexPattern } from '../../../common/index_patterns'; import { IndexPatternsContract } from '../../index_patterns/index_patterns'; import { Filter } from '../../../common/es_query/filters'; +import { coreMock } from '../../../../../core/public/mocks'; +import { dataPluginMock } from '../../mocks'; -describe('createSearchSource', function() { - let createSearchSource: ReturnType; +describe('createSearchSource', () => { const indexPatternMock: IIndexPattern = {} as IIndexPattern; let indexPatternContractMock: jest.Mocked; + let dependencies: any; + let createSearchSource: ReturnType; beforeEach(() => { + const core = coreMock.createStart(); + const data = dataPluginMock.createStartContract(); + + dependencies = { + searchService: data.search, + uiSettings: core.uiSettings, + injectedMetadata: core.injectedMetadata, + }; + indexPatternContractMock = ({ get: jest.fn().mockReturnValue(Promise.resolve(indexPatternMock)), } as unknown) as jest.Mocked; - createSearchSource = createSearchSourceFactory(indexPatternContractMock); + + createSearchSource = createSearchSourceFromJSON(indexPatternContractMock, dependencies); }); - it('should fail if JSON is invalid', () => { + test('should fail if JSON is invalid', () => { expect(createSearchSource('{', [])).rejects.toThrow(); expect(createSearchSource('0', [])).rejects.toThrow(); expect(createSearchSource('"abcdefg"', [])).rejects.toThrow(); }); - it('should set fields', async () => { + test('should set fields', async () => { const searchSource = await createSearchSource( JSON.stringify({ highlightAll: true, @@ -50,6 +63,7 @@ describe('createSearchSource', function() { }), [] ); + expect(searchSource.getOwnField('highlightAll')).toBe(true); expect(searchSource.getOwnField('query')).toEqual({ query: '', @@ -57,7 +71,7 @@ describe('createSearchSource', function() { }); }); - it('should resolve referenced index pattern', async () => { + test('should resolve referenced index pattern', async () => { const searchSource = await createSearchSource( JSON.stringify({ indexRefName: 'kibanaSavedObjectMeta.searchSourceJSON.index', @@ -70,11 +84,12 @@ describe('createSearchSource', function() { }, ] ); + expect(indexPatternContractMock.get).toHaveBeenCalledWith('123-456'); expect(searchSource.getOwnField('index')).toBe(indexPatternMock); }); - it('should set filters and resolve referenced index patterns', async () => { + test('should set filters and resolve referenced index patterns', async () => { const searchSource = await createSearchSource( JSON.stringify({ filter: [ @@ -110,6 +125,7 @@ describe('createSearchSource', function() { ] ); const filters = searchSource.getOwnField('filter') as Filter[]; + expect(filters[0]).toMatchInlineSnapshot(` Object { "$state": Object { @@ -135,7 +151,7 @@ describe('createSearchSource', function() { `); }); - it('should migrate legacy queries on the fly', async () => { + test('should migrate legacy queries on the fly', async () => { const searchSource = await createSearchSource( JSON.stringify({ highlightAll: true, @@ -143,6 +159,7 @@ describe('createSearchSource', function() { }), [] ); + expect(searchSource.getOwnField('query')).toEqual({ query: 'a:b', language: 'lucene', diff --git a/src/plugins/data/public/search/search_source/create_search_source.ts b/src/plugins/data/public/search/search_source/create_search_source.ts index 35b7ac4eb9762..cc98f433b3a03 100644 --- a/src/plugins/data/public/search/search_source/create_search_source.ts +++ b/src/plugins/data/public/search/search_source/create_search_source.ts @@ -16,11 +16,11 @@ * specific language governing permissions and limitations * under the License. */ -import _ from 'lodash'; +import { transform, defaults, isFunction } from 'lodash'; import { SavedObjectReference } from 'kibana/public'; import { migrateLegacyQuery } from '../../../../kibana_legacy/public'; import { InvalidJSONProperty } from '../../../../kibana_utils/public'; -import { SearchSource } from './search_source'; +import { SearchSourceDependencies, SearchSource, ISearchSource } from './search_source'; import { IndexPatternsContract } from '../../index_patterns/index_patterns'; import { SearchSourceFields } from './types'; @@ -38,12 +38,16 @@ import { SearchSourceFields } from './types'; * returned by `serializeSearchSource` and `references`, a list of references including the ones * returned by `serializeSearchSource`. * + * * @public */ -export const createSearchSource = (indexPatterns: IndexPatternsContract) => async ( +export const createSearchSourceFromJSON = ( + indexPatterns: IndexPatternsContract, + searchSourceDependencies: SearchSourceDependencies +) => async ( searchSourceJson: string, references: SavedObjectReference[] -) => { - const searchSource = new SearchSource(); +): Promise => { + const searchSource = new SearchSource({}, searchSourceDependencies); // if we have a searchSource, set its values based on the searchSourceJson field let searchSourceValues: Record; @@ -90,17 +94,17 @@ export const createSearchSource = (indexPatterns: IndexPatternsContract) => asyn } const searchSourceFields = searchSource.getFields(); - const fnProps = _.transform( + const fnProps = transform( searchSourceFields, function(dynamic, val, name) { - if (_.isFunction(val) && name) dynamic[name] = val; + if (isFunction(val) && name) dynamic[name] = val; }, {} ); // This assignment might hide problems because the type of values passed from the parsed JSON // might not fit the SearchSourceFields interface. - const newFields: SearchSourceFields = _.defaults(searchSourceValues, fnProps); + const newFields: SearchSourceFields = defaults(searchSourceValues, fnProps); searchSource.setFields(newFields); const query = searchSource.getOwnField('query'); diff --git a/src/plugins/data/public/search/search_source/index.ts b/src/plugins/data/public/search/search_source/index.ts index 0e9f530d0968a..9c4106b2dc616 100644 --- a/src/plugins/data/public/search/search_source/index.ts +++ b/src/plugins/data/public/search/search_source/index.ts @@ -17,6 +17,6 @@ * under the License. */ -export * from './search_source'; -export { createSearchSource } from './create_search_source'; +export { SearchSource, ISearchSource, SearchSourceDependencies } from './search_source'; +export { createSearchSourceFromJSON } from './create_search_source'; export { SortDirection, EsQuerySortValue, SearchSourceFields } from './types'; diff --git a/src/plugins/data/public/search/search_source/mocks.ts b/src/plugins/data/public/search/search_source/mocks.ts index 1ef7c1187a9e0..157331ea87bb0 100644 --- a/src/plugins/data/public/search/search_source/mocks.ts +++ b/src/plugins/data/public/search/search_source/mocks.ts @@ -17,9 +17,15 @@ * under the License. */ -import { ISearchSource } from './search_source'; +import { + injectedMetadataServiceMock, + uiSettingsServiceMock, +} from '../../../../../core/public/mocks'; -export const searchSourceMock: MockedKeys = { +import { ISearchSource, SearchSource } from './search_source'; +import { SearchSourceFields } from './types'; + +export const searchSourceInstanceMock: MockedKeys = { setPreferredSearchStrategyId: jest.fn(), setFields: jest.fn().mockReturnThis(), setField: jest.fn().mockReturnThis(), @@ -39,3 +45,21 @@ export const searchSourceMock: MockedKeys = { history: [], serialize: jest.fn(), }; + +export const searchSourceMock = { + create: jest.fn().mockReturnValue(searchSourceInstanceMock), + fromJSON: jest.fn().mockReturnValue(searchSourceInstanceMock), +}; + +export const createSearchSourceMock = (fields?: SearchSourceFields) => + new SearchSource(fields, { + search: jest.fn(), + legacySearch: { + esClient: { + search: jest.fn(), + msearch: jest.fn(), + }, + }, + uiSettings: uiSettingsServiceMock.createStartContract(), + injectedMetadata: injectedMetadataServiceMock.createStartContract(), + }); diff --git a/src/plugins/data/public/search/search_source/search_source.test.ts b/src/plugins/data/public/search/search_source/search_source.test.ts index 6e878844664ad..7783e65889a12 100644 --- a/src/plugins/data/public/search/search_source/search_source.test.ts +++ b/src/plugins/data/public/search/search_source/search_source.test.ts @@ -16,28 +16,13 @@ * specific language governing permissions and limitations * under the License. */ - +import { Observable } from 'rxjs'; import { SearchSource } from './search_source'; import { IndexPattern, SortDirection } from '../..'; -import { mockDataServices } from '../aggs/test_helpers'; -import { setSearchService } from '../../services'; -import { searchStartMock } from '../mocks'; import { fetchSoon } from '../legacy'; -import { CoreStart } from 'kibana/public'; -import { Observable } from 'rxjs'; - -// Setup search service mock -searchStartMock.search = jest.fn(() => { - return new Observable(subscriber => { - setTimeout(() => { - subscriber.next({ - rawResponse: '', - }); - subscriber.complete(); - }, 100); - }); -}) as any; -setSearchService(searchStartMock); +import { IUiSettingsClient } from '../../../../../core/public'; +import { dataPluginMock } from '../../../../data/public/mocks'; +import { coreMock } from '../../../../../core/public/mocks'; jest.mock('../legacy', () => ({ fetchSoon: jest.fn().mockResolvedValue({}), @@ -48,48 +33,70 @@ const getComputedFields = () => ({ scriptFields: [], docvalueFields: [], }); + const mockSource = { excludes: ['foo-*'] }; const mockSource2 = { excludes: ['bar-*'] }; + const indexPattern = ({ title: 'foo', getComputedFields, getSourceFiltering: () => mockSource, } as unknown) as IndexPattern; + const indexPattern2 = ({ title: 'foo', getComputedFields, getSourceFiltering: () => mockSource2, } as unknown) as IndexPattern; -describe('SearchSource', function() { - let uiSettingsMock: jest.Mocked; +describe('SearchSource', () => { + let mockSearchMethod: any; + let searchSourceDependencies: any; + beforeEach(() => { - const { core } = mockDataServices(); - uiSettingsMock = core.uiSettings; - jest.clearAllMocks(); + const core = coreMock.createStart(); + const data = dataPluginMock.createStartContract(); + + mockSearchMethod = jest.fn(() => { + return new Observable(subscriber => { + setTimeout(() => { + subscriber.next({ + rawResponse: '', + }); + subscriber.complete(); + }, 100); + }); + }); + + searchSourceDependencies = { + search: mockSearchMethod, + legacySearch: data.search.__LEGACY, + injectedMetadata: core.injectedMetadata, + uiSettings: core.uiSettings, + }; }); - describe('#setField()', function() { - it('sets the value for the property', function() { - const searchSource = new SearchSource(); + describe('#setField()', () => { + test('sets the value for the property', () => { + const searchSource = new SearchSource({}, searchSourceDependencies); searchSource.setField('aggs', 5); expect(searchSource.getField('aggs')).toBe(5); }); }); - describe('#getField()', function() { - it('gets the value for the property', function() { - const searchSource = new SearchSource(); + describe('#getField()', () => { + test('gets the value for the property', () => { + const searchSource = new SearchSource({}, searchSourceDependencies); searchSource.setField('aggs', 5); expect(searchSource.getField('aggs')).toBe(5); }); }); - describe(`#setField('index')`, function() { - describe('auto-sourceFiltering', function() { - describe('new index pattern assigned', function() { - it('generates a searchSource filter', async function() { - const searchSource = new SearchSource(); + describe(`#setField('index')`, () => { + describe('auto-sourceFiltering', () => { + describe('new index pattern assigned', () => { + test('generates a searchSource filter', async () => { + const searchSource = new SearchSource({}, searchSourceDependencies); expect(searchSource.getField('index')).toBe(undefined); expect(searchSource.getField('source')).toBe(undefined); searchSource.setField('index', indexPattern); @@ -98,8 +105,8 @@ describe('SearchSource', function() { expect(request._source).toBe(mockSource); }); - it('removes created searchSource filter on removal', async function() { - const searchSource = new SearchSource(); + test('removes created searchSource filter on removal', async () => { + const searchSource = new SearchSource({}, searchSourceDependencies); searchSource.setField('index', indexPattern); searchSource.setField('index', undefined); const request = await searchSource.getSearchRequestBody(); @@ -107,9 +114,9 @@ describe('SearchSource', function() { }); }); - describe('new index pattern assigned over another', function() { - it('replaces searchSource filter with new', async function() { - const searchSource = new SearchSource(); + describe('new index pattern assigned over another', () => { + test('replaces searchSource filter with new', async () => { + const searchSource = new SearchSource({}, searchSourceDependencies); searchSource.setField('index', indexPattern); searchSource.setField('index', indexPattern2); expect(searchSource.getField('index')).toBe(indexPattern2); @@ -117,8 +124,8 @@ describe('SearchSource', function() { expect(request._source).toBe(mockSource2); }); - it('removes created searchSource filter on removal', async function() { - const searchSource = new SearchSource(); + test('removes created searchSource filter on removal', async () => { + const searchSource = new SearchSource({}, searchSourceDependencies); searchSource.setField('index', indexPattern); searchSource.setField('index', indexPattern2); searchSource.setField('index', undefined); @@ -130,8 +137,8 @@ describe('SearchSource', function() { }); describe('#onRequestStart()', () => { - it('should be called when starting a request', async () => { - const searchSource = new SearchSource({ index: indexPattern }); + test('should be called when starting a request', async () => { + const searchSource = new SearchSource({ index: indexPattern }, searchSourceDependencies); const fn = jest.fn(); searchSource.onRequestStart(fn); const options = {}; @@ -139,9 +146,9 @@ describe('SearchSource', function() { expect(fn).toBeCalledWith(searchSource, options); }); - it('should not be called on parent searchSource', async () => { - const parent = new SearchSource(); - const searchSource = new SearchSource({ index: indexPattern }); + test('should not be called on parent searchSource', async () => { + const parent = new SearchSource({}, searchSourceDependencies); + const searchSource = new SearchSource({ index: indexPattern }, searchSourceDependencies); const fn = jest.fn(); searchSource.onRequestStart(fn); @@ -154,9 +161,12 @@ describe('SearchSource', function() { expect(parentFn).not.toBeCalled(); }); - it('should be called on parent searchSource if callParentStartHandlers is true', async () => { - const parent = new SearchSource(); - const searchSource = new SearchSource({ index: indexPattern }).setParent(parent, { + test('should be called on parent searchSource if callParentStartHandlers is true', async () => { + const parent = new SearchSource({}, searchSourceDependencies); + const searchSource = new SearchSource( + { index: indexPattern }, + searchSourceDependencies + ).setParent(parent, { callParentStartHandlers: true, }); @@ -174,19 +184,21 @@ describe('SearchSource', function() { describe('#legacy fetch()', () => { beforeEach(() => { - uiSettingsMock.get.mockImplementation(() => { - return true; // batchSearches = true - }); - }); + const core = coreMock.createStart(); - afterEach(() => { - uiSettingsMock.get.mockImplementation(() => { - return false; // batchSearches = false - }); + searchSourceDependencies = { + ...searchSourceDependencies, + uiSettings: { + ...core.uiSettings, + get: jest.fn(() => { + return true; // batchSearches = true + }), + } as IUiSettingsClient, + }; }); - it('should call msearch', async () => { - const searchSource = new SearchSource({ index: indexPattern }); + test('should call msearch', async () => { + const searchSource = new SearchSource({ index: indexPattern }, searchSourceDependencies); const options = {}; await searchSource.fetch(options); expect(fetchSoon).toBeCalledTimes(1); @@ -194,18 +206,19 @@ describe('SearchSource', function() { }); describe('#search service fetch()', () => { - it('should call msearch', async () => { - const searchSource = new SearchSource({ index: indexPattern }); + test('should call msearch', async () => { + const searchSource = new SearchSource({ index: indexPattern }, searchSourceDependencies); const options = {}; + await searchSource.fetch(options); - expect(searchStartMock.search).toBeCalledTimes(1); + expect(mockSearchMethod).toBeCalledTimes(1); }); }); - describe('#serialize', function() { - it('should reference index patterns', () => { + describe('#serialize', () => { + test('should reference index patterns', () => { const indexPattern123 = { id: '123' } as IndexPattern; - const searchSource = new SearchSource(); + const searchSource = new SearchSource({}, searchSourceDependencies); searchSource.setField('index', indexPattern123); const { searchSourceJSON, references } = searchSource.serialize(); expect(references[0].id).toEqual('123'); @@ -213,8 +226,8 @@ describe('SearchSource', function() { expect(JSON.parse(searchSourceJSON).indexRefName).toEqual(references[0].name); }); - it('should add other fields', () => { - const searchSource = new SearchSource(); + test('should add other fields', () => { + const searchSource = new SearchSource({}, searchSourceDependencies); searchSource.setField('highlightAll', true); searchSource.setField('from', 123456); const { searchSourceJSON } = searchSource.serialize(); @@ -222,8 +235,8 @@ describe('SearchSource', function() { expect(JSON.parse(searchSourceJSON).from).toEqual(123456); }); - it('should omit sort and size', () => { - const searchSource = new SearchSource(); + test('should omit sort and size', () => { + const searchSource = new SearchSource({}, searchSourceDependencies); searchSource.setField('highlightAll', true); searchSource.setField('from', 123456); searchSource.setField('sort', { field: SortDirection.asc }); @@ -232,8 +245,8 @@ describe('SearchSource', function() { expect(Object.keys(JSON.parse(searchSourceJSON))).toEqual(['highlightAll', 'from']); }); - it('should serialize filters', () => { - const searchSource = new SearchSource(); + test('should serialize filters', () => { + const searchSource = new SearchSource({}, searchSourceDependencies); const filter = [ { query: 'query', @@ -249,8 +262,8 @@ describe('SearchSource', function() { expect(JSON.parse(searchSourceJSON).filter).toEqual(filter); }); - it('should reference index patterns in filters separately from index field', () => { - const searchSource = new SearchSource(); + test('should reference index patterns in filters separately from index field', () => { + const searchSource = new SearchSource({}, searchSourceDependencies); const indexPattern123 = { id: '123' } as IndexPattern; searchSource.setField('index', indexPattern123); const filter = [ diff --git a/src/plugins/data/public/search/search_source/search_source.ts b/src/plugins/data/public/search/search_source/search_source.ts index 9d2bb889953cf..091a27a6f418d 100644 --- a/src/plugins/data/public/search/search_source/search_source.ts +++ b/src/plugins/data/public/search/search_source/search_source.ts @@ -69,34 +69,45 @@ * `appSearchSource`. */ -import _ from 'lodash'; +import { uniqueId, uniq, extend, pick, difference, set, omit, keys, isFunction } from 'lodash'; import { map } from 'rxjs/operators'; -import { SavedObjectReference } from 'kibana/public'; +import { CoreStart, SavedObjectReference } from 'kibana/public'; import { normalizeSortRequest } from './normalize_sort_request'; import { filterDocvalueFields } from './filter_docvalue_fields'; import { fieldWildcardFilter } from '../../../../kibana_utils/public'; -import { IIndexPattern, SearchRequest } from '../..'; +import { IIndexPattern, ISearchGeneric, SearchRequest } from '../..'; import { SearchSourceOptions, SearchSourceFields } from './types'; import { FetchOptions, RequestFailure, getSearchParams, handleResponse } from '../fetch'; -import { getSearchService, getUiSettings, getInjectedMetadata } from '../../services'; import { getEsQueryConfig, buildEsQuery, Filter } from '../../../common'; import { getHighlightRequest } from '../../../common/field_formats'; import { fetchSoon } from '../legacy'; +import { ISearchStartLegacy } from '../types'; -export type ISearchSource = Pick; +export interface SearchSourceDependencies { + uiSettings: CoreStart['uiSettings']; + search: ISearchGeneric; + legacySearch: ISearchStartLegacy; + injectedMetadata: CoreStart['injectedMetadata']; +} +/** @public **/ export class SearchSource { - private id: string = _.uniqueId('data_source'); + private id: string = uniqueId('data_source'); private searchStrategyId?: string; private parent?: SearchSource; private requestStartHandlers: Array< - (searchSource: ISearchSource, options?: FetchOptions) => Promise + (searchSource: SearchSource, options?: FetchOptions) => Promise > = []; private inheritOptions: SearchSourceOptions = {}; public history: SearchRequest[] = []; + private fields: SearchSourceFields; + private readonly dependencies: SearchSourceDependencies; - constructor(private fields: SearchSourceFields = {}) {} + constructor(fields: SearchSourceFields = {}, dependencies: SearchSourceDependencies) { + this.fields = fields; + this.dependencies = dependencies; + } /** *** * PUBLIC API @@ -147,11 +158,11 @@ export class SearchSource { } create() { - return new SearchSource(); + return new SearchSource({}, this.dependencies); } createCopy() { - const newSearchSource = new SearchSource(); + const newSearchSource = new SearchSource({}, this.dependencies); newSearchSource.setFields({ ...this.fields }); // when serializing the internal fields we lose the internal classes used in the index // pattern, so we have to set it again to workaround this behavior @@ -161,7 +172,7 @@ export class SearchSource { } createChild(options = {}) { - const childSearchSource = new SearchSource(); + const childSearchSource = new SearchSource({}, this.dependencies); childSearchSource.setParent(this, options); return childSearchSource; } @@ -191,16 +202,17 @@ export class SearchSource { * @return {Observable>} */ private fetch$(searchRequest: SearchRequest, signal?: AbortSignal) { - const esShardTimeout = getInjectedMetadata().getInjectedVar('esShardTimeout') as number; - const searchParams = getSearchParams(getUiSettings(), esShardTimeout); + const { search, injectedMetadata, uiSettings } = this.dependencies; + const esShardTimeout = injectedMetadata.getInjectedVar('esShardTimeout') as number; + const searchParams = getSearchParams(uiSettings, esShardTimeout); const params = { index: searchRequest.index.title || searchRequest.index, body: searchRequest.body, ...searchParams, }; - return getSearchService() - .search({ params, indexType: searchRequest.indexType }, { signal }) - .pipe(map(({ rawResponse }) => handleResponse(searchRequest, rawResponse))); + return search({ params, indexType: searchRequest.indexType }, { signal }).pipe( + map(({ rawResponse }) => handleResponse(searchRequest, rawResponse)) + ); } /** @@ -208,7 +220,9 @@ export class SearchSource { * @return {Promise>} */ private async legacyFetch(searchRequest: SearchRequest, options: FetchOptions) { - const esShardTimeout = getInjectedMetadata().getInjectedVar('esShardTimeout') as number; + const { injectedMetadata, legacySearch, uiSettings } = this.dependencies; + const esShardTimeout = injectedMetadata.getInjectedVar('esShardTimeout') as number; + return await fetchSoon( searchRequest, { @@ -216,8 +230,8 @@ export class SearchSource { ...options, }, { - searchService: getSearchService(), - config: getUiSettings(), + legacySearchService: legacySearch, + config: uiSettings, esShardTimeout, } ); @@ -228,13 +242,14 @@ export class SearchSource { * @async */ async fetch(options: FetchOptions = {}) { + const { uiSettings } = this.dependencies; await this.requestIsStarting(options); const searchRequest = await this.flatten(); this.history = [searchRequest]; let response; - if (getUiSettings().get('courier:batchSearches')) { + if (uiSettings.get('courier:batchSearches')) { response = await this.legacyFetch(searchRequest, options); } else { response = this.fetch$(searchRequest, options.abortSignal).toPromise(); @@ -253,7 +268,7 @@ export class SearchSource { * @return {undefined} */ onRequestStart( - handler: (searchSource: ISearchSource, options?: FetchOptions) => Promise + handler: (searchSource: SearchSource, options?: FetchOptions) => Promise ) { this.requestStartHandlers.push(handler); } @@ -326,13 +341,15 @@ export class SearchSource { } }; + const { uiSettings } = this.dependencies; + switch (key) { case 'filter': return addToRoot('filters', (data.filters || []).concat(val)); case 'query': return addToRoot(key, (data[key] || []).concat(val)); case 'fields': - const fields = _.uniq((data[key] || []).concat(val)); + const fields = uniq((data[key] || []).concat(val)); return addToRoot(key, fields); case 'index': case 'type': @@ -346,7 +363,7 @@ export class SearchSource { const sort = normalizeSortRequest( val, this.getField('index'), - getUiSettings().get('sort:options') + uiSettings.get('sort:options') ); return addToBody(key, sort); default: @@ -389,7 +406,7 @@ export class SearchSource { body.stored_fields = computedFields.storedFields; body.script_fields = body.script_fields || {}; - _.extend(body.script_fields, computedFields.scriptFields); + extend(body.script_fields, computedFields.scriptFields); const defaultDocValueFields = computedFields.docvalueFields ? computedFields.docvalueFields @@ -400,9 +417,11 @@ export class SearchSource { body._source = index.getSourceFiltering(); } + const { uiSettings } = this.dependencies; + if (body._source) { // exclude source fields for this index pattern specified by the user - const filter = fieldWildcardFilter(body._source.excludes, getUiSettings().get('metaFields')); + const filter = fieldWildcardFilter(body._source.excludes, uiSettings.get('metaFields')); body.docvalue_fields = body.docvalue_fields.filter((docvalueField: any) => filter(docvalueField.field) ); @@ -412,42 +431,22 @@ export class SearchSource { if (fields) { // filter out the docvalue_fields, and script_fields to only include those that we are concerned with body.docvalue_fields = filterDocvalueFields(body.docvalue_fields, fields); - body.script_fields = _.pick(body.script_fields, fields); + body.script_fields = pick(body.script_fields, fields); // request the remaining fields from both stored_fields and _source - const remainingFields = _.difference(fields, _.keys(body.script_fields)); + const remainingFields = difference(fields, keys(body.script_fields)); body.stored_fields = remainingFields; - _.set(body, '_source.includes', remainingFields); + set(body, '_source.includes', remainingFields); } - const esQueryConfigs = getEsQueryConfig(getUiSettings()); + const esQueryConfigs = getEsQueryConfig(uiSettings); body.query = buildEsQuery(index, query, filters, esQueryConfigs); if (highlightAll && body.query) { - body.highlight = getHighlightRequest(body.query, getUiSettings().get('doc_table:highlight')); + body.highlight = getHighlightRequest(body.query, uiSettings.get('doc_table:highlight')); delete searchRequest.highlightAll; } - const translateToQuery = (filter: Filter) => filter && (filter.query || filter); - - // re-write filters within filter aggregations - (function recurse(aggBranch) { - if (!aggBranch) return; - Object.keys(aggBranch).forEach(function(id) { - const agg = aggBranch[id]; - - if (agg.filters) { - // translate filters aggregations - const { filters: aggFilters } = agg.filters; - Object.keys(aggFilters).forEach(filterId => { - aggFilters[filterId] = translateToQuery(aggFilters[filterId]); - }); - } - - recurse(agg.aggs || agg.aggregations); - }); - })(body.aggs || body.aggregations); - return searchRequest; } @@ -467,7 +466,7 @@ export class SearchSource { const { filter: originalFilters, ...searchSourceFields - }: Omit = _.omit(this.getFields(), ['sort', 'size']); + }: Omit = omit(this.getFields(), ['sort', 'size']); let serializedSearchSourceFields: Omit & { indexRefName?: string; filter?: Array & { meta: Filter['meta'] & { indexRefName?: string } }>; @@ -524,10 +523,13 @@ export class SearchSource { return filterField; } - if (_.isFunction(filterField)) { + if (isFunction(filterField)) { return this.getFilters(filterField()); } return [filterField]; } } + +/** @public **/ +export type ISearchSource = Pick; diff --git a/src/plugins/data/public/search/types.ts b/src/plugins/data/public/search/types.ts index 2122e4e82ec1d..99d111ce1574e 100644 --- a/src/plugins/data/public/search/types.ts +++ b/src/plugins/data/public/search/types.ts @@ -17,13 +17,13 @@ * under the License. */ -import { CoreStart } from 'kibana/public'; -import { createSearchSource } from './search_source'; +import { CoreStart, SavedObjectReference } from 'kibana/public'; import { SearchAggsSetup, SearchAggsStart, SearchAggsStartLegacy } from './aggs'; import { ISearch, ISearchGeneric } from './i_search'; import { TStrategyTypes } from './strategy_types'; import { LegacyApiCaller } from './legacy/es_client'; import { SearchInterceptor } from './search_interceptor'; +import { ISearchSource, SearchSourceFields } from './search_source'; export interface ISearchContext { core: CoreStart; @@ -60,7 +60,7 @@ export type TRegisterSearchStrategyProvider = ( searchStrategyProvider: TSearchStrategyProvider ) => void; -interface ISearchStartLegacy { +export interface ISearchStartLegacy { esClient: LegacyApiCaller; } @@ -81,6 +81,12 @@ export interface ISearchStart { aggs: SearchAggsStart; setInterceptor: (searchInterceptor: SearchInterceptor) => void; search: ISearchGeneric; - createSearchSource: ReturnType; + searchSource: { + create: (fields?: SearchSourceFields) => ISearchSource; + fromJSON: ( + searchSourceJson: string, + references: SavedObjectReference[] + ) => Promise; + }; __LEGACY: ISearchStartLegacy & SearchAggsStartLegacy; } diff --git a/src/plugins/data/public/services.ts b/src/plugins/data/public/services.ts index 199ba17b3b81b..ba0b2de393bde 100644 --- a/src/plugins/data/public/services.ts +++ b/src/plugins/data/public/services.ts @@ -17,7 +17,7 @@ * under the License. */ -import { NotificationsStart, CoreSetup, CoreStart } from 'src/core/public'; +import { NotificationsStart, CoreStart } from 'src/core/public'; import { FieldFormatsStart } from './field_formats'; import { createGetterSetter } from '../../kibana_utils/public'; import { IndexPatternsContract } from './index_patterns'; @@ -48,7 +48,7 @@ export const [getQueryService, setQueryService] = createGetterSetter< >('Query'); export const [getInjectedMetadata, setInjectedMetadata] = createGetterSetter< - CoreSetup['injectedMetadata'] + CoreStart['injectedMetadata'] >('InjectedMetadata'); export const [getSearchService, setSearchService] = createGetterSetter< diff --git a/src/plugins/data/public/types.ts b/src/plugins/data/public/types.ts index e24e01d241278..aaef403979de6 100644 --- a/src/plugins/data/public/types.ts +++ b/src/plugins/data/public/types.ts @@ -24,7 +24,7 @@ import { ExpressionsSetup } from 'src/plugins/expressions/public'; import { UiActionsSetup, UiActionsStart } from 'src/plugins/ui_actions/public'; import { AutocompleteSetup, AutocompleteStart } from './autocomplete'; import { FieldFormatsSetup, FieldFormatsStart } from './field_formats'; -import { createFiltersFromEvent } from './actions'; +import { createFiltersFromRangeSelectAction, createFiltersFromValueClickAction } from './actions'; import { ISearchSetup, ISearchStart } from './search'; import { QuerySetup, QueryStart } from './query'; import { IndexPatternSelectProps } from './ui/index_pattern_select'; @@ -49,7 +49,8 @@ export interface DataPublicPluginSetup { export interface DataPublicPluginStart { actions: { - createFiltersFromEvent: typeof createFiltersFromEvent; + createFiltersFromValueClickAction: typeof createFiltersFromValueClickAction; + createFiltersFromRangeSelectAction: typeof createFiltersFromRangeSelectAction; }; autocomplete: AutocompleteStart; indexPatterns: IndexPatternsContract; @@ -74,8 +75,11 @@ export interface IDataPluginServices extends Partial { /** @internal **/ export interface InternalStartServices { - fieldFormats: FieldFormatsStart; - notifications: CoreStart['notifications']; + readonly fieldFormats: FieldFormatsStart; + readonly notifications: CoreStart['notifications']; + readonly uiSettings: CoreStart['uiSettings']; + readonly searchService: DataPublicPluginStart['search']; + readonly injectedMetadata: CoreStart['injectedMetadata']; } /** @internal **/ diff --git a/src/plugins/data/public/ui/index_pattern_select/index_pattern_select.tsx b/src/plugins/data/public/ui/index_pattern_select/index_pattern_select.tsx index c56060bb9c288..6eb4f82a940b1 100644 --- a/src/plugins/data/public/ui/index_pattern_select/index_pattern_select.tsx +++ b/src/plugins/data/public/ui/index_pattern_select/index_pattern_select.tsx @@ -27,6 +27,7 @@ import { SavedObjectsClientContract, SimpleSavedObject } from '../../../../../co import { getTitle } from '../../index_patterns/lib'; export type IndexPatternSelectProps = Required< + // Omit, 'isLoading' | 'onSearchChange' | 'options' | 'selectedOptions' | 'append' | 'prepend' | 'sortMatchesBy'>, Omit, 'isLoading' | 'onSearchChange' | 'options' | 'selectedOptions'>, 'onChange' | 'placeholder' > & { diff --git a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/should_read_field_from_doc_values.test.ts b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/should_read_field_from_doc_values.test.ts new file mode 100644 index 0000000000000..784b0b4d4f3d7 --- /dev/null +++ b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/should_read_field_from_doc_values.test.ts @@ -0,0 +1,42 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { shouldReadFieldFromDocValues } from './should_read_field_from_doc_values'; + +describe('shouldReadFieldFromDocValues', () => { + test('should read field from doc values for aggregatable "number" field', async () => { + expect(shouldReadFieldFromDocValues(true, 'number')).toBe(true); + }); + + test('should not read field from doc values for non-aggregatable "number "field', async () => { + expect(shouldReadFieldFromDocValues(false, 'number')).toBe(false); + }); + + test('should not read field from doc values for "text" field', async () => { + expect(shouldReadFieldFromDocValues(true, 'text')).toBe(false); + }); + + test('should not read field from doc values for "geo_shape" field', async () => { + expect(shouldReadFieldFromDocValues(true, 'geo_shape')).toBe(false); + }); + + test('should not read field from doc values for underscore field', async () => { + expect(shouldReadFieldFromDocValues(true, '_source')).toBe(false); + }); +}); diff --git a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/should_read_field_from_doc_values.ts b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/should_read_field_from_doc_values.ts index 6d58f7a02c134..56a1cf3ccd161 100644 --- a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/should_read_field_from_doc_values.ts +++ b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/should_read_field_from_doc_values.ts @@ -18,5 +18,5 @@ */ export function shouldReadFieldFromDocValues(aggregatable: boolean, esType: string) { - return aggregatable && esType !== 'text' && !esType.startsWith('_'); + return aggregatable && !['text', 'geo_shape'].includes(esType) && !esType.startsWith('_'); } diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index c41023eab6d20..f8a9a7792c492 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -283,7 +283,7 @@ export interface FieldFormatConfig { export const fieldFormats: { FieldFormatsRegistry: typeof FieldFormatsRegistry; FieldFormat: typeof FieldFormat; - serializeFieldFormat: (agg: import("../public/search").AggConfig) => import("../../expressions/common").SerializedFieldFormat; + serializeFieldFormat: (agg: import("../public/search").AggConfig) => import("../../expressions").SerializedFieldFormat; BoolFormat: typeof BoolFormat; BytesFormat: typeof BytesFormat; ColorFormat: typeof ColorFormat; diff --git a/src/plugins/discover/public/components/field_name/__snapshots__/field_name.test.tsx.snap b/src/plugins/discover/public/components/field_name/__snapshots__/field_name.test.tsx.snap index 9f48e6e57e0ff..afb541253d994 100644 --- a/src/plugins/discover/public/components/field_name/__snapshots__/field_name.test.tsx.snap +++ b/src/plugins/discover/public/components/field_name/__snapshots__/field_name.test.tsx.snap @@ -8,12 +8,12 @@ exports[`FieldName renders a geo field, useShortDots is set to true 1`] = ` class="euiFlexItem euiFlexItem--flexGrowZero" >
@@ -39,12 +39,12 @@ exports[`FieldName renders a number field by providing a field record, useShortD class="euiFlexItem euiFlexItem--flexGrowZero" >
@@ -70,12 +70,12 @@ exports[`FieldName renders a string field by providing fieldType and fieldName 1 class="euiFlexItem euiFlexItem--flexGrowZero" >
diff --git a/src/plugins/embeddable/public/index.ts b/src/plugins/embeddable/public/index.ts index bdb7bfbddc308..5ee66f9d19ac0 100644 --- a/src/plugins/embeddable/public/index.ts +++ b/src/plugins/embeddable/public/index.ts @@ -47,7 +47,8 @@ export { EmbeddableOutput, EmbeddablePanel, EmbeddableRoot, - EmbeddableVisTriggerContext, + ValueClickTriggerContext, + RangeSelectTriggerContext, ErrorEmbeddable, IContainer, IEmbeddable, diff --git a/src/plugins/embeddable/public/lib/actions/apply_filter_action.test.ts b/src/plugins/embeddable/public/lib/actions/apply_filter_action.test.ts index 5297cf6cd365c..636ce3e623c5b 100644 --- a/src/plugins/embeddable/public/lib/actions/apply_filter_action.test.ts +++ b/src/plugins/embeddable/public/lib/actions/apply_filter_action.test.ts @@ -31,6 +31,14 @@ test('has expected display name', () => { expect(action.getDisplayName({} as any)).toMatchInlineSnapshot(`"Apply filter to current view"`); }); +describe('getIconType()', () => { + test('returns "filter" icon', async () => { + const action = createFilterAction(); + const result = action.getIconType({} as any); + expect(result).toBe('filter'); + }); +}); + describe('isCompatible()', () => { test('when embeddable filters and filters exist, returns true', async () => { const action = createFilterAction(); diff --git a/src/plugins/embeddable/public/lib/actions/apply_filter_action.ts b/src/plugins/embeddable/public/lib/actions/apply_filter_action.ts index 4680512fb81c8..1cdb5af00e748 100644 --- a/src/plugins/embeddable/public/lib/actions/apply_filter_action.ts +++ b/src/plugins/embeddable/public/lib/actions/apply_filter_action.ts @@ -42,6 +42,7 @@ export function createFilterAction(): ActionByType { return createAction({ type: ACTION_APPLY_FILTER, id: ACTION_APPLY_FILTER, + getIconType: () => 'filter', getDisplayName: () => { return i18n.translate('embeddableApi.actions.applyFilterActionTitle', { defaultMessage: 'Apply filter to current view', diff --git a/src/plugins/embeddable/public/lib/triggers/triggers.ts b/src/plugins/embeddable/public/lib/triggers/triggers.ts index e29302fd6cc13..da7be1eea199a 100644 --- a/src/plugins/embeddable/public/lib/triggers/triggers.ts +++ b/src/plugins/embeddable/public/lib/triggers/triggers.ts @@ -18,18 +18,34 @@ */ import { Trigger } from '../../../../ui_actions/public'; +import { KibanaDatatable } from '../../../../expressions'; import { IEmbeddable } from '..'; export interface EmbeddableContext { embeddable: IEmbeddable; } -export interface EmbeddableVisTriggerContext { +export interface ValueClickTriggerContext { embeddable?: IEmbeddable; timeFieldName?: string; data: { - e?: MouseEvent; - data: unknown; + data: Array<{ + table: Pick; + column: number; + row: number; + value: any; + }>; + negate?: boolean; + }; +} + +export interface RangeSelectTriggerContext { + embeddable?: IEmbeddable; + timeFieldName?: string; + data: { + table: KibanaDatatable; + column: number; + range: number[]; }; } diff --git a/src/plugins/home/server/plugin.ts b/src/plugins/home/server/plugin.ts index d2f2d7041024e..6ea6784cd5adb 100644 --- a/src/plugins/home/server/plugin.ts +++ b/src/plugins/home/server/plugin.ts @@ -26,6 +26,7 @@ import { SampleDataRegistryStart, } from './services'; import { UsageCollectionSetup } from '../../usage_collection/server'; +import { sampleDataTelemetry } from './saved_objects'; interface HomeServerPluginSetupDependencies { usage_collection?: UsageCollectionSetup; @@ -37,6 +38,7 @@ export class HomeServerPlugin implements Plugin { expect(component.find(TOP_NAV_ITEM_SELECTOR).length).toBe(0); expect(component.find(SEARCH_BAR_SELECTOR).length).toBe(1); }); + + it('Should render with a class name', () => { + const component = shallowWithIntl( + + ); + expect(component.find('.kbnTopNavMenu').length).toBe(1); + expect(component.find('.myCoolClass').length).toBeTruthy(); + }); }); diff --git a/src/plugins/navigation/public/top_nav_menu/top_nav_menu.tsx b/src/plugins/navigation/public/top_nav_menu/top_nav_menu.tsx index 14ad40f13e388..d492c7feb61a7 100644 --- a/src/plugins/navigation/public/top_nav_menu/top_nav_menu.tsx +++ b/src/plugins/navigation/public/top_nav_menu/top_nav_menu.tsx @@ -21,6 +21,7 @@ import React from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import classNames from 'classnames'; import { TopNavMenuData } from './top_nav_menu_data'; import { TopNavMenuItem } from './top_nav_menu_item'; import { StatefulSearchBarProps, DataPublicPluginStart } from '../../../data/public'; @@ -29,6 +30,7 @@ export type TopNavMenuProps = StatefulSearchBarProps & { config?: TopNavMenuData[]; showSearchBar?: boolean; data?: DataPublicPluginStart; + className?: string; }; /* @@ -65,6 +67,7 @@ export function TopNavMenu(props: TopNavMenuProps) { } function renderLayout() { + const className = classNames('kbnTopNavMenu', props.className); return ( {renderItems()} diff --git a/src/plugins/saved_objects/public/saved_object/helpers/apply_es_resp.ts b/src/plugins/saved_objects/public/saved_object/helpers/apply_es_resp.ts index 9776887b6d741..df687a051fc7d 100644 --- a/src/plugins/saved_objects/public/saved_object/helpers/apply_es_resp.ts +++ b/src/plugins/saved_objects/public/saved_object/helpers/apply_es_resp.ts @@ -17,9 +17,9 @@ * under the License. */ import _ from 'lodash'; -import { EsResponse, SavedObject, SavedObjectConfig } from '../../types'; +import { EsResponse, SavedObject, SavedObjectConfig, SavedObjectKibanaServices } from '../../types'; import { expandShorthand, SavedObjectNotFound } from '../../../../kibana_utils/public'; -import { DataPublicPluginStart, IndexPattern } from '../../../../data/public'; +import { IndexPattern } from '../../../../data/public'; /** * A given response of and ElasticSearch containing a plain saved object is applied to the given @@ -29,7 +29,7 @@ export async function applyESResp( resp: EsResponse, savedObject: SavedObject, config: SavedObjectConfig, - createSearchSource: DataPublicPluginStart['search']['createSearchSource'] + dependencies: SavedObjectKibanaServices ) { const mapping = expandShorthand(config.mapping); const esType = config.type || ''; @@ -65,7 +65,10 @@ export async function applyESResp( if (config.searchSource) { try { - savedObject.searchSource = await createSearchSource(meta.searchSourceJSON, resp.references); + savedObject.searchSource = await dependencies.search.searchSource.fromJSON( + meta.searchSourceJSON, + resp.references + ); } catch (error) { if ( error.constructor.name === 'SavedObjectNotFound' && diff --git a/src/plugins/saved_objects/public/saved_object/helpers/build_saved_object.ts b/src/plugins/saved_objects/public/saved_object/helpers/build_saved_object.ts index e8faef4e9e040..e5b0e18e7b433 100644 --- a/src/plugins/saved_objects/public/saved_object/helpers/build_saved_object.ts +++ b/src/plugins/saved_objects/public/saved_object/helpers/build_saved_object.ts @@ -16,8 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import _ from 'lodash'; -import { SearchSource } from '../../../../data/public'; +import { once } from 'lodash'; import { hydrateIndexPattern } from './hydrate_index_pattern'; import { intializeSavedObject } from './initialize_saved_object'; import { serializeSavedObject } from './serialize_saved_object'; @@ -55,7 +54,9 @@ export function buildSavedObject( savedObject.isSaving = false; savedObject.defaults = config.defaults || {}; // optional search source which this object configures - savedObject.searchSource = config.searchSource ? new SearchSource() : undefined; + savedObject.searchSource = config.searchSource + ? services.search.searchSource.create() + : undefined; // the id of the document savedObject.id = config.id || void 0; // the migration version of the document, should only be set on imports @@ -79,10 +80,9 @@ export function buildSavedObject( * @return {Promise} * @resolved {SavedObject} */ - savedObject.init = _.once(() => intializeSavedObject(savedObject, savedObjectsClient, config)); + savedObject.init = once(() => intializeSavedObject(savedObject, savedObjectsClient, config)); - savedObject.applyESResp = (resp: EsResponse) => - applyESResp(resp, savedObject, config, services.search.createSearchSource); + savedObject.applyESResp = (resp: EsResponse) => applyESResp(resp, savedObject, config, services); /** * Serialize this object diff --git a/src/plugins/saved_objects/public/saved_object/saved_object.test.ts b/src/plugins/saved_objects/public/saved_object/saved_object.test.ts index 60c66f84080b2..f7e67dbe3ee1d 100644 --- a/src/plugins/saved_objects/public/saved_object/saved_object.test.ts +++ b/src/plugins/saved_objects/public/saved_object/saved_object.test.ts @@ -28,9 +28,8 @@ import { // @ts-ignore import StubIndexPattern from 'test_utils/stub_index_pattern'; -import { InvalidJSONProperty } from '../../../kibana_utils/public'; import { coreMock } from '../../../../core/public/mocks'; -import { dataPluginMock } from '../../../../plugins/data/public/mocks'; +import { dataPluginMock, createSearchSourceMock } from '../../../../plugins/data/public/mocks'; import { SavedObjectAttributes, SimpleSavedObject } from 'kibana/public'; import { IIndexPattern } from '../../../data/common/index_patterns'; @@ -40,9 +39,9 @@ describe('Saved Object', () => { const startMock = coreMock.createStart(); const dataStartMock = dataPluginMock.createStartContract(); const saveOptionsMock = {} as SavedObjectSaveOpts; + const savedObjectsClientStub = startMock.savedObjects.client; let SavedObjectClass: new (config: SavedObjectConfig) => SavedObject; - const savedObjectsClientStub = startMock.savedObjects.client; /** * Returns a fake doc response with the given index and id, of type dashboard @@ -99,16 +98,22 @@ describe('Saved Object', () => { function createInitializedSavedObject(config: SavedObjectConfig = {}) { const savedObject = new SavedObjectClass(config); savedObject.title = 'my saved object'; + return savedObject.init!(); } beforeEach(() => { - (dataStartMock.search.createSearchSource as jest.Mock).mockReset(); - SavedObjectClass = createSavedObjectClass({ + SavedObjectClass = createSavedObjectClass(({ savedObjectsClient: savedObjectsClientStub, indexPatterns: dataStartMock.indexPatterns, - search: dataStartMock.search, - } as SavedObjectKibanaServices); + search: { + ...dataStartMock.search, + searchSource: { + ...dataStartMock.search.searchSource, + create: createSearchSourceMock, + }, + }, + } as unknown) as SavedObjectKibanaServices); }); describe('save', () => { @@ -411,27 +416,6 @@ describe('Saved Object', () => { }); }); - it('forwards thrown exceptions from createSearchSource', async () => { - (dataStartMock.search.createSearchSource as jest.Mock).mockImplementation(() => { - throw new InvalidJSONProperty(''); - }); - const savedObject = await createInitializedSavedObject({ - type: 'dashboard', - searchSource: true, - }); - const response = { - found: true, - _source: {}, - }; - - try { - await savedObject.applyESResp(response); - throw new Error('applyESResp should have failed, but did not.'); - } catch (err) { - expect(err instanceof InvalidJSONProperty).toBe(true); - } - }); - it('preserves original defaults if not overridden', () => { const id = 'anid'; const preserveMeValue = 'here to stay!'; @@ -589,42 +573,45 @@ describe('Saved Object', () => { it('passes references to search source parsing function', async () => { const savedObject = new SavedObjectClass({ type: 'dashboard', searchSource: true }); - return savedObject.init!().then(() => { - const searchSourceJSON = JSON.stringify({ - indexRefName: 'kibanaSavedObjectMeta.searchSourceJSON.index', - filter: [ - { - meta: { - indexRefName: 'kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index', - }, - }, - ], - }); - const response = { - found: true, - _source: { - kibanaSavedObjectMeta: { - searchSourceJSON, + await savedObject.init!(); + + const searchSourceJSON = JSON.stringify({ + indexRefName: 'kibanaSavedObjectMeta.searchSourceJSON.index', + filter: [ + { + meta: { + indexRefName: 'kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index', }, }, - references: [ - { - name: 'kibanaSavedObjectMeta.searchSourceJSON.index', - type: 'index-pattern', - id: 'my-index-1', - }, - { - name: 'kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index', - type: 'index-pattern', - id: 'my-index-2', - }, - ], - }; - savedObject.applyESResp(response); - expect(dataStartMock.search.createSearchSource).toBeCalledWith( - searchSourceJSON, - response.references - ); + ], + }); + const response = { + found: true, + _source: { + kibanaSavedObjectMeta: { + searchSourceJSON, + }, + }, + references: [ + { + name: 'kibanaSavedObjectMeta.searchSourceJSON.index', + type: 'index-pattern', + id: 'my-index-1', + }, + { + name: 'kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index', + type: 'index-pattern', + id: 'my-index-2', + }, + ], + }; + const result = await savedObject.applyESResp(response); + + expect(result._source).toEqual({ + kibanaSavedObjectMeta: { + searchSourceJSON: + '{"indexRefName":"kibanaSavedObjectMeta.searchSourceJSON.index","filter":[{"meta":{"indexRefName":"kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index"}}]}', + }, }); }); }); diff --git a/src/plugins/saved_objects_management/public/lib/resolve_saved_objects.test.ts b/src/plugins/saved_objects_management/public/lib/resolve_saved_objects.test.ts index 23c2b75169555..f98b1762511f4 100644 --- a/src/plugins/saved_objects_management/public/lib/resolve_saved_objects.test.ts +++ b/src/plugins/saved_objects_management/public/lib/resolve_saved_objects.test.ts @@ -25,6 +25,7 @@ import { } from './resolve_saved_objects'; import { SavedObject, SavedObjectLoader } from '../../../saved_objects/public'; import { IndexPatternsContract } from '../../../data/public'; +import { dataPluginMock } from '../../../data/public/mocks'; class SavedObjectNotFound extends Error { constructor(options: Record) { @@ -233,6 +234,19 @@ describe('resolveSavedObjects', () => { }); describe('resolveIndexPatternConflicts', () => { + let dependencies: Parameters[3]; + + beforeEach(() => { + const search = dataPluginMock.createStartContract().search; + + dependencies = { + indexPatterns: ({ + get: (id: string) => Promise.resolve({ id }), + } as unknown) as IndexPatternsContract, + search, + }; + }); + it('should resave resolutions', async () => { const save = jest.fn(); @@ -284,11 +298,13 @@ describe('resolveSavedObjects', () => { const overwriteAll = false; - await resolveIndexPatternConflicts(resolutions, conflictedIndexPatterns, overwriteAll, ({ - get: (id: string) => Promise.resolve({ id }), - } as unknown) as IndexPatternsContract); - expect(conflictedIndexPatterns[0].obj.searchSource!.getField('index')!.id).toEqual('2'); - expect(conflictedIndexPatterns[1].obj.searchSource!.getField('index')!.id).toEqual('4'); + await resolveIndexPatternConflicts( + resolutions, + conflictedIndexPatterns, + overwriteAll, + dependencies + ); + expect(save.mock.calls.length).toBe(2); expect(save).toHaveBeenCalledWith({ confirmOverwrite: !overwriteAll }); }); @@ -345,13 +361,13 @@ describe('resolveSavedObjects', () => { const overwriteAll = false; - await resolveIndexPatternConflicts(resolutions, conflictedIndexPatterns, overwriteAll, ({ - get: (id: string) => Promise.resolve({ id }), - } as unknown) as IndexPatternsContract); + await resolveIndexPatternConflicts( + resolutions, + conflictedIndexPatterns, + overwriteAll, + dependencies + ); - expect(conflictedIndexPatterns[0].obj.searchSource!.getField('filter')).toEqual([ - { meta: { index: 'newFilterIndex' } }, - ]); expect(save.mock.calls.length).toBe(2); }); }); diff --git a/src/plugins/saved_objects_management/public/lib/resolve_saved_objects.ts b/src/plugins/saved_objects_management/public/lib/resolve_saved_objects.ts index 15e03ed39d88c..d4764b8949a60 100644 --- a/src/plugins/saved_objects_management/public/lib/resolve_saved_objects.ts +++ b/src/plugins/saved_objects_management/public/lib/resolve_saved_objects.ts @@ -21,7 +21,7 @@ import { i18n } from '@kbn/i18n'; import { cloneDeep } from 'lodash'; import { OverlayStart, SavedObjectReference } from 'src/core/public'; import { SavedObject, SavedObjectLoader } from '../../../saved_objects/public'; -import { IndexPatternsContract, IIndexPattern, createSearchSource } from '../../../data/public'; +import { IndexPatternsContract, IIndexPattern, DataPublicPluginStart } from '../../../data/public'; type SavedObjectsRawDoc = Record; @@ -162,7 +162,10 @@ export async function resolveIndexPatternConflicts( resolutions: Array<{ oldId: string; newId: string }>, conflictedIndexPatterns: any[], overwriteAll: boolean, - indexPatterns: IndexPatternsContract + dependencies: { + indexPatterns: IndexPatternsContract; + search: DataPublicPluginStart['search']; + } ) { let importCount = 0; @@ -208,7 +211,7 @@ export async function resolveIndexPatternConflicts( // The user decided to skip this conflict so do nothing return; } - obj.searchSource = await createSearchSource(indexPatterns)( + obj.searchSource = await dependencies.search.searchSource.fromJSON( JSON.stringify(serializedSearchSource), replacedReferences ); diff --git a/src/plugins/saved_objects_management/public/management_section/mount_section.tsx b/src/plugins/saved_objects_management/public/management_section/mount_section.tsx index 6f03f97079bb6..fe3150fc0bb07 100644 --- a/src/plugins/saved_objects_management/public/management_section/mount_section.tsx +++ b/src/plugins/saved_objects_management/public/management_section/mount_section.tsx @@ -187,6 +187,7 @@ const SavedObjectsTablePage = ({ actionRegistry={actionRegistry} savedObjectsClient={coreStart.savedObjects.client} indexPatterns={dataStart.indexPatterns} + search={dataStart.search} http={coreStart.http} overlays={coreStart.overlays} notifications={coreStart.notifications} diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap b/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap index fe64df6ff51d1..563ce87b82ae5 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap @@ -256,6 +256,41 @@ exports[`SavedObjectsTable import should show the flyout 1`] = ` "openModal": [MockFunction], } } + search={ + Object { + "__LEGACY": Object { + "AggConfig": [MockFunction], + "AggType": [MockFunction], + "FieldParamType": [MockFunction], + "MetricAggType": [MockFunction], + "aggTypeFieldFilters": AggTypeFieldFilters { + "filters": Set {}, + }, + "esClient": Object { + "msearch": [MockFunction], + "search": [MockFunction], + }, + "parentPipelineAggHelper": [MockFunction], + "siblingPipelineAggHelper": [MockFunction], + }, + "aggs": Object { + "calculateAutoTimeExpression": [Function], + "createAggConfigs": [MockFunction], + "types": Object { + "get": [Function], + "getAll": [Function], + "getBuckets": [Function], + "getMetrics": [Function], + }, + }, + "search": [MockFunction], + "searchSource": Object { + "create": [MockFunction], + "fromJSON": [MockFunction], + }, + "setInterceptor": [MockFunction], + } + } serviceRegistry={ Object { "all": [MockFunction], diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.test.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.test.tsx index 5d713ff044f24..c915a8a2be8f8 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.test.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.test.tsx @@ -33,6 +33,7 @@ import { coreMock } from '../../../../../../core/public/mocks'; import { serviceRegistryMock } from '../../../services/service_registry.mock'; import { Flyout, FlyoutProps, FlyoutState } from './flyout'; import { ShallowWrapper } from 'enzyme'; +import { dataPluginMock } from '../../../../../data/public/mocks'; const mockFile = ({ name: 'foo.ndjson', @@ -56,6 +57,7 @@ describe('Flyout', () => { beforeEach(() => { const { http, overlays } = coreMock.createStart(); + const search = dataPluginMock.createStartContract().search; defaultProps = { close: jest.fn(), @@ -68,6 +70,7 @@ describe('Flyout', () => { http, allowedTypes: ['search', 'index-pattern', 'visualization'], serviceRegistry: serviceRegistryMock.create(), + search, }; }); @@ -499,7 +502,10 @@ describe('Flyout', () => { component.instance().resolutions, mockConflictedIndexPatterns, true, - defaultProps.indexPatterns + { + search: defaultProps.search, + indexPatterns: defaultProps.indexPatterns, + } ); expect(saveObjectsMock).toHaveBeenCalledWith( mockConflictedSavedObjectsLinkedToSavedSearches, diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx index 45788dcb601ae..fbcfeafe291a3 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx @@ -48,7 +48,11 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { OverlayStart, HttpStart } from 'src/core/public'; -import { IndexPatternsContract, IIndexPattern } from '../../../../../data/public'; +import { + IndexPatternsContract, + IIndexPattern, + DataPublicPluginStart, +} from '../../../../../data/public'; import { importFile, importLegacyFile, @@ -75,6 +79,7 @@ export interface FlyoutProps { indexPatterns: IndexPatternsContract; overlays: OverlayStart; http: HttpStart; + search: DataPublicPluginStart['search']; } export interface FlyoutState { @@ -362,7 +367,7 @@ export class Flyout extends Component { failedImports, } = this.state; - const { serviceRegistry, indexPatterns } = this.props; + const { serviceRegistry, indexPatterns, search } = this.props; this.setState({ error: undefined, @@ -388,7 +393,10 @@ export class Flyout extends Component { resolutions, conflictedIndexPatterns!, isOverwriteAllChecked, - indexPatterns + { + indexPatterns, + search, + } ); } this.setState({ diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/relationships.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/components/relationships.tsx index ddb262138d565..d9e39f31d181a 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/components/relationships.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/relationships.tsx @@ -32,7 +32,7 @@ import { EuiText, EuiSpacer, } from '@elastic/eui'; -import { FilterConfig } from '@elastic/eui/src/components/search_bar/filters/filters'; +import { SearchFilterConfig } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { IBasePath } from 'src/core/public'; @@ -284,7 +284,7 @@ export class Relationships extends Component { let overlays: ReturnType; let notifications: ReturnType; let savedObjects: ReturnType; + let search: ReturnType['search']; const shallowRender = (overrides: Partial = {}) => { return (shallowWithI18nProvider( @@ -106,6 +107,7 @@ describe('SavedObjectsTable', () => { overlays = overlayServiceMock.createStartContract(); notifications = notificationServiceMock.createStartContract(); savedObjects = savedObjectsServiceMock.createStartContract(); + search = dataPluginMock.createStartContract().search; const applications = applicationServiceMock.createStartContract(); applications.capabilities = { @@ -141,6 +143,7 @@ describe('SavedObjectsTable', () => { perPageConfig: 15, goInspectObject: () => {}, canGoInApp: () => true, + search, }; findObjectsMock.mockImplementation(() => ({ diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx index c76fea5a0fb29..b9ebaf2b236f4 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx @@ -73,6 +73,7 @@ import { SavedObjectsManagementActionServiceStart, } from '../../services'; import { Header, Table, Flyout, Relationships } from './components'; +import { DataPublicPluginStart } from '../../../../../plugins/data/public'; interface ExportAllOption { id: string; @@ -86,6 +87,7 @@ export interface SavedObjectsTableProps { savedObjectsClient: SavedObjectsClientContract; indexPatterns: IndexPatternsContract; http: HttpStart; + search: DataPublicPluginStart['search']; overlays: OverlayStart; notifications: NotificationsStart; applications: ApplicationStart; @@ -467,6 +469,7 @@ export class SavedObjectsTable extends Component ); } diff --git a/src/plugins/saved_objects_management/public/plugin.ts b/src/plugins/saved_objects_management/public/plugin.ts index c8dede3da9263..28eac96dcbf46 100644 --- a/src/plugins/saved_objects_management/public/plugin.ts +++ b/src/plugins/saved_objects_management/public/plugin.ts @@ -116,8 +116,9 @@ export class SavedObjectsManagementPlugin }; } - public start(core: CoreStart) { + public start(core: CoreStart, { data }: StartDependencies) { const actionStart = this.actionService.start(); + return { actions: actionStart, }; diff --git a/src/plugins/ui_actions/public/types.ts b/src/plugins/ui_actions/public/types.ts index c7e6d61e15f31..e6247a8bafff7 100644 --- a/src/plugins/ui_actions/public/types.ts +++ b/src/plugins/ui_actions/public/types.ts @@ -19,9 +19,10 @@ import { ActionByType } from './actions/action'; import { TriggerInternal } from './triggers/trigger_internal'; -import { EmbeddableVisTriggerContext, IEmbeddable } from '../../embeddable/public'; import { Filter } from '../../data/public'; import { SELECT_RANGE_TRIGGER, VALUE_CLICK_TRIGGER, APPLY_FILTER_TRIGGER } from './triggers'; +import { IEmbeddable } from '../../embeddable/public'; +import { RangeSelectTriggerContext, ValueClickTriggerContext } from '../../embeddable/public'; export type TriggerRegistry = Map>; export type ActionRegistry = Map>; @@ -36,8 +37,8 @@ export type TriggerContext = BaseContext; export interface TriggerContextMapping { [DEFAULT_TRIGGER]: TriggerContext; - [SELECT_RANGE_TRIGGER]: EmbeddableVisTriggerContext; - [VALUE_CLICK_TRIGGER]: EmbeddableVisTriggerContext; + [SELECT_RANGE_TRIGGER]: RangeSelectTriggerContext; + [VALUE_CLICK_TRIGGER]: ValueClickTriggerContext; [APPLY_FILTER_TRIGGER]: { embeddable: IEmbeddable; filters: Filter[]; diff --git a/src/plugins/vis_default_editor/public/components/agg_group.tsx b/src/plugins/vis_default_editor/public/components/agg_group.tsx index ecbc41f28003c..72515d0845926 100644 --- a/src/plugins/vis_default_editor/public/components/agg_group.tsx +++ b/src/plugins/vis_default_editor/public/components/agg_group.tsx @@ -170,7 +170,7 @@ function DefaultEditorAggGroup({ agg={agg} aggIndex={index} aggIsTooLow={calcAggIsTooLow(agg, index, group, schemas)} - dragHandleProps={provided.dragHandleProps} + dragHandleProps={provided.dragHandleProps || null} formIsTouched={aggsState[agg.id] ? aggsState[agg.id].touched : false} groupName={groupName} isDraggable={stats.count > 1} diff --git a/src/plugins/vis_type_timeseries/public/application/components/icon_select/__snapshots__/icon_select.test.js.snap b/src/plugins/vis_type_timeseries/public/application/components/icon_select/__snapshots__/icon_select.test.js.snap index d269f61beefab..dd454231a9ac2 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/icon_select/__snapshots__/icon_select.test.js.snap +++ b/src/plugins/vis_type_timeseries/public/application/components/icon_select/__snapshots__/icon_select.test.js.snap @@ -85,6 +85,7 @@ exports[`src/legacy/core_plugins/metrics/public/components/icon_select/icon_sele "asPlainText": true, } } + sortMatchesBy="none" /> `; diff --git a/src/plugins/vis_type_timeseries/public/application/components/splits/__snapshots__/terms.test.js.snap b/src/plugins/vis_type_timeseries/public/application/components/splits/__snapshots__/terms.test.js.snap index 78436720dd66c..3bf8b77621cf5 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/splits/__snapshots__/terms.test.js.snap +++ b/src/plugins/vis_type_timeseries/public/application/components/splits/__snapshots__/terms.test.js.snap @@ -223,6 +223,7 @@ exports[`src/legacy/core_plugins/metrics/public/components/splits/terms.test.js "asPlainText": true, } } + sortMatchesBy="none" /> diff --git a/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts b/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts index ffb028ff131b3..1c545bb36cff0 100644 --- a/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts +++ b/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts @@ -33,7 +33,6 @@ import { EmbeddableInput, EmbeddableOutput, Embeddable, - EmbeddableVisTriggerContext, IContainer, } from '../../../../plugins/embeddable/public'; import { dispatchRenderComplete } from '../../../../plugins/kibana_utils/public'; @@ -261,7 +260,7 @@ export class VisualizeEmbeddable extends Embeddable { if (!this.eventsSubject) return; - this.eventsSubject.next({ name: 'filterBucket', data }); + this.eventsSubject.next({ + name: 'filterBucket', + data: data.data + ? { + data: data.data, + negate: data.negate, + } + : { data: [data] }, + }); }, brush: (data: any) => { if (!this.eventsSubject) return; diff --git a/src/plugins/visualizations/public/saved_visualizations/_saved_vis.ts b/src/plugins/visualizations/public/saved_visualizations/_saved_vis.ts index c99c7a4c2caa1..8bc98ca4b4784 100644 --- a/src/plugins/visualizations/public/saved_visualizations/_saved_vis.ts +++ b/src/plugins/visualizations/public/saved_visualizations/_saved_vis.ts @@ -32,7 +32,7 @@ import { // @ts-ignore import { updateOldState } from '../legacy/vis_update_state'; import { extractReferences, injectReferences } from './saved_visualization_references'; -import { IIndexPattern, ISearchSource, SearchSource } from '../../../../plugins/data/public'; +import { IIndexPattern, ISearchSource } from '../../../../plugins/data/public'; import { ISavedVis, SerializedVis } from '../types'; import { createSavedSearchesLoader } from '../../../../plugins/discover/public'; import { getChrome, getOverlays, getIndexPatterns, getSavedObjects, getSearch } from '../services'; @@ -80,20 +80,24 @@ export const convertFromSerializedVis = (vis: SerializedVis): ISavedVis => { }; const getSearchSource = async (inputSearchSource: ISearchSource, savedSearchId?: string) => { + const search = getSearch(); + const searchSource = inputSearchSource.createCopy ? inputSearchSource.createCopy() - : new SearchSource({ ...(inputSearchSource as any).fields }); + : search.searchSource.create({ ...(inputSearchSource as any).fields }); + if (savedSearchId) { const savedSearch = await createSavedSearchesLoader({ + search, savedObjectsClient: getSavedObjects().client, indexPatterns: getIndexPatterns(), - search: getSearch(), chrome: getChrome(), overlays: getOverlays(), }).get(savedSearchId); searchSource.setParent(savedSearch.searchSource); } + searchSource!.setField('size', 0); return searchSource; }; diff --git a/src/plugins/visualizations/public/wizard/__snapshots__/new_vis_modal.test.tsx.snap b/src/plugins/visualizations/public/wizard/__snapshots__/new_vis_modal.test.tsx.snap index 860150d688711..6aef2c2b2ceb7 100644 --- a/src/plugins/visualizations/public/wizard/__snapshots__/new_vis_modal.test.tsx.snap +++ b/src/plugins/visualizations/public/wizard/__snapshots__/new_vis_modal.test.tsx.snap @@ -1214,7 +1214,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1` data-test-subj="visNewDialogTypes" role="menu" > - - - + - - + - + @@ -2669,7 +2669,7 @@ exports[`NewVisModal should render as expected 1`] = ` data-test-subj="visNewDialogTypes" role="menu" > - - - + - - + - + diff --git a/src/plugins/visualizations/public/wizard/type_selection/type_selection.tsx b/src/plugins/visualizations/public/wizard/type_selection/type_selection.tsx index bb5037545cc82..acf562d5bf363 100644 --- a/src/plugins/visualizations/public/wizard/type_selection/type_selection.tsx +++ b/src/plugins/visualizations/public/wizard/type_selection/type_selection.tsx @@ -27,7 +27,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiKeyPadMenu, - EuiKeyPadMenuItemButton, + EuiKeyPadMenuItem, EuiModalHeader, EuiModalHeaderTitle, EuiScreenReaderOnly, @@ -262,7 +262,7 @@ class TypeSelection extends React.Component{visType.title}} onClick={onClick} @@ -282,7 +282,7 @@ class TypeSelection extends React.Component - + ); }; diff --git a/src/plugins/visualize/public/plugin.ts b/src/plugins/visualize/public/plugin.ts index ab64e083a553d..df8479bc891b8 100644 --- a/src/plugins/visualize/public/plugin.ts +++ b/src/plugins/visualize/public/plugin.ts @@ -123,6 +123,8 @@ export class VisualizePlugin }; setServices(deps); + // make sure the index pattern list is up to date + await pluginsStart.data.indexPatterns.clearCache(); const { renderApp } = await import('./application/application'); const unmount = renderApp(params.element, params.appBasePath, deps); return () => { diff --git a/test/functional/apps/dashboard/dashboard_saved_query.js b/test/functional/apps/dashboard/dashboard_saved_query.js new file mode 100644 index 0000000000000..99d0aed082e70 --- /dev/null +++ b/test/functional/apps/dashboard/dashboard_saved_query.js @@ -0,0 +1,128 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import expect from '@kbn/expect'; + +export default function({ getService, getPageObjects }) { + const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); + const PageObjects = getPageObjects(['common', 'dashboard', 'timePicker']); + const browser = getService('browser'); + const queryBar = getService('queryBar'); + const savedQueryManagementComponent = getService('savedQueryManagementComponent'); + const testSubjects = getService('testSubjects'); + + describe('dashboard saved queries', function describeIndexTests() { + before(async function() { + await esArchiver.load('dashboard/current/kibana'); + await kibanaServer.uiSettings.replace({ + defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', + }); + await PageObjects.common.navigateToApp('dashboard'); + }); + + describe('saved query management component functionality', function() { + before(async () => { + await PageObjects.dashboard.gotoDashboardLandingPage(); + await PageObjects.dashboard.clickNewDashboard(); + }); + + it('should show the saved query management component when there are no saved queries', async () => { + await savedQueryManagementComponent.openSavedQueryManagementComponent(); + const descriptionText = await testSubjects.getVisibleText('saved-query-management-popover'); + expect(descriptionText).to.eql( + 'SAVED QUERIES\nThere are no saved queries. Save query text and filters that you want to use again.\nSave current query' + ); + }); + + it('should allow a query to be saved via the saved objects management component', async () => { + await queryBar.setQuery('response:200'); + await savedQueryManagementComponent.saveNewQuery( + 'OkResponse', + '200 responses for .jpg over 24 hours', + true, + true + ); + await savedQueryManagementComponent.savedQueryExistOrFail('OkResponse'); + await savedQueryManagementComponent.savedQueryTextExist('response:200'); + }); + + it('reinstates filters and the time filter when a saved query has filters and a time filter included', async () => { + await PageObjects.timePicker.setDefaultAbsoluteRange(); + await savedQueryManagementComponent.clearCurrentlyLoadedQuery(); + await savedQueryManagementComponent.loadSavedQuery('OkResponse'); + const timePickerValues = await PageObjects.timePicker.getTimeConfigAsAbsoluteTimes(); + expect(timePickerValues.start).to.not.eql(PageObjects.timePicker.defaultStartTime); + expect(timePickerValues.end).to.not.eql(PageObjects.timePicker.defaultEndTime); + }); + + it('preserves the currently loaded query when the page is reloaded', async () => { + await browser.refresh(); + const timePickerValues = await PageObjects.timePicker.getTimeConfigAsAbsoluteTimes(); + expect(timePickerValues.start).to.not.eql(PageObjects.timePicker.defaultStartTime); + expect(timePickerValues.end).to.not.eql(PageObjects.timePicker.defaultEndTime); + expect(await savedQueryManagementComponent.getCurrentlyLoadedQueryID()).to.be('OkResponse'); + }); + + it('allows saving changes to a currently loaded query via the saved query management component', async () => { + await queryBar.setQuery('response:404'); + await savedQueryManagementComponent.updateCurrentlyLoadedQuery( + 'OkResponse', + '404 responses', + false, + false + ); + await savedQueryManagementComponent.savedQueryExistOrFail('OkResponse'); + await savedQueryManagementComponent.clearCurrentlyLoadedQuery(); + expect(await queryBar.getQueryString()).to.eql(''); + await savedQueryManagementComponent.loadSavedQuery('OkResponse'); + expect(await queryBar.getQueryString()).to.eql('response:404'); + }); + + it('allows saving the currently loaded query as a new query', async () => { + await savedQueryManagementComponent.saveCurrentlyLoadedAsNewQuery( + 'OkResponseCopy', + '200 responses', + false, + false + ); + await savedQueryManagementComponent.savedQueryExistOrFail('OkResponseCopy'); + }); + + it('allows deleting the currently loaded saved query in the saved query management component and clears the query', async () => { + await savedQueryManagementComponent.deleteSavedQuery('OkResponseCopy'); + await savedQueryManagementComponent.savedQueryMissingOrFail('OkResponseCopy'); + expect(await queryBar.getQueryString()).to.eql(''); + }); + + it('resets any changes to a loaded query on reloading the same saved query', async () => { + await savedQueryManagementComponent.loadSavedQuery('OkResponse'); + await queryBar.setQuery('response:503'); + await savedQueryManagementComponent.loadSavedQuery('OkResponse'); + expect(await queryBar.getQueryString()).to.eql('response:404'); + }); + + it('allows clearing the currently loaded saved query', async () => { + await savedQueryManagementComponent.loadSavedQuery('OkResponse'); + await savedQueryManagementComponent.clearCurrentlyLoadedQuery(); + expect(await queryBar.getQueryString()).to.eql(''); + }); + }); + }); +} diff --git a/test/functional/apps/dashboard/full_screen_mode.js b/test/functional/apps/dashboard/full_screen_mode.js index df00f64530ca0..17eb6d8f08a9c 100644 --- a/test/functional/apps/dashboard/full_screen_mode.js +++ b/test/functional/apps/dashboard/full_screen_mode.js @@ -25,6 +25,7 @@ export default function({ getService, getPageObjects }) { const kibanaServer = getService('kibanaServer'); const dashboardPanelActions = getService('dashboardPanelActions'); const PageObjects = getPageObjects(['dashboard', 'common']); + const filterBar = getService('filterBar'); describe('full screen mode', () => { before(async () => { @@ -81,5 +82,22 @@ export default function({ getService, getPageObjects }) { expect(isChromeVisible).to.be(true); }); }); + + it('shows filter bar in fullscreen mode', async () => { + await filterBar.addFilter('bytes', 'is', '12345678'); + await PageObjects.dashboard.waitForRenderComplete(); + await PageObjects.dashboard.clickFullScreenMode(); + await retry.try(async () => { + const isChromeHidden = await PageObjects.common.isChromeHidden(); + expect(isChromeHidden).to.be(true); + }); + expect(await filterBar.getFilterCount()).to.be(1); + await PageObjects.dashboard.clickExitFullScreenLogoButton(); + await retry.try(async () => { + const isChromeVisible = await PageObjects.common.isChromeVisible(); + expect(isChromeVisible).to.be(true); + }); + await filterBar.removeFilter('bytes'); + }); }); } diff --git a/test/functional/apps/dashboard/index.js b/test/functional/apps/dashboard/index.js index 6666ccc57d584..bd8e6812147e1 100644 --- a/test/functional/apps/dashboard/index.js +++ b/test/functional/apps/dashboard/index.js @@ -74,6 +74,7 @@ export default function({ getService, loadTestFile }) { loadTestFile(require.resolve('./panel_expand_toggle')); loadTestFile(require.resolve('./dashboard_grid')); loadTestFile(require.resolve('./view_edit')); + loadTestFile(require.resolve('./dashboard_saved_query')); // Order of test suites *shouldn't* be important but there's a bug for the view_edit test above // https://github.com/elastic/kibana/issues/46752 // The dashboard_snapshot test below requires the timestamped URL which breaks the view_edit test. diff --git a/test/functional/apps/discover/_saved_queries.js b/test/functional/apps/discover/_saved_queries.js index 76f3a3aea365f..9b50eeda20073 100644 --- a/test/functional/apps/discover/_saved_queries.js +++ b/test/functional/apps/discover/_saved_queries.js @@ -74,6 +74,7 @@ export default function({ getService, getPageObjects }) { true ); await savedQueryManagementComponent.savedQueryExistOrFail('OkResponse'); + await savedQueryManagementComponent.savedQueryTextExist('response:200'); }); it('reinstates filters and the time filter when a saved query has filters and a time filter included', async () => { diff --git a/test/functional/page_objects/dashboard_page.ts b/test/functional/page_objects/dashboard_page.ts index a20d7ae9a5372..b76ce141a4418 100644 --- a/test/functional/page_objects/dashboard_page.ts +++ b/test/functional/page_objects/dashboard_page.ts @@ -215,6 +215,8 @@ export function DashboardPageProvider({ getService, getPageObjects }: FtrProvide public async clickNewDashboard() { await listingTable.clickNewButton('createDashboardPromptButton'); + // make sure the dashboard page is shown + await this.waitForRenderComplete(); } public async clickCreateDashboardPrompt() { diff --git a/test/functional/services/saved_query_management_component.ts b/test/functional/services/saved_query_management_component.ts index 244c1cd214de5..66bf15f3da53c 100644 --- a/test/functional/services/saved_query_management_component.ts +++ b/test/functional/services/saved_query_management_component.ts @@ -151,6 +151,12 @@ export function SavedQueryManagementComponentProvider({ getService }: FtrProvide await testSubjects.existOrFail(`~load-saved-query-${title}-button`); } + async savedQueryTextExist(text: string) { + await this.openSavedQueryManagementComponent(); + const queryString = await queryBar.getQueryString(); + expect(queryString).to.eql(text); + } + async savedQueryMissingOrFail(title: string) { await retry.try(async () => { await this.openSavedQueryManagementComponent(); diff --git a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json index 611e16e5a942d..001293be44829 100644 --- a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json +++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json @@ -7,7 +7,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@elastic/eui": "21.0.1", + "@elastic/eui": "22.3.0", "react": "^16.12.0", "react-dom": "^16.12.0" } diff --git a/test/plugin_functional/plugins/kbn_sample_panel_action/package.json b/test/plugin_functional/plugins/kbn_sample_panel_action/package.json index 914ff39884fa3..4ca705137a12c 100644 --- a/test/plugin_functional/plugins/kbn_sample_panel_action/package.json +++ b/test/plugin_functional/plugins/kbn_sample_panel_action/package.json @@ -8,7 +8,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@elastic/eui": "21.0.1", + "@elastic/eui": "22.3.0", "react": "^16.12.0" }, "scripts": { diff --git a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json index 9ee7845816faa..818621ffe5ae5 100644 --- a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json +++ b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json @@ -8,7 +8,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@elastic/eui": "21.0.1", + "@elastic/eui": "22.3.0", "react": "^16.12.0" }, "scripts": { diff --git a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/package.json b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/package.json index 3b3d69c06ff3e..59b7337f5ff07 100644 --- a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/package.json +++ b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/package.json @@ -8,7 +8,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@elastic/eui": "21.0.1", + "@elastic/eui": "22.3.0", "react": "^16.12.0" }, "scripts": { diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index c8715ac3447bd..8e5563e4ff674 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -8,7 +8,7 @@ "xpack.apm": ["legacy/plugins/apm", "plugins/apm"], "xpack.beatsManagement": "legacy/plugins/beats_management", "xpack.canvas": "legacy/plugins/canvas", - "xpack.crossClusterReplication": "legacy/plugins/cross_cluster_replication", + "xpack.crossClusterReplication": "plugins/cross_cluster_replication", "xpack.dashboardMode": "legacy/plugins/dashboard_mode", "xpack.data": "plugins/data_enhanced", "xpack.drilldowns": "plugins/drilldowns", diff --git a/x-pack/index.js b/x-pack/index.js index 1a78c24b1221b..43ae5c3e5c5dd 100644 --- a/x-pack/index.js +++ b/x-pack/index.js @@ -21,7 +21,6 @@ import { taskManager } from './legacy/plugins/task_manager'; import { rollup } from './legacy/plugins/rollup'; import { siem } from './legacy/plugins/siem'; import { remoteClusters } from './legacy/plugins/remote_clusters'; -import { crossClusterReplication } from './legacy/plugins/cross_cluster_replication'; import { upgradeAssistant } from './legacy/plugins/upgrade_assistant'; import { uptime } from './legacy/plugins/uptime'; import { encryptedSavedObjects } from './legacy/plugins/encrypted_saved_objects'; @@ -49,7 +48,6 @@ module.exports = function(kibana) { rollup(kibana), siem(kibana), remoteClusters(kibana), - crossClusterReplication(kibana), upgradeAssistant(kibana), uptime(kibana), encryptedSavedObjects(kibana), diff --git a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/__snapshots__/List.test.tsx.snap b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/__snapshots__/List.test.tsx.snap index afa0cb51cd108..0fbf0a5c7a27d 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/__snapshots__/List.test.tsx.snap +++ b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/__snapshots__/List.test.tsx.snap @@ -141,6 +141,7 @@ exports[`ErrorGroupOverview -> List should render empty state 1`] = ` > Group ID @@ -162,6 +163,7 @@ exports[`ErrorGroupOverview -> List should render empty state 1`] = ` > Type @@ -183,6 +185,7 @@ exports[`ErrorGroupOverview -> List should render empty state 1`] = ` > Error message and culprit @@ -231,6 +234,7 @@ exports[`ErrorGroupOverview -> List should render empty state 1`] = ` > Occurrences @@ -272,6 +276,7 @@ exports[`ErrorGroupOverview -> List should render empty state 1`] = ` > Latest occurrence @@ -519,6 +524,7 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = ` > Group ID @@ -540,6 +546,7 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = ` > Type @@ -561,6 +568,7 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = ` > Error message and culprit @@ -609,6 +617,7 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = ` > Occurrences @@ -650,6 +659,7 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = ` > Latest occurrence diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Cytoscape.stories.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Cytoscape.stories.tsx index 31c227d8bbcab..de775dbc8162a 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Cytoscape.stories.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Cytoscape.stories.tsx @@ -75,8 +75,22 @@ storiesOf('app/ServiceMap/Cytoscape', module) const cy = cytoscape(); const elements = [ { data: { id: 'default' } }, + { + data: { + id: 'aws', + 'span.type': 'aws', + 'span.subtype': 'servicename' + } + }, { data: { id: 'cache', 'span.type': 'cache' } }, { data: { id: 'database', 'span.type': 'db' } }, + { + data: { + id: 'cassandra', + 'span.type': 'db', + 'span.subtype': 'cassandra' + } + }, { data: { id: 'elasticsearch', @@ -84,9 +98,87 @@ storiesOf('app/ServiceMap/Cytoscape', module) 'span.subtype': 'elasticsearch' } }, + { + data: { + id: 'mongodb', + 'span.type': 'db', + 'span.subtype': 'mongodb' + } + }, + { + data: { + id: 'mysql', + 'span.type': 'db', + 'span.subtype': 'mysql' + } + }, + { + data: { + id: 'postgresql', + 'span.type': 'db', + 'span.subtype': 'postgresql' + } + }, + { + data: { + id: 'redis', + 'span.type': 'db', + 'span.subtype': 'redis' + } + }, { data: { id: 'external', 'span.type': 'external' } }, { data: { id: 'ext', 'span.type': 'ext' } }, + { + data: { + id: 'graphql', + 'span.type': 'external', + 'span.subtype': 'graphql' + } + }, + { + data: { + id: 'grpc', + 'span.type': 'external', + 'span.subtype': 'grpc' + } + }, + { + data: { + id: 'websocket', + 'span.type': 'external', + 'span.subtype': 'websocket' + } + }, { data: { id: 'messaging', 'span.type': 'messaging' } }, + { + data: { + id: 'jms', + 'span.type': 'messaging', + 'span.subtype': 'jms' + } + }, + { + data: { + id: 'kafka', + 'span.type': 'messaging', + 'span.subtype': 'kafka' + } + }, + { data: { id: 'template', 'span.type': 'template' } }, + { + data: { + id: 'handlebars', + 'span.type': 'template', + 'span.subtype': 'handlebars' + } + }, + { + data: { + id: 'dark', + 'service.name': 'dark service', + 'agent.name': 'dark' + } + }, { data: { id: 'dotnet', @@ -159,11 +251,13 @@ storiesOf('app/ServiceMap/Cytoscape', module) - agent.name: {node.data('agent.name') || 'undefined'}, - span.type: {node.data('span.type') || 'undefined'}, + + agent.name: {node.data('agent.name') || 'undefined'} +
+ span.type: {node.data('span.type') || 'undefined'} +
span.subtype: {node.data('span.subtype') || 'undefined'} - +
} icon={ + + + + diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/icons/cassandra.svg b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/icons/cassandra.svg new file mode 100644 index 0000000000000..0cc2710563958 --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/icons/cassandra.svg @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/icons/dark.svg b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/icons/dark.svg new file mode 100644 index 0000000000000..9ae4b31c1a0d6 --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/icons/dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/icons/graphql.svg b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/icons/graphql.svg new file mode 100644 index 0000000000000..16dd58cd53184 --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/icons/graphql.svg @@ -0,0 +1,3 @@ + + + diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/icons/grpc.svg b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/icons/grpc.svg new file mode 100644 index 0000000000000..768294776f382 --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/icons/grpc.svg @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/icons/handlebars.svg b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/icons/handlebars.svg new file mode 100644 index 0000000000000..b8e808baa1ac1 --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/icons/handlebars.svg @@ -0,0 +1,3 @@ + + + diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/icons/kafka.svg b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/icons/kafka.svg new file mode 100644 index 0000000000000..ed3f00b0dadf2 --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/icons/kafka.svg @@ -0,0 +1,3 @@ + + + diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/icons/mongodb.svg b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/icons/mongodb.svg new file mode 100644 index 0000000000000..bb73f9a5ea90b --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/icons/mongodb.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/icons/mysql.svg b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/icons/mysql.svg new file mode 100644 index 0000000000000..77e4e9b3b5ff8 --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/icons/mysql.svg @@ -0,0 +1,4 @@ + + + + diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/icons/postgresql.svg b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/icons/postgresql.svg new file mode 100644 index 0000000000000..fcae8d10013da --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/icons/postgresql.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/icons/redis.svg b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/icons/redis.svg new file mode 100644 index 0000000000000..907312ebe74e6 --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/icons/redis.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/icons/websocket.svg b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/icons/websocket.svg new file mode 100644 index 0000000000000..2c83babd0bac1 --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/icons/websocket.svg @@ -0,0 +1,3 @@ + + + diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/index.tsx index 87102a486ab5f..27095264461ee 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/index.tsx @@ -74,7 +74,7 @@ function getSpanTypes(span: Span) { const SpanBadge = styled(EuiBadge)` display: inline-block; margin-right: ${px(units.quarter)}; -`; +` as any; const HttpInfoContainer = styled('div')` margin-right: ${px(units.quarter)}; diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SyncBadge.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SyncBadge.tsx index 764f15f943ad2..a5d8902ff1626 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SyncBadge.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SyncBadge.tsx @@ -13,7 +13,7 @@ import { px, units } from '../../../../../../style/variables'; const SpanBadge = styled(EuiBadge)` display: inline-block; margin-right: ${px(units.quarter)}; -`; +` as any; interface SyncBadgeProps { /** diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/__test__/__snapshots__/Stackframe.test.tsx.snap b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/__test__/__snapshots__/Stackframe.test.tsx.snap index 0aeb2443679fa..4177b54f20385 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/__test__/__snapshots__/Stackframe.test.tsx.snap +++ b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/__test__/__snapshots__/Stackframe.test.tsx.snap @@ -192,7 +192,9 @@ exports[`Stackframe when stackframe has source lines should render correctly 1`] />
- + -
-
+ diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Summary/ErrorCountSummaryItemBadge.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Summary/ErrorCountSummaryItemBadge.tsx index 7558f002c0afc..858e6d29bfa5e 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Summary/ErrorCountSummaryItemBadge.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/Summary/ErrorCountSummaryItemBadge.tsx @@ -15,9 +15,9 @@ interface Props { count: number; } -const Badge = styled(EuiBadge)` +const Badge = (styled(EuiBadge)` margin-top: ${px(units.eighth)}; -`; +` as any) as any; export const ErrorCountSummaryItemBadge = ({ count }: Props) => ( diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Summary/HttpInfoSummaryItem/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Summary/HttpInfoSummaryItem/index.tsx index 94ae9e63644b9..1e1d49b2cf417 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Summary/HttpInfoSummaryItem/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/Summary/HttpInfoSummaryItem/index.tsx @@ -11,9 +11,9 @@ import styled from 'styled-components'; import { units, px, truncate, unit } from '../../../../style/variables'; import { HttpStatusBadge } from '../HttpStatusBadge'; -const HttpInfoBadge = styled(EuiBadge)` +const HttpInfoBadge = (styled(EuiBadge)` margin-right: ${px(units.quarter)}; -`; +` as any) as any; const Url = styled('span')` display: inline-block; diff --git a/x-pack/legacy/plugins/beats_management/public/components/tag/tag_badge.tsx b/x-pack/legacy/plugins/beats_management/public/components/tag/tag_badge.tsx index be8a56486577e..7fa0231cf3409 100644 --- a/x-pack/legacy/plugins/beats_management/public/components/tag/tag_badge.tsx +++ b/x-pack/legacy/plugins/beats_management/public/components/tag/tag_badge.tsx @@ -31,7 +31,7 @@ export const TagBadge = (props: TagBadgeProps) => { } onClickAriaLabel={onClickAriaLabel} > {idToRender} diff --git a/x-pack/legacy/plugins/canvas/.storybook/storyshots.test.js b/x-pack/legacy/plugins/canvas/.storybook/storyshots.test.js index 72b0b8f0e533f..a81483d1e7a17 100644 --- a/x-pack/legacy/plugins/canvas/.storybook/storyshots.test.js +++ b/x-pack/legacy/plugins/canvas/.storybook/storyshots.test.js @@ -7,6 +7,7 @@ import path from 'path'; import moment from 'moment'; import 'moment-timezone'; +import ReactDOM from "react-dom"; import initStoryshots, { multiSnapshotWithOptions } from '@storybook/addon-storyshots'; import styleSheetSerializer from 'jest-styled-components/src/styleSheetSerializer'; @@ -24,6 +25,9 @@ moment.tz.setDefault('UTC'); const testTime = new Date(Date.UTC(2019, 5, 1)); // June 1 2019 Date.now = jest.fn(() => testTime); +// Mock telemetry service +jest.mock('../public/lib/ui_metric', () => ({ trackCanvasUiMetric: () => { } })); + // Mock EUI generated ids to be consistently predictable for snapshots. jest.mock(`@elastic/eui/lib/components/form/form_row/make_id`, () => () => `generated-id`); @@ -32,7 +36,7 @@ jest.mock(`@elastic/eui/lib/components/form/form_row/make_id`, () => () => `gene jest.mock('@elastic/eui/lib/components/code/code', () => { const React = require.requireActual('react'); return { - EuiCode: ({children, className}) => ( + EuiCode: ({ children, className }) => ( {children} @@ -61,6 +65,12 @@ jest.mock('@elastic/eui/packages/react-datepicker', () => { }; }); + +// Mock React Portal for components that use modals, tooltips, etc +ReactDOM.createPortal = jest.fn((element) => { + return element; +}); + jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => { return { htmlIdGenerator: () => () => `generated-id`, @@ -71,7 +81,7 @@ jest.mock('plugins/interpreter/registries', () => ({})); // Disabling this test due to https://github.com/elastic/eui/issues/2242 jest.mock( - '../public/components/workpad_header/workpad_export/flyout/__examples__/share_website_flyout.stories', + '../public/components/workpad_header/share_menu/flyout/__examples__/share_website_flyout.stories', () => { return 'Disabled Panel'; } diff --git a/x-pack/legacy/plugins/canvas/.storybook/webpack.config.js b/x-pack/legacy/plugins/canvas/.storybook/webpack.config.js index cc74faeac6a96..963cf831ef698 100644 --- a/x-pack/legacy/plugins/canvas/.storybook/webpack.config.js +++ b/x-pack/legacy/plugins/canvas/.storybook/webpack.config.js @@ -177,8 +177,10 @@ module.exports = async ({ config }) => { }), // Mock out libs used by a few componets to avoid loading in kibana_legacy and platform - new webpack.NormalModuleReplacementPlugin(/lib\/notify/, path.resolve(__dirname, '../tasks/mocks/uiNotify')), + new webpack.NormalModuleReplacementPlugin(/(lib)?\/notify/, path.resolve(__dirname, '../tasks/mocks/uiNotify')), new webpack.NormalModuleReplacementPlugin(/lib\/download_workpad/, path.resolve(__dirname, '../tasks/mocks/downloadWorkpad')), + new webpack.NormalModuleReplacementPlugin(/(lib)?\/custom_element_service/, path.resolve(__dirname, '../tasks/mocks/customElementService')), + new webpack.NormalModuleReplacementPlugin(/(lib)?\/ui_metric/, path.resolve(__dirname, '../tasks/mocks/uiMetric')), ); // Tell Webpack about relevant extensions diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/area_chart/header.png b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/area_chart/header.png deleted file mode 100644 index 93456066429d9..0000000000000 Binary files a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/area_chart/header.png and /dev/null differ diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/area_chart/index.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/area_chart/index.ts index 0650ac15c656e..df829e8b97676 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/area_chart/index.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/area_chart/index.ts @@ -5,14 +5,13 @@ */ import { ElementFactory } from '../../../types'; -import header from './header.png'; export const areaChart: ElementFactory = () => ({ name: 'areaChart', - displayName: 'Area chart', + displayName: 'Area', help: 'A line chart with a filled body', - tags: ['chart'], - image: header, + type: 'chart', + icon: 'visArea', expression: `filters | demodata | pointseries x="time" y="mean(price)" diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/bubble_chart/header.png b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/bubble_chart/header.png deleted file mode 100644 index db541fe7c53b8..0000000000000 Binary files a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/bubble_chart/header.png and /dev/null differ diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/bubble_chart/index.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/bubble_chart/index.ts index 7ab510e419769..7ac1d0ac83b0b 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/bubble_chart/index.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/bubble_chart/index.ts @@ -5,16 +5,15 @@ */ import { ElementFactory } from '../../../types'; -import header from './header.png'; export const bubbleChart: ElementFactory = () => ({ name: 'bubbleChart', - displayName: 'Bubble chart', - tags: ['chart'], + displayName: 'Bubble', + type: 'chart', help: 'A customizable bubble chart', width: 700, height: 300, - image: header, + icon: 'heatmap', expression: `filters | demodata | pointseries x="project" y="sum(price)" color="state" size="size(username)" diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/debug/header.png b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/debug/header.png deleted file mode 100644 index 37ab329a49bb8..0000000000000 Binary files a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/debug/header.png and /dev/null differ diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/debug/index.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/debug/index.ts index 914982951d664..ec8477f8f1017 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/debug/index.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/debug/index.ts @@ -5,14 +5,12 @@ */ import { ElementFactory } from '../../../types'; -import header from './header.png'; export const debug: ElementFactory = () => ({ name: 'debug', - displayName: 'Debug', - tags: ['text'], + displayName: 'Debug data', help: 'Just dumps the configuration of the element', - image: header, + icon: 'bug', expression: `demodata | render as=debug`, }); diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/donut/header.png b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/donut/header.png deleted file mode 100644 index 4bbfb6f8f68fc..0000000000000 Binary files a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/donut/header.png and /dev/null differ diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/donut/index.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/donut/index.ts deleted file mode 100644 index 4ea8037d2073e..0000000000000 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/donut/index.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { ElementFactory } from '../../../types'; -import header from './header.png'; - -export const donut: ElementFactory = () => ({ - name: 'donut', - displayName: 'Donut chart', - tags: ['chart', 'proportion'], - help: 'A customizable donut chart', - image: header, - expression: `filters -| demodata -| pointseries color="project" size="max(price)" -| pie hole=50 labels=false legend="ne" -| render`, -}); diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/dropdown_filter/header.png b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/dropdown_filter/header.png deleted file mode 100644 index 727b4d23941fd..0000000000000 Binary files a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/dropdown_filter/header.png and /dev/null differ diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/dropdown_filter/index.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/dropdown_filter/index.ts index bde223d2a606e..bb1c13ca618be 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/dropdown_filter/index.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/dropdown_filter/index.ts @@ -5,14 +5,13 @@ */ import { ElementFactory } from '../../../types'; -import header from './header.png'; export const dropdownFilter: ElementFactory = () => ({ - name: 'dropdown_filter', - displayName: 'Dropdown filter', - tags: ['filter'], + name: 'dropdownFilter', + displayName: 'Dropdown select', + type: 'filter', help: 'A dropdown from which you can select values for an "exactly" filter', - image: header, + icon: 'filter', height: 50, expression: `demodata | dropdownControl valueColumn=project filterColumn=project | render`, diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/filter_debug/index.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/filter_debug/index.ts new file mode 100644 index 0000000000000..35a4a75f49c4e --- /dev/null +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/filter_debug/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ElementFactory } from '../../../types'; + +export const filterDebug: ElementFactory = () => ({ + name: 'filterDebug', + displayName: 'Debug filter', + help: 'Shows the underlying global filters in a workpad', + icon: 'bug', + expression: `filters +| render as=debug`, +}); diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/horizontal_bar_chart/header.png b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/horizontal_bar_chart/header.png deleted file mode 100644 index 9b6ee47d88698..0000000000000 Binary files a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/horizontal_bar_chart/header.png and /dev/null differ diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/horizontal_bar_chart/index.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/horizontal_bar_chart/index.ts index 7fddf48c70385..9567336decd5d 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/horizontal_bar_chart/index.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/horizontal_bar_chart/index.ts @@ -5,14 +5,13 @@ */ import { ElementFactory } from '../../../types'; -import header from './header.png'; export const horizontalBarChart: ElementFactory = () => ({ name: 'horizontalBarChart', - displayName: 'Horizontal bar chart', - tags: ['chart'], + displayName: 'Bar horizontal', + type: 'chart', help: 'A customizable horizontal bar chart', - image: header, + icon: 'visBarHorizontal', expression: `filters | demodata | pointseries x="size(cost)" y="project" color="project" diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/horizontal_progress_bar/header.png b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/horizontal_progress_bar/header.png deleted file mode 100644 index f28ad4a3ce4be..0000000000000 Binary files a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/horizontal_progress_bar/header.png and /dev/null differ diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/horizontal_progress_bar/index.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/horizontal_progress_bar/index.ts index f4a50a007c5de..529a74893a5de 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/horizontal_progress_bar/index.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/horizontal_progress_bar/index.ts @@ -6,16 +6,14 @@ import { openSans } from '../../../common/lib/fonts'; import { ElementFactory } from '../../../types'; -import header from './header.png'; export const horizontalProgressBar: ElementFactory = () => ({ name: 'horizontalProgressBar', - displayName: 'Horizontal progress bar', - tags: ['chart', 'proportion'], + displayName: 'Horizontal bar', + type: 'progress', help: 'Displays progress as a portion of a horizontal bar', width: 400, height: 30, - image: header, expression: `filters | demodata | math "mean(percent_uptime)" diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/horizontal_progress_pill/header.png b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/horizontal_progress_pill/header.png deleted file mode 100644 index 2eaeb2e976a78..0000000000000 Binary files a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/horizontal_progress_pill/header.png and /dev/null differ diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/horizontal_progress_pill/index.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/horizontal_progress_pill/index.ts index 9b3aea2e55324..d5eba32325d1a 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/horizontal_progress_pill/index.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/horizontal_progress_pill/index.ts @@ -6,16 +6,14 @@ import { openSans } from '../../../common/lib/fonts'; import { ElementFactory } from '../../../types'; -import header from './header.png'; export const horizontalProgressPill: ElementFactory = () => ({ name: 'horizontalProgressPill', - displayName: 'Horizontal progress pill', - tags: ['chart', 'proportion'], + displayName: 'Horizontal pill', + type: 'progress', help: 'Displays progress as a portion of a horizontal pill', width: 400, height: 30, - image: header, expression: `filters | demodata | math "mean(percent_uptime)" diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/image/header.png b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/image/header.png deleted file mode 100644 index 7f29fc64c36b9..0000000000000 Binary files a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/image/header.png and /dev/null differ diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/image/index.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/image/index.ts index eec1e2af61aad..ed7f6a99ddc32 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/image/index.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/image/index.ts @@ -5,14 +5,13 @@ */ import { ElementFactory } from '../../../types'; -import header from './header.png'; export const image: ElementFactory = () => ({ name: 'image', displayName: 'Image', - tags: ['graphic'], + type: 'image', help: 'A static image', - image: header, + icon: 'image', expression: `image dataurl=null mode="contain" | render`, }); diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/index.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/index.ts index feba88dbe8b90..ec3b8a7798be1 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/index.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/index.ts @@ -8,15 +8,15 @@ import { applyElementStrings } from '../../i18n/elements'; import { areaChart } from './area_chart'; import { bubbleChart } from './bubble_chart'; import { debug } from './debug'; -import { donut } from './donut'; import { dropdownFilter } from './dropdown_filter'; +import { filterDebug } from './filter_debug'; import { horizontalBarChart } from './horizontal_bar_chart'; import { horizontalProgressBar } from './horizontal_progress_bar'; import { horizontalProgressPill } from './horizontal_progress_pill'; import { image } from './image'; import { lineChart } from './line_chart'; import { markdown } from './markdown'; -import { metric } from './metric'; +import { metricElementInitializer } from './metric'; import { pie } from './pie'; import { plot } from './plot'; import { progressGauge } from './progress_gauge'; @@ -26,25 +26,26 @@ import { repeatImage } from './repeat_image'; import { revealImage } from './reveal_image'; import { shape } from './shape'; import { table } from './table'; -import { tiltedPie } from './tilted_pie'; import { timeFilter } from './time_filter'; import { verticalBarChart } from './vert_bar_chart'; import { verticalProgressBar } from './vertical_progress_bar'; import { verticalProgressPill } from './vertical_progress_pill'; -export const elementSpecs = applyElementStrings([ +import { SetupInitializer } from '../plugin'; +import { ElementFactory } from '../../types'; + +const elementSpecs = [ areaChart, bubbleChart, debug, - donut, dropdownFilter, + filterDebug, image, horizontalBarChart, horizontalProgressBar, horizontalProgressPill, lineChart, markdown, - metric, pie, plot, progressGauge, @@ -54,9 +55,19 @@ export const elementSpecs = applyElementStrings([ revealImage, shape, table, - tiltedPie, timeFilter, verticalBarChart, verticalProgressBar, verticalProgressPill, -]); +]; + +const initializeElementFactories = [metricElementInitializer]; + +export const initializeElements: SetupInitializer = (core, plugins) => { + const specs = [ + ...elementSpecs, + ...initializeElementFactories.map(factory => factory(core, plugins)), + ]; + + return applyElementStrings(specs); +}; diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/line_chart/header.png b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/line_chart/header.png deleted file mode 100644 index eea133ee3680b..0000000000000 Binary files a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/line_chart/header.png and /dev/null differ diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/line_chart/index.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/line_chart/index.ts index 5b8533eea65bc..d19ddeb00dd67 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/line_chart/index.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/line_chart/index.ts @@ -5,14 +5,13 @@ */ import { ElementFactory } from '../../../types'; -import header from './header.png'; export const lineChart: ElementFactory = () => ({ name: 'lineChart', - displayName: 'Line chart', - tags: ['chart'], + displayName: 'Line', + type: 'chart', help: 'A customizable line chart', - image: header, + icon: 'visLine', expression: `filters | demodata | pointseries x="time" y="mean(price)" diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/markdown/header.png b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/markdown/header.png deleted file mode 100644 index a8b8550f5baea..0000000000000 Binary files a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/markdown/header.png and /dev/null differ diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/markdown/index.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/markdown/index.ts index 1c7013834cbe4..7b114daa11870 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/markdown/index.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/markdown/index.ts @@ -4,15 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import header from './header.png'; - import { ElementFactory } from '../../../types'; export const markdown: ElementFactory = () => ({ name: 'markdown', - displayName: 'Markdown', - tags: ['text'], - help: 'Markup from Markdown', - image: header, + displayName: 'Text', + type: 'text', + help: 'Add text using Markdown', + icon: 'visText', expression: `filters | demodata | markdown "### Welcome to the Markdown element diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/metric/header.png b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/metric/header.png deleted file mode 100644 index 0510342cdc54a..0000000000000 Binary files a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/metric/header.png and /dev/null differ diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/metric/index.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/metric/index.ts index c08c090f11f91..7256657903aab 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/metric/index.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/metric/index.ts @@ -5,24 +5,25 @@ */ import { openSans } from '../../../common/lib/fonts'; -import header from './header.png'; -import { getAdvancedSettings } from '../../../public/lib/kibana_advanced_settings'; - import { ElementFactory } from '../../../types'; -export const metric: ElementFactory = () => ({ - name: 'metric', - displayName: 'Metric', - tags: ['text'], - help: 'A number with a label', - width: 200, - height: 100, - image: header, - expression: `filters -| demodata -| math "unique(country)" -| metric "Countries" - metricFont={font size=48 family="${openSans.value}" color="#000000" align="center" lHeight=48} - labelFont={font size=14 family="${openSans.value}" color="#000000" align="center"} - metricFormat="${getAdvancedSettings().get('format:number:defaultPattern')}" -| render`, -}); +import { SetupInitializer } from '../../plugin'; + +export const metricElementInitializer: SetupInitializer = (core, setup) => { + return () => ({ + name: 'metric', + displayName: 'Metric', + type: 'chart', + help: 'A number with a label', + width: 200, + height: 100, + icon: 'visMetric', + expression: `filters + | demodata + | math "unique(country)" + | metric "Countries" + metricFont={font size=48 family="${openSans.value}" color="#000000" align="center" lHeight=48} + labelFont={font size=14 family="${openSans.value}" color="#000000" align="center"} + metricFormat="${core.uiSettings.get('format:number:defaultPattern')}" + | render`, + }); +}; diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/pie/header.png b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/pie/header.png deleted file mode 100644 index deecd1067427c..0000000000000 Binary files a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/pie/header.png and /dev/null differ diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/pie/index.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/pie/index.ts index cfb9031325254..b7606ea94bc9f 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/pie/index.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/pie/index.ts @@ -4,17 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import header from './header.png'; - import { ElementFactory } from '../../../types'; export const pie: ElementFactory = () => ({ name: 'pie', - displayName: 'Pie chart', - tags: ['chart', 'proportion'], + displayName: 'Pie', + type: 'chart', width: 300, height: 300, help: 'A simple pie chart', - image: header, + icon: 'visPie', expression: `filters | demodata | pointseries color="state" size="max(price)" diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/plot/header.png b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/plot/header.png deleted file mode 100644 index d48c789ae5a92..0000000000000 Binary files a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/plot/header.png and /dev/null differ diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/plot/index.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/plot/index.ts index dd1660d558667..8648b65def4b2 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/plot/index.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/plot/index.ts @@ -5,14 +5,12 @@ */ import { ElementFactory } from '../../../types'; -import header from './header.png'; export const plot: ElementFactory = () => ({ name: 'plot', displayName: 'Coordinate plot', - tags: ['chart'], + type: 'chart', help: 'Mixed line, bar or dot charts', - image: header, expression: `filters | demodata | pointseries x="time" y="sum(price)" color="state" diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/progress_gauge/header.png b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/progress_gauge/header.png deleted file mode 100644 index 8340c8a53b6ce..0000000000000 Binary files a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/progress_gauge/header.png and /dev/null differ diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/progress_gauge/index.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/progress_gauge/index.ts index 4ec192fb787fe..b21b7df286ace 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/progress_gauge/index.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/progress_gauge/index.ts @@ -6,16 +6,15 @@ import { openSans } from '../../../common/lib/fonts'; import { ElementFactory } from '../../../types'; -import header from './header.png'; export const progressGauge: ElementFactory = () => ({ name: 'progressGauge', - displayName: 'Progress gauge', - tags: ['chart', 'proportion'], + displayName: 'Gauge', + type: 'progress', help: 'Displays progress as a portion of a gauge', width: 200, height: 200, - image: header, + icon: 'visGoal', expression: `filters | demodata | math "mean(percent_uptime)" diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/progress_semicircle/header.png b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/progress_semicircle/header.png deleted file mode 100644 index b5b708529edd4..0000000000000 Binary files a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/progress_semicircle/header.png and /dev/null differ diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/progress_semicircle/index.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/progress_semicircle/index.ts index 91fcb24996bc0..9ccb9489e8306 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/progress_semicircle/index.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/progress_semicircle/index.ts @@ -6,16 +6,14 @@ import { openSans } from '../../../common/lib/fonts'; import { ElementFactory } from '../../../types'; -import header from './header.png'; export const progressSemicircle: ElementFactory = () => ({ name: 'progressSemicircle', - displayName: 'Progress semicircle', - tags: ['chart', 'proportion'], + displayName: 'Semicircle', + type: 'progress', help: 'Displays progress as a portion of a semicircle', width: 200, height: 100, - image: header, expression: `filters | demodata | math "mean(percent_uptime)" diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/progress_wheel/header.png b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/progress_wheel/header.png deleted file mode 100644 index 71e5d7e29444e..0000000000000 Binary files a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/progress_wheel/header.png and /dev/null differ diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/progress_wheel/index.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/progress_wheel/index.ts index 05c537f88756b..42bf36346c303 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/progress_wheel/index.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/progress_wheel/index.ts @@ -6,16 +6,14 @@ import { openSans } from '../../../common/lib/fonts'; import { ElementFactory } from '../../../types'; -import header from './header.png'; export const progressWheel: ElementFactory = () => ({ name: 'progressWheel', - displayName: 'Progress wheel', - tags: ['chart', 'proportion'], + displayName: 'Wheel', + type: 'progress', help: 'Displays progress as a portion of a wheel', width: 200, height: 200, - image: header, expression: `filters | demodata | math "mean(percent_uptime)" diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/repeat_image/header.png b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/repeat_image/header.png deleted file mode 100644 index 9843c9a6d02c0..0000000000000 Binary files a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/repeat_image/header.png and /dev/null differ diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/repeat_image/index.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/repeat_image/index.ts index df79651620642..0bf0ec4c2dc1f 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/repeat_image/index.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/repeat_image/index.ts @@ -5,14 +5,12 @@ */ import { ElementFactory } from '../../../types'; -import header from './header.png'; export const repeatImage: ElementFactory = () => ({ name: 'repeatImage', displayName: 'Image repeat', - tags: ['graphic', 'proportion'], + type: 'image', help: 'Repeats an image N times', - image: header, expression: `filters | demodata | math "mean(cost)" diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/reveal_image/header.png b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/reveal_image/header.png deleted file mode 100644 index 8dc33b5a7259e..0000000000000 Binary files a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/reveal_image/header.png and /dev/null differ diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/reveal_image/index.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/reveal_image/index.ts index 01c66ed3a26ec..88000f6b66a91 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/reveal_image/index.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/reveal_image/index.ts @@ -5,14 +5,12 @@ */ import { ElementFactory } from '../../../types'; -import header from './header.png'; export const revealImage: ElementFactory = () => ({ name: 'revealImage', displayName: 'Image reveal', - tags: ['graphic', 'proportion'], + type: 'image', help: 'Reveals a percentage of an image', - image: header, expression: `filters | demodata | math "mean(percent_uptime)" diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/shape/header.png b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/shape/header.png deleted file mode 100644 index 3212d47591c07..0000000000000 Binary files a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/shape/header.png and /dev/null differ diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/shape/index.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/shape/index.ts index 3f3954ff02b02..f922473fa818f 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/shape/index.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/shape/index.ts @@ -5,16 +5,15 @@ */ import { ElementFactory } from '../../../types'; -import header from './header.png'; export const shape: ElementFactory = () => ({ name: 'shape', displayName: 'Shape', - tags: ['graphic'], + type: 'shape', help: 'A customizable shape', width: 200, height: 200, - image: header, + icon: 'node', expression: 'shape "square" fill="#4cbce4" border="rgba(255,255,255,0)" borderWidth=0 maintainAspect=false | render', }); diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/table/header.png b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/table/header.png deleted file mode 100644 index a883faa693c1f..0000000000000 Binary files a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/table/header.png and /dev/null differ diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/table/index.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/table/index.ts index ac13b7dc21293..ec26773fc3bf9 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/table/index.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/table/index.ts @@ -5,14 +5,13 @@ */ import { ElementFactory } from '../../../types'; -import header from './header.png'; export const table: ElementFactory = () => ({ name: 'table', displayName: 'Data table', - tags: ['text'], + type: 'chart', help: 'A scrollable grid for displaying data in a tabular format', - image: header, + icon: 'visTable', expression: `filters | demodata | table diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/tilted_pie/header.png b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/tilted_pie/header.png deleted file mode 100644 index b3329f991158c..0000000000000 Binary files a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/tilted_pie/header.png and /dev/null differ diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/tilted_pie/index.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/tilted_pie/index.ts deleted file mode 100644 index 21d8ba1e1b04a..0000000000000 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/tilted_pie/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { ElementFactory } from '../../../types'; -import header from './header.png'; - -export const tiltedPie: ElementFactory = () => ({ - name: 'tiltedPie', - displayName: 'Tilted pie chart', - tags: ['chart', 'proportion'], - width: 500, - height: 250, - help: 'A customizable tilted pie chart', - image: header, - expression: `filters -| demodata -| pointseries color="project" size="max(price)" -| pie tilt=0.5 -| render`, -}); diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/time_filter/header.png b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/time_filter/header.png deleted file mode 100644 index d36b4cc97e5b1..0000000000000 Binary files a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/time_filter/header.png and /dev/null differ diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/time_filter/index.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/time_filter/index.ts index b384707c243d1..702ccb8a2312f 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/time_filter/index.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/time_filter/index.ts @@ -5,14 +5,13 @@ */ import { ElementFactory } from '../../../types'; -import header from './header.png'; export const timeFilter: ElementFactory = () => ({ - name: 'time_filter', + name: 'timeFilter', displayName: 'Time filter', - tags: ['filter'], + type: 'filter', help: 'Set a time window', - image: header, + icon: 'calendar', height: 50, expression: `timefilterControl compact=true column=@timestamp | render`, diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/vert_bar_chart/header.png b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/vert_bar_chart/header.png deleted file mode 100644 index 90505dd0dc77d..0000000000000 Binary files a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/vert_bar_chart/header.png and /dev/null differ diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/vert_bar_chart/index.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/vert_bar_chart/index.ts index ac4e3a0a72150..2354be0328e7c 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/vert_bar_chart/index.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/vert_bar_chart/index.ts @@ -5,14 +5,13 @@ */ import { ElementFactory } from '../../../types'; -import header from './header.png'; export const verticalBarChart: ElementFactory = () => ({ name: 'verticalBarChart', displayName: 'Vertical bar chart', - tags: ['chart'], + type: 'chart', help: 'A customizable vertical bar chart', - image: header, + icon: 'visBarVertical', expression: `filters | demodata | pointseries x="project" y="size(cost)" color="project" diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/vertical_progress_bar/header.png b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/vertical_progress_bar/header.png deleted file mode 100644 index b9ff963e92c31..0000000000000 Binary files a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/vertical_progress_bar/header.png and /dev/null differ diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/vertical_progress_bar/index.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/vertical_progress_bar/index.ts index e12903dafede9..b5e6c9816e3f5 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/vertical_progress_bar/index.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/vertical_progress_bar/index.ts @@ -6,16 +6,14 @@ import { openSans } from '../../../common/lib/fonts'; import { ElementFactory } from '../../../types'; -import header from './header.png'; export const verticalProgressBar: ElementFactory = () => ({ name: 'verticalProgressBar', displayName: 'Vertical progress bar', - tags: ['chart', 'proportion'], + type: 'progress', help: 'Displays progress as a portion of a vertical bar', width: 80, height: 400, - image: header, expression: `filters | demodata | math "mean(percent_uptime)" diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/vertical_progress_pill/header.png b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/vertical_progress_pill/header.png deleted file mode 100644 index a4ac6b57da236..0000000000000 Binary files a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/vertical_progress_pill/header.png and /dev/null differ diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/vertical_progress_pill/index.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/vertical_progress_pill/index.ts index 8926a12da8a47..28e80372494db 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/vertical_progress_pill/index.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/elements/vertical_progress_pill/index.ts @@ -6,16 +6,14 @@ import { openSans } from '../../../common/lib/fonts'; import { ElementFactory } from '../../../types'; -import header from './header.png'; export const verticalProgressPill: ElementFactory = () => ({ name: 'verticalProgressPill', displayName: 'Vertical progress pill', - tags: ['chart', 'proportion'], + type: 'progress', help: 'Displays progress as a portion of a vertical pill', width: 80, height: 400, - image: header, expression: `filters | demodata | math "mean(percent_uptime)" diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/timefilterControl.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/timefilterControl.ts index 2f7f5c26ad0b7..61ecd66455e78 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/timefilterControl.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/timefilterControl.ts @@ -8,7 +8,7 @@ import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; import { Render } from '../../../types'; import { getFunctionHelp } from '../../../i18n'; -interface Arguments { +export interface Arguments { column: string; compact: boolean; filterGroup: string; diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/plugin.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/plugin.ts index a654c6b28b350..4452e5e9e31fe 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/plugin.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/plugin.ts @@ -14,18 +14,16 @@ import { functions } from './functions/browser'; import { typeFunctions } from './expression_types'; // @ts-ignore: untyped local import { renderFunctions, renderFunctionFactories } from './renderers'; - -import { elementSpecs } from './elements'; +import { initializeElements } from './elements'; // @ts-ignore Untyped Local import { transformSpecs } from './uis/transforms'; // @ts-ignore Untyped Local import { datasourceSpecs } from './uis/datasources'; // @ts-ignore Untyped Local import { modelSpecs } from './uis/models'; +import { initializeViews } from './uis/views'; // @ts-ignore Untyped Local -import { viewSpecs } from './uis/views'; -// @ts-ignore Untyped Local -import { args as argSpecs } from './uis/arguments'; +import { initializeArgs } from './uis/arguments'; import { tagSpecs } from './uis/tags'; import { templateSpecs } from './templates'; @@ -39,6 +37,9 @@ export interface StartDeps { inspector: InspectorStart; } +export type SetupInitializer = (core: CoreSetup, plugins: SetupDeps) => T; +export type StartInitializer = (core: CoreStart, plugins: StartDeps) => T; + /** @internal */ export class CanvasSrcPlugin implements Plugin { public setup(core: CoreSetup, plugins: SetupDeps) { @@ -53,11 +54,11 @@ export class CanvasSrcPlugin implements Plugin ); }); - plugins.canvas.addElements(elementSpecs); + plugins.canvas.addElements(initializeElements(core, plugins)); plugins.canvas.addDatasourceUIs(datasourceSpecs); plugins.canvas.addModelUIs(modelSpecs); - plugins.canvas.addViewUIs(viewSpecs); - plugins.canvas.addArgumentUIs(argSpecs); + plugins.canvas.addViewUIs(initializeViews(core, plugins)); + plugins.canvas.addArgumentUIs(initializeArgs(core, plugins)); plugins.canvas.addTagUIs(tagSpecs); plugins.canvas.addTemplates(templateSpecs); plugins.canvas.addTransformUIs(transformSpecs); diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/index.js b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/index.js index 84f92f5149893..263f2d8ec30b5 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/index.js +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/index.js @@ -20,7 +20,7 @@ import { revealImage } from './reveal_image'; import { shape } from './shape'; import { table } from './table'; import { text } from './text'; -import { timeFilter } from './time_filter'; +import { timeFilterFactory } from './time_filter'; export const renderFunctions = [ advancedFilter, @@ -38,7 +38,6 @@ export const renderFunctions = [ shape, table, text, - timeFilter, ]; -export const renderFunctionFactories = [embeddableRendererFactory]; +export const renderFunctionFactories = [embeddableRendererFactory, timeFilterFactory]; diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/index.tsx b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/index.tsx index 55a453720e2f0..85ea754de670d 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/index.tsx +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/index.tsx @@ -4,22 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; -import { getAdvancedSettings } from '../../../../public/lib/kibana_advanced_settings'; -import { TimeFilter as Component, Props } from './time_filter'; +import { TimeFilter } from './time_filter'; -export const TimeFilter = (props: Props) => { - const customQuickRanges = (getAdvancedSettings().get('timepicker:quickRanges') || []).map( - ({ from, to, display }: { from: string; to: string; display: string }) => ({ - start: from, - end: to, - label: display, - }) - ); - - const customDateFormat = getAdvancedSettings().get('dateFormat'); - - return ( - - ); -}; +export { TimeFilter }; diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/index.js b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/index.js deleted file mode 100644 index cbc514e218d74..0000000000000 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/index.js +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import ReactDOM from 'react-dom'; -import React from 'react'; -import { toExpression } from '@kbn/interpreter/common'; -import { syncFilterExpression } from '../../../public/lib/sync_filter_expression'; -import { RendererStrings } from '../../../i18n'; -import { TimeFilter } from './components'; - -const { timeFilter: strings } = RendererStrings; - -export const timeFilter = () => ({ - name: 'time_filter', - displayName: strings.getDisplayName(), - help: strings.getHelpDescription(), - reuseDomNode: true, // must be true, otherwise popovers don't work - render(domNode, config, handlers) { - const filterExpression = handlers.getFilter(); - - if (filterExpression !== '') { - // NOTE: setFilter() will cause a data refresh, avoid calling unless required - // compare expression and filter, update filter if needed - const { changed, newAst } = syncFilterExpression(config, filterExpression, [ - 'column', - 'filterGroup', - ]); - - if (changed) { - handlers.setFilter(toExpression(newAst)); - } - } - - ReactDOM.render( - , - domNode, - () => handlers.done() - ); - - handlers.onDestroy(() => { - ReactDOM.unmountComponentAtNode(domNode); - }); - }, -}); diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/index.tsx b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/index.tsx new file mode 100644 index 0000000000000..ef5bfb70d4b3d --- /dev/null +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/index.tsx @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import ReactDOM from 'react-dom'; +import React from 'react'; +import { toExpression } from '@kbn/interpreter/common'; +import { syncFilterExpression } from '../../../public/lib/sync_filter_expression'; +import { RendererStrings } from '../../../i18n'; +import { TimeFilter } from './components'; +import { StartInitializer } from '../../plugin'; +import { RendererHandlers } from '../../../types'; +import { Arguments } from '../../functions/common/timefilterControl'; +import { RendererFactory } from '../../../types'; + +const { timeFilter: strings } = RendererStrings; + +export const timeFilterFactory: StartInitializer> = (core, plugins) => { + const { uiSettings } = core; + + const customQuickRanges = (uiSettings.get('timepicker:quickRanges') || []).map( + ({ from, to, display }: { from: string; to: string; display: string }) => ({ + start: from, + end: to, + label: display, + }) + ); + + const customDateFormat = uiSettings.get('dateFormat'); + + return () => ({ + name: 'time_filter', + displayName: strings.getDisplayName(), + help: strings.getHelpDescription(), + reuseDomNode: true, // must be true, otherwise popovers don't work + render: async (domNode: HTMLElement, config: Arguments, handlers: RendererHandlers) => { + const filterExpression = handlers.getFilter(); + + if (filterExpression !== '') { + // NOTE: setFilter() will cause a data refresh, avoid calling unless required + // compare expression and filter, update filter if needed + const { changed, newAst } = syncFilterExpression(config, filterExpression, [ + 'column', + 'filterGroup', + ]); + + if (changed) { + handlers.setFilter(toExpression(newAst)); + } + } + + ReactDOM.render( + , + domNode, + () => handlers.done() + ); + + handlers.onDestroy(() => { + ReactDOM.unmountComponentAtNode(domNode); + }); + }, + }); +}; diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/date_format/index.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/date_format/index.ts index d19bfa64bae76..655a362fe6d33 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/date_format/index.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/date_format/index.ts @@ -7,38 +7,40 @@ import { compose, withProps } from 'recompose'; import moment from 'moment'; import { DateFormatArgInput as Component, Props as ComponentProps } from './date_format'; -import { getAdvancedSettings } from '../../../../public/lib/kibana_advanced_settings'; // @ts-ignore untyped local lib import { templateFromReactComponent } from '../../../../public/lib/template_from_react_component'; import { ArgumentFactory } from '../../../../types/arguments'; import { ArgumentStrings } from '../../../../i18n'; -const { DateFormat: strings } = ArgumentStrings; +import { SetupInitializer } from '../../../plugin'; -const getFormatMap = () => ({ - DEFAULT: getAdvancedSettings().get('dateFormat'), - NANOS: getAdvancedSettings().get('dateNanosFormat'), - ISO8601: '', - LOCAL_LONG: 'LLLL', - LOCAL_SHORT: 'LLL', - LOCAL_DATE: 'l', - LOCAL_TIME_WITH_SECONDS: 'LTS', -}); +const { DateFormat: strings } = ArgumentStrings; -const now = moment(); +export const dateFormatInitializer: SetupInitializer> = ( + core, + plugins +) => { + const formatMap = { + DEFAULT: core.uiSettings.get('dateFormat'), + NANOS: core.uiSettings.get('dateNanosFormat'), + ISO8601: '', + LOCAL_LONG: 'LLLL', + LOCAL_SHORT: 'LLL', + LOCAL_DATE: 'l', + LOCAL_TIME_WITH_SECONDS: 'LTS', + }; -const dateFormats = Object.values(getFormatMap()).map(format => ({ - value: format, - text: moment.utc(now).format(format), -})); + const dateFormats = Object.values(formatMap).map(format => ({ + value: format, + text: moment.utc(moment()).format(format), + })); -export const DateFormatArgInput = compose(withProps({ dateFormats }))( - Component -); + const DateFormatArgInput = compose(withProps({ dateFormats }))(Component); -export const dateFormat: ArgumentFactory = () => ({ - name: 'dateFormat', - displayName: strings.getDisplayName(), - help: strings.getHelp(), - simpleTemplate: templateFromReactComponent(DateFormatArgInput), -}); + return () => ({ + name: 'dateFormat', + displayName: strings.getDisplayName(), + help: strings.getHelp(), + simpleTemplate: templateFromReactComponent(DateFormatArgInput), + }); +}; diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/index.js b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/index.ts similarity index 55% rename from x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/index.js rename to x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/index.ts index 829fe3e3bef3d..bd84a2eb97d24 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/index.js +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/index.ts @@ -5,29 +5,41 @@ */ import { axisConfig } from './axis_config'; +// @ts-ignore untyped local import { datacolumn } from './datacolumn'; -import { dateFormat } from './date_format'; +import { dateFormatInitializer } from './date_format'; +// @ts-ignore untyped local import { filterGroup } from './filter_group'; +// @ts-ignore untyped local import { imageUpload } from './image_upload'; +// @ts-ignore untyped local import { number } from './number'; -import { numberFormat } from './number_format'; +import { numberFormatInitializer } from './number_format'; +// @ts-ignore untyped local import { palette } from './palette'; +// @ts-ignore untyped local import { percentage } from './percentage'; +// @ts-ignore untyped local import { range } from './range'; +// @ts-ignore untyped local import { select } from './select'; +// @ts-ignore untyped local import { shape } from './shape'; +// @ts-ignore untyped local import { string } from './string'; +// @ts-ignore untyped local import { textarea } from './textarea'; +// @ts-ignore untyped local import { toggle } from './toggle'; +import { SetupInitializer } from '../../plugin'; + export const args = [ axisConfig, datacolumn, - dateFormat, filterGroup, imageUpload, number, - numberFormat, palette, percentage, range, @@ -37,3 +49,9 @@ export const args = [ textarea, toggle, ]; + +export const initializers = [dateFormatInitializer, numberFormatInitializer]; + +export const initializeArgs: SetupInitializer = (core, plugins) => { + return [...args, ...initializers.map(initializer => initializer(core, plugins))]; +}; diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/number_format/index.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/number_format/index.ts index ce6c90c89a5a0..4025d4deaf997 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/number_format/index.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/number_format/index.ts @@ -6,40 +6,42 @@ import { compose, withProps } from 'recompose'; import { NumberFormatArgInput as Component, Props as ComponentProps } from './number_format'; -import { getAdvancedSettings } from '../../../../public/lib/kibana_advanced_settings'; // @ts-ignore untyped local lib import { templateFromReactComponent } from '../../../../public/lib/template_from_react_component'; import { ArgumentFactory } from '../../../../types/arguments'; import { ArgumentStrings } from '../../../../i18n'; +import { SetupInitializer } from '../../../plugin'; const { NumberFormat: strings } = ArgumentStrings; -const getFormatMap = () => ({ - NUMBER: getAdvancedSettings().get('format:number:defaultPattern'), - PERCENT: getAdvancedSettings().get('format:percent:defaultPattern'), - CURRENCY: getAdvancedSettings().get('format:currency:defaultPattern'), - DURATION: '00:00:00', - BYTES: getAdvancedSettings().get('format:bytes:defaultPattern'), -}); +export const numberFormatInitializer: SetupInitializer> = ( + core, + plugins +) => { + const formatMap = { + NUMBER: core.uiSettings.get('format:number:defaultPattern'), + PERCENT: core.uiSettings.get('format:percent:defaultPattern'), + CURRENCY: core.uiSettings.get('format:currency:defaultPattern'), + DURATION: '00:00:00', + BYTES: core.uiSettings.get('format:bytes:defaultPattern'), + }; -const getNumberFormats = () => { - const formatMap = getFormatMap(); - return [ + const numberFormats = [ { value: formatMap.NUMBER, text: strings.getFormatNumber() }, { value: formatMap.PERCENT, text: strings.getFormatPercent() }, { value: formatMap.CURRENCY, text: strings.getFormatCurrency() }, { value: formatMap.DURATION, text: strings.getFormatDuration() }, { value: formatMap.BYTES, text: strings.getFormatBytes() }, ]; -}; -export const NumberFormatArgInput = compose( - withProps({ numberFormats: getNumberFormats() }) -)(Component); + const NumberFormatArgInput = compose(withProps({ numberFormats }))( + Component + ); -export const numberFormat: ArgumentFactory = () => ({ - name: 'numberFormat', - displayName: strings.getDisplayName(), - help: strings.getHelp(), - simpleTemplate: templateFromReactComponent(NumberFormatArgInput), -}); + return () => ({ + name: 'numberFormat', + displayName: strings.getDisplayName(), + help: strings.getHelp(), + simpleTemplate: templateFromReactComponent(NumberFormatArgInput), + }); +}; diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/tags/chart.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/tags/chart.ts deleted file mode 100644 index 4c535a42c3c44..0000000000000 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/tags/chart.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { euiPaletteColorBlind } from '@elastic/eui'; -import { TagFactory } from '../../../public/lib/tag'; -import { TagStrings as strings } from '../../../i18n'; -const euiVisPalette = euiPaletteColorBlind(); - -export const chart: TagFactory = () => ({ - name: strings.chart(), - color: euiVisPalette[4], -}); diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/tags/filter.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/tags/filter.ts deleted file mode 100644 index 5249856dec271..0000000000000 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/tags/filter.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { euiPaletteColorBlind } from '@elastic/eui'; -import { TagFactory } from '../../../public/lib/tag'; -import { TagStrings as strings } from '../../../i18n'; - -const euiVisPalette = euiPaletteColorBlind(); - -export const filter: TagFactory = () => ({ - name: strings.filter(), - color: euiVisPalette[1], -}); diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/tags/graphic.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/tags/graphic.ts deleted file mode 100644 index 36d66801ef681..0000000000000 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/tags/graphic.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { euiPaletteColorBlind } from '@elastic/eui'; -import { TagFactory } from '../../../public/lib/tag'; -import { TagStrings as strings } from '../../../i18n'; -const euiVisPalette = euiPaletteColorBlind(); - -export const graphic: TagFactory = () => ({ - name: strings.graphic(), - color: euiVisPalette[5], -}); diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/tags/index.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/tags/index.ts index 2587665a452b5..2e711437c72a8 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/tags/index.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/tags/index.ts @@ -4,13 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { chart } from './chart'; -import { filter } from './filter'; -import { graphic } from './graphic'; import { presentation } from './presentation'; -import { proportion } from './proportion'; import { report } from './report'; -import { text } from './text'; // Registry expects a function that returns a spec object -export const tagSpecs = [chart, filter, graphic, presentation, proportion, report, text]; +export const tagSpecs = [presentation, report]; diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/tags/proportion.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/tags/proportion.ts deleted file mode 100644 index 4d37ecfaa367a..0000000000000 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/tags/proportion.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { euiPaletteColorBlind } from '@elastic/eui'; -import { TagFactory } from '../../../public/lib/tag'; -import { TagStrings as strings } from '../../../i18n'; -const euiVisPalette = euiPaletteColorBlind(); - -export const proportion: TagFactory = () => ({ - name: strings.proportion(), - color: euiVisPalette[3], -}); diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/views/index.js b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/views/index.ts similarity index 56% rename from x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/views/index.js rename to x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/views/index.ts index 6e2685dcb9893..fdcaf21982050 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/views/index.js +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/views/index.ts @@ -4,27 +4,42 @@ * you may not use this file except in compliance with the Elastic License. */ +// @ts-ignore untyped local import { dropdownControl } from './dropdownControl'; +// @ts-ignore untyped local import { getCell } from './getCell'; +// @ts-ignore untyped local import { image } from './image'; +// @ts-ignore untyped local import { markdown } from './markdown'; -import { metric } from './metric'; +// @ts-ignore untyped local +import { metricInitializer } from './metric'; +// @ts-ignore untyped local import { pie } from './pie'; +// @ts-ignore untyped local import { plot } from './plot'; +// @ts-ignore untyped local import { progress } from './progress'; +// @ts-ignore untyped local import { repeatImage } from './repeatImage'; +// @ts-ignore untyped local import { revealImage } from './revealImage'; +// @ts-ignore untyped local import { render } from './render'; +// @ts-ignore untyped local import { shape } from './shape'; +// @ts-ignore untyped local import { table } from './table'; +// @ts-ignore untyped local import { timefilterControl } from './timefilterControl'; +import { SetupInitializer } from '../../plugin'; + export const viewSpecs = [ dropdownControl, getCell, image, markdown, - metric, pie, plot, progress, @@ -35,3 +50,9 @@ export const viewSpecs = [ table, timefilterControl, ]; + +export const viewInitializers = [metricInitializer]; + +export const initializeViews: SetupInitializer = (core, plugins) => { + return [...viewSpecs, ...viewInitializers.map(initializer => initializer(core, plugins))]; +}; diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/views/metric.js b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/views/metric.js deleted file mode 100644 index e69f8f1de5952..0000000000000 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/views/metric.js +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { openSans } from '../../../common/lib/fonts'; -import { getAdvancedSettings } from '../../../public/lib/kibana_advanced_settings'; -import { ViewStrings } from '../../../i18n'; - -const { Metric: strings } = ViewStrings; - -export const metric = () => ({ - name: 'metric', - displayName: strings.getDisplayName(), - modelArgs: [['_', { label: strings.getNumberDisplayName() }]], - requiresContext: false, - args: [ - { - name: 'metricFormat', - displayName: strings.getMetricFormatDisplayName(), - help: strings.getMetricFormatHelp(), - argType: 'numberFormat', - default: `"${getAdvancedSettings().get('format:number:defaultPattern')}"`, - }, - { - name: '_', - displayName: strings.getLabelDisplayName(), - help: strings.getLabelHelp(), - argType: 'string', - default: '""', - }, - { - name: 'metricFont', - displayName: strings.getMetricFontDisplayName(), - help: strings.getMetricFontHelp(), - argType: 'font', - default: `{font size=48 family="${openSans.value}" color="#000000" align=center lHeight=48}`, - }, - { - name: 'labelFont', - displayName: strings.getLabelFontDisplayName(), - help: strings.getLabelFontHelp(), - argType: 'font', - default: `{font size=18 family="${openSans.value}" color="#000000" align=center}`, - }, - ], -}); diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/views/metric.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/views/metric.ts new file mode 100644 index 0000000000000..93912b7b0517f --- /dev/null +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/views/metric.ts @@ -0,0 +1,50 @@ +/* + * 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 { openSans } from '../../../common/lib/fonts'; +import { ViewStrings } from '../../../i18n'; +import { SetupInitializer } from '../../plugin'; + +const { Metric: strings } = ViewStrings; + +export const metricInitializer: SetupInitializer = (core, plugin) => { + return () => ({ + name: 'metric', + displayName: strings.getDisplayName(), + modelArgs: [['_', { label: strings.getNumberDisplayName() }]], + requiresContext: false, + args: [ + { + name: 'metricFormat', + displayName: strings.getMetricFormatDisplayName(), + help: strings.getMetricFormatHelp(), + argType: 'numberFormat', + default: `"${core.uiSettings.get('format:number:defaultPattern')}"`, + }, + { + name: '_', + displayName: strings.getLabelDisplayName(), + help: strings.getLabelHelp(), + argType: 'string', + default: '""', + }, + { + name: 'metricFont', + displayName: strings.getMetricFontDisplayName(), + help: strings.getMetricFontHelp(), + argType: 'font', + default: `{font size=48 family="${openSans.value}" color="#000000" align=center lHeight=48}`, + }, + { + name: 'labelFont', + displayName: strings.getLabelFontDisplayName(), + help: strings.getLabelFontHelp(), + argType: 'font', + default: `{font size=18 family="${openSans.value}" color="#000000" align=center}`, + }, + ], + }); +}; diff --git a/x-pack/legacy/plugins/canvas/common/lib/constants.ts b/x-pack/legacy/plugins/canvas/common/lib/constants.ts index ac8e80b8d7b89..a37dc3fd6a7b3 100644 --- a/x-pack/legacy/plugins/canvas/common/lib/constants.ts +++ b/x-pack/legacy/plugins/canvas/common/lib/constants.ts @@ -40,3 +40,4 @@ export const API_ROUTE_SHAREABLE_ZIP = '/public/canvas/zip'; export const API_ROUTE_SHAREABLE_RUNTIME = '/public/canvas/runtime'; export const API_ROUTE_SHAREABLE_RUNTIME_DOWNLOAD = `/public/canvas/${SHAREABLE_RUNTIME_NAME}.js`; export const CANVAS_EMBEDDABLE_CLASSNAME = `canvasEmbeddable`; +export const CONTEXT_MENU_TOP_BORDER_CLASSNAME = 'canvasContextMenu--topBorder'; diff --git a/x-pack/legacy/plugins/canvas/i18n/components.ts b/x-pack/legacy/plugins/canvas/i18n/components.ts index d0a9051d7af87..7bd16c4933ce1 100644 --- a/x-pack/legacy/plugins/canvas/i18n/components.ts +++ b/x-pack/legacy/plugins/canvas/i18n/components.ts @@ -15,7 +15,7 @@ export const ComponentStrings = { }), getTitleText: () => i18n.translate('xpack.canvas.embedObject.titleText', { - defaultMessage: 'Embed Object', + defaultMessage: 'Add from Visualize library', }), }, AdvancedFilter: { @@ -305,21 +305,21 @@ export const ComponentStrings = { }), }, ElementControls: { - getEditTooltip: () => - i18n.translate('xpack.canvas.elementControls.editToolTip', { - defaultMessage: 'Edit', - }), - getEditAriaLabel: () => - i18n.translate('xpack.canvas.elementControls.editAriaLabel', { - defaultMessage: 'Edit element', + getDeleteAriaLabel: () => + i18n.translate('xpack.canvas.elementControls.deleteAriaLabel', { + defaultMessage: 'Delete element', }), getDeleteTooltip: () => i18n.translate('xpack.canvas.elementControls.deleteToolTip', { defaultMessage: 'Delete', }), - getDeleteAriaLabel: () => - i18n.translate('xpack.canvas.elementControls.deleteAriaLabel', { - defaultMessage: 'Delete element', + getEditAriaLabel: () => + i18n.translate('xpack.canvas.elementControls.editAriaLabel', { + defaultMessage: 'Edit element', + }), + getEditTooltip: () => + i18n.translate('xpack.canvas.elementControls.editToolTip', { + defaultMessage: 'Edit', }), }, ElementSettings: { @@ -336,53 +336,6 @@ export const ComponentStrings = { description: 'This tab contains the settings for how data is displayed in a Canvas element', }), }, - ElementTypes: { - getEditElementTitle: () => - i18n.translate('xpack.canvas.elementTypes.editElementTitle', { - defaultMessage: 'Edit element', - }), - getDeleteElementTitle: (elementName: string) => - i18n.translate('xpack.canvas.elementTypes.deleteElementTitle', { - defaultMessage: `Delete element '{elementName}'?`, - values: { - elementName, - }, - }), - getDeleteElementDescription: () => - i18n.translate('xpack.canvas.elementTypes.deleteElementDescription', { - defaultMessage: 'Are you sure you want to delete this element?', - }), - getCancelButtonLabel: () => - i18n.translate('xpack.canvas.elementTypes.cancelButtonLabel', { - defaultMessage: 'Cancel', - }), - getDeleteButtonLabel: () => - i18n.translate('xpack.canvas.elementTypes.deleteButtonLabel', { - defaultMessage: 'Delete', - }), - getAddNewElementTitle: () => - i18n.translate('xpack.canvas.elementTypes.addNewElementTitle', { - defaultMessage: 'Add new elements', - }), - getAddNewElementDescription: () => - i18n.translate('xpack.canvas.elementTypes.addNewElementDescription', { - defaultMessage: 'Group and save workpad elements to create new elements', - }), - getFindElementPlaceholder: () => - i18n.translate('xpack.canvas.elementTypes.findElementPlaceholder', { - defaultMessage: 'Find element', - }), - getElementsTitle: () => - i18n.translate('xpack.canvas.elementTypes.elementsTitle', { - defaultMessage: 'Elements', - description: 'Title for the "Elements" tab when adding a new element', - }), - getMyElementsTitle: () => - i18n.translate('xpack.canvas.elementTypes.myElementsTitle', { - defaultMessage: 'My elements', - description: 'Title for the "My elements" tab when adding a new element', - }), - }, Error: { getDescription: () => i18n.translate('xpack.canvas.errorComponent.description', { @@ -633,6 +586,61 @@ export const ComponentStrings = { defaultMessage: 'Delete', }), }, + SavedElementsModal: { + getAddNewElementDescription: () => + i18n.translate('xpack.canvas.savedElementsModal.addNewElementDescription', { + defaultMessage: 'Group and save workpad elements to create new elements', + }), + getAddNewElementTitle: () => + i18n.translate('xpack.canvas.savedElementsModal.addNewElementTitle', { + defaultMessage: 'Add new elements', + }), + getCancelButtonLabel: () => + i18n.translate('xpack.canvas.savedElementsModal.cancelButtonLabel', { + defaultMessage: 'Cancel', + }), + getDeleteButtonLabel: () => + i18n.translate('xpack.canvas.savedElementsModal.deleteButtonLabel', { + defaultMessage: 'Delete', + }), + getDeleteElementDescription: () => + i18n.translate('xpack.canvas.savedElementsModal.deleteElementDescription', { + defaultMessage: 'Are you sure you want to delete this element?', + }), + getDeleteElementTitle: (elementName: string) => + i18n.translate('xpack.canvas.savedElementsModal.deleteElementTitle', { + defaultMessage: `Delete element '{elementName}'?`, + values: { + elementName, + }, + }), + getEditElementTitle: () => + i18n.translate('xpack.canvas.savedElementsModal.editElementTitle', { + defaultMessage: 'Edit element', + }), + getElementsTitle: () => + i18n.translate('xpack.canvas.savedElementsModal.elementsTitle', { + defaultMessage: 'Elements', + description: 'Title for the "Elements" tab when adding a new element', + }), + getFindElementPlaceholder: () => + i18n.translate('xpack.canvas.savedElementsModal.findElementPlaceholder', { + defaultMessage: 'Find element', + }), + getModalTitle: () => + i18n.translate('xpack.canvas.savedElementsModal.modalTitle', { + defaultMessage: 'My elements', + }), + getMyElementsTitle: () => + i18n.translate('xpack.canvas.savedElementsModal.myElementsTitle', { + defaultMessage: 'My elements', + description: 'Title for the "My elements" tab when adding a new element', + }), + getSavedElementsModalCloseButtonLabel: () => + i18n.translate('xpack.canvas.workpadHeader.addElementModalCloseButtonLabel', { + defaultMessage: 'Close', + }), + }, ShareWebsiteFlyout: { getRuntimeStepTitle: () => i18n.translate('xpack.canvas.shareWebsiteFlyout.snippetsStep.downloadRuntimeTitle', { @@ -652,7 +660,7 @@ export const ComponentStrings = { defaultMessage: 'Share on a website', }), getUnsupportedRendererWarning: () => - i18n.translate('xpack.canvas.workpadHeaderWorkpadExport.unsupportedRendererWarning', { + i18n.translate('xpack.canvas.workpadHeaderShareMenu.unsupportedRendererWarning', { defaultMessage: 'This workpad contains render functions that are not supported by the {CANVAS} Shareable Workpad Runtime. These elements will not be rendered:', values: { @@ -900,6 +908,10 @@ export const ComponentStrings = { i18n.translate('xpack.canvas.textStylePicker.alignRightOption', { defaultMessage: 'Align right', }), + getFontColorLabel: () => + i18n.translate('xpack.canvas.textStylePicker.fontColorLabel', { + defaultMessage: 'Font Color', + }), getStyleBoldOption: () => i18n.translate('xpack.canvas.textStylePicker.styleBoldOption', { defaultMessage: 'Bold', @@ -912,10 +924,6 @@ export const ComponentStrings = { i18n.translate('xpack.canvas.textStylePicker.styleUnderlineOption', { defaultMessage: 'Underline', }), - getFontColorLabel: () => - i18n.translate('xpack.canvas.textStylePicker.fontColorLabel', { - defaultMessage: 'Font Color', - }), }, TimePicker: { getApplyButtonLabel: () => @@ -962,6 +970,10 @@ export const ComponentStrings = { description: '"stylesheet" refers to the collection of CSS style rules entered by the user.', }), + getBackgroundColorLabel: () => + i18n.translate('xpack.canvas.workpadConfig.backgroundColorLabel', { + defaultMessage: 'Background color', + }), getFlipDimensionAriaLabel: () => i18n.translate('xpack.canvas.workpadConfig.swapDimensionsAriaLabel', { defaultMessage: `Swap the page's width and height`, @@ -1013,10 +1025,6 @@ export const ComponentStrings = { defaultMessage: 'US Letter', description: 'This is referring to the dimensions of U.S. standard letter paper.', }), - getBackgroundColorLabel: () => - i18n.translate('xpack.canvas.workpadConfig.backgroundColorLabel', { - defaultMessage: 'Background color', - }), }, WorkpadCreate: { getWorkpadCreateButtonLabel: () => @@ -1029,14 +1037,6 @@ export const ComponentStrings = { i18n.translate('xpack.canvas.workpadHeader.addElementButtonLabel', { defaultMessage: 'Add element', }), - getAddElementModalCloseButtonLabel: () => - i18n.translate('xpack.canvas.workpadHeader.addElementModalCloseButtonLabel', { - defaultMessage: 'Close', - }), - getEmbedObjectButtonLabel: () => - i18n.translate('xpack.canvas.workpadHeader.embedObjectButtonLabel', { - defaultMessage: 'Embed object', - }), getFullScreenButtonAriaLabel: () => i18n.translate('xpack.canvas.workpadHeader.fullscreenButtonAriaLabel', { defaultMessage: 'View fullscreen', @@ -1080,9 +1080,9 @@ export const ComponentStrings = { }), }, WorkpadHeaderControlSettings: { - getTooltip: () => - i18n.translate('xpack.canvas.workpadHeaderControlSettings.settingsTooltip', { - defaultMessage: 'Control settings', + getButtonLabel: () => + i18n.translate('xpack.canvas.workpadHeaderControlSettings.buttonLabel', { + defaultMessage: 'Options', }), }, WorkpadHeaderCustomInterval: { @@ -1105,6 +1105,56 @@ export const ComponentStrings = { defaultMessage: 'Set a custom interval', }), }, + WorkpadHeaderElementMenu: { + getAssetsMenuItemLabel: () => + i18n.translate('xpack.canvas.workpadHeaderElementMenu.manageAssetsMenuItemLabel', { + defaultMessage: 'Manage assets', + }), + getChartMenuItemLabel: () => + i18n.translate('xpack.canvas.workpadHeaderElementMenu.chartMenuItemLabel', { + defaultMessage: 'Chart', + }), + getElementMenuButtonLabel: () => + i18n.translate('xpack.canvas.workpadHeaderElementMenu.elementMenuButtonLabel', { + defaultMessage: 'Add element', + }), + getElementMenuLabel: () => + i18n.translate('xpack.canvas.workpadHeaderElementMenu.elementMenuLabel', { + defaultMessage: 'Add an element', + }), + getEmbedObjectMenuItemLabel: () => + i18n.translate('xpack.canvas.workpadHeaderElementMenu.embedObjectMenuItemLabel', { + defaultMessage: 'Add from Visualize library', + }), + getFilterMenuItemLabel: () => + i18n.translate('xpack.canvas.workpadHeaderElementMenu.filterMenuItemLabel', { + defaultMessage: 'Filter', + }), + getImageMenuItemLabel: () => + i18n.translate('xpack.canvas.workpadHeaderElementMenu.imageMenuItemLabel', { + defaultMessage: 'Image', + }), + getMyElementsMenuItemLabel: () => + i18n.translate('xpack.canvas.workpadHeaderElementMenu.myElementsMenuItemLabel', { + defaultMessage: 'My elements', + }), + getOtherMenuItemLabel: () => + i18n.translate('xpack.canvas.workpadHeaderElementMenu.otherMenuItemLabel', { + defaultMessage: 'Other', + }), + getProgressMenuItemLabel: () => + i18n.translate('xpack.canvas.workpadHeaderElementMenu.progressMenuItemLabel', { + defaultMessage: 'Progress', + }), + getShapeMenuItemLabel: () => + i18n.translate('xpack.canvas.workpadHeaderElementMenu.shapeMenuItemLabel', { + defaultMessage: 'Shape', + }), + getTextMenuItemLabel: () => + i18n.translate('xpack.canvas.workpadHeaderElementMenu.textMenuItemLabel', { + defaultMessage: 'Text', + }), + }, WorkpadHeaderKioskControls: { getCycleFormLabel: () => i18n.translate('xpack.canvas.workpadHeaderKioskControl.cycleFormLabel', { @@ -1129,9 +1179,9 @@ export const ComponentStrings = { defaultMessage: 'Refresh data', }), }, - WorkpadHeaderWorkpadExport: { + WorkpadHeaderShareMenu: { getCopyPDFMessage: () => - i18n.translate('xpack.canvas.workpadHeaderWorkpadExport.copyPDFMessage', { + i18n.translate('xpack.canvas.workpadHeaderShareMenu.copyPDFMessage', { defaultMessage: 'The {PDF} generation {URL} was copied to your clipboard.', values: { PDF, @@ -1139,15 +1189,15 @@ export const ComponentStrings = { }, }), getCopyReportingConfigMessage: () => - i18n.translate('xpack.canvas.workpadHeaderWorkpadExport.copyReportingConfigMessage', { + i18n.translate('xpack.canvas.workpadHeaderShareMenu.copyReportingConfigMessage', { defaultMessage: 'Copied reporting configuration to clipboard', }), getCopyShareConfigMessage: () => - i18n.translate('xpack.canvas.workpadHeaderWorkpadExport.copyShareConfigMessage', { + i18n.translate('xpack.canvas.workpadHeaderShareMenu.copyShareConfigMessage', { defaultMessage: 'Copied share markup to clipboard', }), getExportPDFErrorTitle: (workpadName: string) => - i18n.translate('xpack.canvas.workpadHeaderWorkpadExport.exportPDFErrorMessage', { + i18n.translate('xpack.canvas.workpadHeaderShareMenu.exportPDFErrorMessage', { defaultMessage: "Failed to create {PDF} for '{workpadName}'", values: { PDF, @@ -1155,14 +1205,14 @@ export const ComponentStrings = { }, }), getExportPDFMessage: () => - i18n.translate('xpack.canvas.workpadHeaderWorkpadExport.exportPDFMessage', { + i18n.translate('xpack.canvas.workpadHeaderShareMenu.exportPDFMessage', { defaultMessage: 'Exporting {PDF}. You can track the progress in Management.', values: { PDF, }, }), getExportPDFTitle: (workpadName: string) => - i18n.translate('xpack.canvas.workpadHeaderWorkpadExport.exportPDFTitle', { + i18n.translate('xpack.canvas.workpadHeaderShareMenu.exportPDFTitle', { defaultMessage: "{PDF} export of workpad '{workpadName}'", values: { PDF, @@ -1170,7 +1220,7 @@ export const ComponentStrings = { }, }), getPDFPanelCopyAriaLabel: () => - i18n.translate('xpack.canvas.workpadHeaderWorkpadExport.pdfPanelCopyAriaLabel', { + i18n.translate('xpack.canvas.workpadHeaderShareMenu.pdfPanelCopyAriaLabel', { defaultMessage: 'Alternatively, you can generate a {PDF} from a script or with Watcher by using this {URL}. Press Enter to copy the {URL} to clipboard.', values: { @@ -1179,7 +1229,7 @@ export const ComponentStrings = { }, }), getPDFPanelCopyButtonLabel: () => - i18n.translate('xpack.canvas.workpadHeaderWorkpadExport.pdfPanelCopyButtonLabel', { + i18n.translate('xpack.canvas.workpadHeaderShareMenu.pdfPanelCopyButtonLabel', { defaultMessage: 'Copy {POST} {URL}', values: { POST, @@ -1187,7 +1237,7 @@ export const ComponentStrings = { }, }), getPDFPanelCopyDescription: () => - i18n.translate('xpack.canvas.workpadHeaderWorkpadExport.pdfPanelCopyDescription', { + i18n.translate('xpack.canvas.workpadHeaderShareMenu.pdfPanelCopyDescription', { defaultMessage: 'Alternatively, copy this {POST} {URL} to call generation from outside {KIBANA} or from Watcher.', values: { @@ -1197,14 +1247,14 @@ export const ComponentStrings = { }, }), getPDFPanelGenerateButtonLabel: () => - i18n.translate('xpack.canvas.workpadHeaderWorkpadExport.pdfPanelGenerateButtonLabel', { + i18n.translate('xpack.canvas.workpadHeaderShareMenu.pdfPanelGenerateButtonLabel', { defaultMessage: 'Generate {PDF}', values: { PDF, }, }), getPDFPanelGenerateDescription: () => - i18n.translate('xpack.canvas.workpadHeaderWorkpadExport.pdfPanelGenerateDescription', { + i18n.translate('xpack.canvas.workpadHeaderShareMenu.pdfPanelGenerateDescription', { defaultMessage: '{PDF}s can take a minute or two to generate based on the size of your workpad.', values: { @@ -1212,7 +1262,7 @@ export const ComponentStrings = { }, }), getShareableZipErrorTitle: (workpadName: string) => - i18n.translate('xpack.canvas.workpadHeaderWorkpadExport.shareWebsiteErrorTitle', { + i18n.translate('xpack.canvas.workpadHeaderShareMenu.shareWebsiteErrorTitle', { defaultMessage: "Failed to create {ZIP} file for '{workpadName}'. The workpad may be too large. You'll need to download the files separately.", values: { @@ -1221,69 +1271,101 @@ export const ComponentStrings = { }, }), getShareDownloadJSONTitle: () => - i18n.translate('xpack.canvas.workpadHeaderWorkpadExport.shareDownloadJSONTitle', { + i18n.translate('xpack.canvas.workpadHeaderShareMenu.shareDownloadJSONTitle', { defaultMessage: 'Download as {JSON}', values: { JSON, }, }), getShareDownloadPDFTitle: () => - i18n.translate('xpack.canvas.workpadHeaderWorkpadExport.shareDownloadPDFTitle', { + i18n.translate('xpack.canvas.workpadHeaderShareMenu.shareDownloadPDFTitle', { defaultMessage: '{PDF} reports', values: { PDF, }, }), + getShareMenuButtonLabel: () => + i18n.translate('xpack.canvas.workpadHeaderShareMenu.shareMenuButtonLabel', { + defaultMessage: 'Share', + }), getShareWebsiteTitle: () => - i18n.translate('xpack.canvas.workpadHeaderWorkpadExport.shareWebsiteTitle', { + i18n.translate('xpack.canvas.workpadHeaderShareMenu.shareWebsiteTitle', { defaultMessage: 'Share on a website', }), getShareWorkpadMessage: () => - i18n.translate('xpack.canvas.workpadHeaderWorkpadExport.shareWorkpadMessage', { + i18n.translate('xpack.canvas.workpadHeaderShareMenu.shareWorkpadMessage', { defaultMessage: 'Share this workpad', }), getUnknownExportErrorMessage: (type: string) => - i18n.translate('xpack.canvas.workpadHeaderWorkpadExport.unknownExportErrorMessage', { + i18n.translate('xpack.canvas.workpadHeaderShareMenu.unknownExportErrorMessage', { defaultMessage: 'Unknown export type: {type}', values: { type, }, }), }, - WorkpadHeaderWorkpadZoom: { + WorkpadHeaderViewMenu: { + getFullscreenMenuItemLabel: () => + i18n.translate('xpack.canvas.workpadHeaderViewMenu.fullscreenMenuLabel', { + defaultMessage: 'Enter fullscreen mode', + }), + getHideEditModeLabel: () => + i18n.translate('xpack.canvas.workpadHeaderViewMenu.hideEditModeLabel', { + defaultMessage: 'Hide editing controls', + }), + getRefreshMenuItemLabel: () => + i18n.translate('xpack.canvas.workpadHeaderViewMenu.refreshMenuItemLabel', { + defaultMessage: 'Refresh data', + }), + getShowEditModeLabel: () => + i18n.translate('xpack.canvas.workpadHeaderViewMenu.showEditModeLabel', { + defaultMessage: 'Show editing controls', + }), + getViewMenuButtonLabel: () => + i18n.translate('xpack.canvas.workpadHeaderViewMenu.viewMenuButtonLabel', { + defaultMessage: 'View', + }), + getViewMenuLabel: () => + i18n.translate('xpack.canvas.workpadHeaderViewMenu.viewMenuLabel', { + defaultMessage: 'View options', + }), getZoomControlsAriaLabel: () => - i18n.translate('xpack.canvas.workpadHeaderWorkpadZoom.zoomControlsAriaLabel', { + i18n.translate('xpack.canvas.workpadHeaderViewMenu.zoomControlsAriaLabel', { defaultMessage: 'Zoom controls', }), getZoomControlsTooltip: () => - i18n.translate('xpack.canvas.workpadHeaderWorkpadZoom.zoomControlsTooltip', { + i18n.translate('xpack.canvas.workpadHeaderViewMenu.zoomControlsTooltip', { defaultMessage: 'Zoom controls', }), getZoomFitToWindowText: () => - i18n.translate('xpack.canvas.workpadHeaderWorkpadZoom.zoomFitToWindowText', { + i18n.translate('xpack.canvas.workpadHeaderViewMenu.zoomFitToWindowText', { defaultMessage: 'Fit to window', }), getZoomInText: () => - i18n.translate('xpack.canvas.workpadHeaderWorkpadZoom.zoomInText', { + i18n.translate('xpack.canvas.workpadHeaderViewMenu.zoomInText', { defaultMessage: 'Zoom in', }), + getZoomMenuItemLabel: () => + i18n.translate('xpack.canvas.workpadHeaderViewMenu.zoomMenuItemLabel', { + defaultMessage: 'Zoom', + }), getZoomOutText: () => - i18n.translate('xpack.canvas.workpadHeaderWorkpadZoom.zoomOutText', { + i18n.translate('xpack.canvas.workpadHeaderViewMenu.zoomOutText', { defaultMessage: 'Zoom out', }), getZoomPanelTitle: () => - i18n.translate('xpack.canvas.workpadHeaderWorkpadZoom.zoomPanelTitle', { + i18n.translate('xpack.canvas.workpadHeaderViewMenu.zoomPanelTitle', { defaultMessage: 'Zoom', }), getZoomPercentage: (scale: number) => - i18n.translate('xpack.canvas.workpadHeaderWorkpadZoom.zoomResetText', { + i18n.translate('xpack.canvas.workpadHeaderViewMenu.zoomResetText', { defaultMessage: '{scalePercentage}%', values: { scalePercentage: scale * 100, }, }), getZoomResetText: () => - i18n.translate('xpack.canvas.workpadHeaderWorkpadZoom.zoomPrecentageValue', { + i18n.translate('xpack.canvas.workpadHeaderViewMenu.zoomPrecentageValue', { defaultMessage: 'Reset', }), }, diff --git a/x-pack/legacy/plugins/canvas/i18n/elements/apply_strings.ts b/x-pack/legacy/plugins/canvas/i18n/elements/apply_strings.ts index 41f88a3c75f90..4464ed5dbf185 100644 --- a/x-pack/legacy/plugins/canvas/i18n/elements/apply_strings.ts +++ b/x-pack/legacy/plugins/canvas/i18n/elements/apply_strings.ts @@ -7,8 +7,6 @@ import { ElementFactory } from '../../types'; import { getElementStrings } from './index'; -import { TagStrings } from '../../i18n'; - /** * This function takes a set of Canvas Element specification factories, runs them, * replaces relevant strings (if available) and returns a new factory. We do this @@ -34,17 +32,6 @@ export const applyElementStrings = (elements: ElementFactory[]) => { if (displayName) { result.displayName = displayName; } - - // Set translated tags - if (result.tags) { - result.tags = result.tags.map(tag => { - if (tag in TagStrings) { - return TagStrings[tag](); - } - - return tag; - }); - } } return () => result; diff --git a/x-pack/legacy/plugins/canvas/i18n/elements/element_strings.test.ts b/x-pack/legacy/plugins/canvas/i18n/elements/element_strings.test.ts index 3d835bdf31bf8..c28229bdab33f 100644 --- a/x-pack/legacy/plugins/canvas/i18n/elements/element_strings.test.ts +++ b/x-pack/legacy/plugins/canvas/i18n/elements/element_strings.test.ts @@ -3,11 +3,11 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -jest.mock('ui/new_platform'); import { getElementStrings } from './element_strings'; -import { elementSpecs } from '../../canvas_plugin_src/elements'; +import { initializeElements } from '../../canvas_plugin_src/elements'; +import { coreMock } from '../../../../../../src/core/public/mocks'; -import { TagStrings } from '../tags'; +const elementSpecs = initializeElements(coreMock.createSetup() as any, {} as any); describe('ElementStrings', () => { const elementStrings = getElementStrings(); @@ -35,15 +35,4 @@ describe('ElementStrings', () => { expect(value).toHaveProperty('help'); }); }); - - test('All elements should have tags that are defined', () => { - const tagNames = Object.keys(TagStrings); - - elementSpecs.forEach(spec => { - const element = spec(); - if (element.tags) { - element.tags.forEach((tagName: string) => expect(tagNames).toContain(tagName)); - } - }); - }); }); diff --git a/x-pack/legacy/plugins/canvas/i18n/elements/element_strings.ts b/x-pack/legacy/plugins/canvas/i18n/elements/element_strings.ts index df23dff1c7127..595ef4cb92206 100644 --- a/x-pack/legacy/plugins/canvas/i18n/elements/element_strings.ts +++ b/x-pack/legacy/plugins/canvas/i18n/elements/element_strings.ts @@ -23,7 +23,7 @@ interface ElementStringDict { export const getElementStrings = (): ElementStringDict => ({ areaChart: { displayName: i18n.translate('xpack.canvas.elements.areaChartDisplayName', { - defaultMessage: 'Area chart', + defaultMessage: 'Area', }), help: i18n.translate('xpack.canvas.elements.areaChartHelpText', { defaultMessage: 'A line chart with a filled body', @@ -31,7 +31,7 @@ export const getElementStrings = (): ElementStringDict => ({ }, bubbleChart: { displayName: i18n.translate('xpack.canvas.elements.bubbleChartDisplayName', { - defaultMessage: 'Bubble chart', + defaultMessage: 'Bubble', }), help: i18n.translate('xpack.canvas.elements.bubbleChartHelpText', { defaultMessage: 'A customizable bubble chart', @@ -39,31 +39,31 @@ export const getElementStrings = (): ElementStringDict => ({ }, debug: { displayName: i18n.translate('xpack.canvas.elements.debugDisplayName', { - defaultMessage: 'Debug', + defaultMessage: 'Debug data', }), help: i18n.translate('xpack.canvas.elements.debugHelpText', { defaultMessage: 'Just dumps the configuration of the element', }), }, - donut: { - displayName: i18n.translate('xpack.canvas.elements.donutChartDisplayName', { - defaultMessage: 'Donut chart', - }), - help: i18n.translate('xpack.canvas.elements.donutChartHelpText', { - defaultMessage: 'A customizable donut chart', - }), - }, - dropdown_filter: { + dropdownFilter: { displayName: i18n.translate('xpack.canvas.elements.dropdownFilterDisplayName', { - defaultMessage: 'Dropdown filter', + defaultMessage: 'Dropdown select', }), help: i18n.translate('xpack.canvas.elements.dropdownFilterHelpText', { defaultMessage: 'A dropdown from which you can select values for an "exactly" filter', }), }, + filterDebug: { + displayName: i18n.translate('xpack.canvas.elements.filterDebugDisplayName', { + defaultMessage: 'Debug filters', + }), + help: i18n.translate('xpack.canvas.elements.filterDebugHelpText', { + defaultMessage: 'Shows the underlying global filters in a workpad', + }), + }, horizontalBarChart: { displayName: i18n.translate('xpack.canvas.elements.horizontalBarChartDisplayName', { - defaultMessage: 'Horizontal bar chart', + defaultMessage: 'Horizontal bar', }), help: i18n.translate('xpack.canvas.elements.horizontalBarChartHelpText', { defaultMessage: 'A customizable horizontal bar chart', @@ -71,7 +71,7 @@ export const getElementStrings = (): ElementStringDict => ({ }, horizontalProgressBar: { displayName: i18n.translate('xpack.canvas.elements.horizontalProgressBarDisplayName', { - defaultMessage: 'Horizontal progress bar', + defaultMessage: 'Horizontal bar', }), help: i18n.translate('xpack.canvas.elements.horizontalProgressBarHelpText', { defaultMessage: 'Displays progress as a portion of a horizontal bar', @@ -79,7 +79,7 @@ export const getElementStrings = (): ElementStringDict => ({ }, horizontalProgressPill: { displayName: i18n.translate('xpack.canvas.elements.horizontalProgressPillDisplayName', { - defaultMessage: 'Horizontal progress pill', + defaultMessage: 'Horizontal pill', }), help: i18n.translate('xpack.canvas.elements.horizontalProgressPillHelpText', { defaultMessage: 'Displays progress as a portion of a horizontal pill', @@ -95,7 +95,7 @@ export const getElementStrings = (): ElementStringDict => ({ }, lineChart: { displayName: i18n.translate('xpack.canvas.elements.lineChartDisplayName', { - defaultMessage: 'Line chart', + defaultMessage: 'Line', }), help: i18n.translate('xpack.canvas.elements.lineChartHelpText', { defaultMessage: 'A customizable line chart', @@ -119,7 +119,7 @@ export const getElementStrings = (): ElementStringDict => ({ }, pie: { displayName: i18n.translate('xpack.canvas.elements.pieDisplayName', { - defaultMessage: 'Pie chart', + defaultMessage: 'Pie', }), help: i18n.translate('xpack.canvas.elements.pieHelpText', { defaultMessage: 'Pie chart', @@ -135,7 +135,7 @@ export const getElementStrings = (): ElementStringDict => ({ }, progressGauge: { displayName: i18n.translate('xpack.canvas.elements.progressGaugeDisplayName', { - defaultMessage: 'Progress gauge', + defaultMessage: 'Gauge', }), help: i18n.translate('xpack.canvas.elements.progressGaugeHelpText', { defaultMessage: 'Displays progress as a portion of a gauge', @@ -143,7 +143,7 @@ export const getElementStrings = (): ElementStringDict => ({ }, progressSemicircle: { displayName: i18n.translate('xpack.canvas.elements.progressSemicircleDisplayName', { - defaultMessage: 'Progress semicircle', + defaultMessage: 'Semicircle', }), help: i18n.translate('xpack.canvas.elements.progressSemicircleHelpText', { defaultMessage: 'Displays progress as a portion of a semicircle', @@ -151,7 +151,7 @@ export const getElementStrings = (): ElementStringDict => ({ }, progressWheel: { displayName: i18n.translate('xpack.canvas.elements.progressWheelDisplayName', { - defaultMessage: 'Progress wheel', + defaultMessage: 'Wheel', }), help: i18n.translate('xpack.canvas.elements.progressWheelHelpText', { defaultMessage: 'Displays progress as a portion of a wheel', @@ -189,15 +189,7 @@ export const getElementStrings = (): ElementStringDict => ({ defaultMessage: 'A scrollable grid for displaying data in a tabular format', }), }, - tiltedPie: { - displayName: i18n.translate('xpack.canvas.elements.tiltedPieDisplayName', { - defaultMessage: 'Tilted pie chart', - }), - help: i18n.translate('xpack.canvas.elements.tiltedPieHelpText', { - defaultMessage: 'A customizable tilted pie chart', - }), - }, - time_filter: { + timeFilter: { displayName: i18n.translate('xpack.canvas.elements.timeFilterDisplayName', { defaultMessage: 'Time filter', }), @@ -207,7 +199,7 @@ export const getElementStrings = (): ElementStringDict => ({ }, verticalBarChart: { displayName: i18n.translate('xpack.canvas.elements.verticalBarChartDisplayName', { - defaultMessage: 'Vertical bar chart', + defaultMessage: 'Vertical bar', }), help: i18n.translate('xpack.canvas.elements.verticalBarChartHelpText', { defaultMessage: 'A customizable vertical bar chart', @@ -215,7 +207,7 @@ export const getElementStrings = (): ElementStringDict => ({ }, verticalProgressBar: { displayName: i18n.translate('xpack.canvas.elements.verticalProgressBarDisplayName', { - defaultMessage: 'Vertical progress bar', + defaultMessage: 'Vertical bar', }), help: i18n.translate('xpack.canvas.elements.verticalProgressBarHelpText', { defaultMessage: 'Displays progress as a portion of a vertical bar', @@ -223,7 +215,7 @@ export const getElementStrings = (): ElementStringDict => ({ }, verticalProgressPill: { displayName: i18n.translate('xpack.canvas.elements.verticalProgressPillDisplayName', { - defaultMessage: 'Vertical progress pill', + defaultMessage: 'Vertical pill', }), help: i18n.translate('xpack.canvas.elements.verticalProgressPillHelpText', { defaultMessage: 'Displays progress as a portion of a vertical pill', diff --git a/x-pack/legacy/plugins/canvas/i18n/functions/dict/alter_column.ts b/x-pack/legacy/plugins/canvas/i18n/functions/dict/alter_column.ts index 836c7395ac448..cc601b0ea0e31 100644 --- a/x-pack/legacy/plugins/canvas/i18n/functions/dict/alter_column.ts +++ b/x-pack/legacy/plugins/canvas/i18n/functions/dict/alter_column.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { alterColumn } from '../../../canvas_plugin_src/functions/common/alterColumn'; import { FunctionHelp } from '../function_help'; import { FunctionFactory } from '../../../types'; -import { DATATABLE_COLUMN_TYPES } from '../../../common/lib'; +import { DATATABLE_COLUMN_TYPES } from '../../../common/lib/constants'; export const help: FunctionHelp> = { help: i18n.translate('xpack.canvas.functions.alterColumnHelpText', { diff --git a/x-pack/legacy/plugins/canvas/i18n/tags.ts b/x-pack/legacy/plugins/canvas/i18n/tags.ts index 41007c58d738d..9595554260297 100644 --- a/x-pack/legacy/plugins/canvas/i18n/tags.ts +++ b/x-pack/legacy/plugins/canvas/i18n/tags.ts @@ -7,32 +7,12 @@ import { i18n } from '@kbn/i18n'; export const TagStrings: { [key: string]: () => string } = { - chart: () => - i18n.translate('xpack.canvas.tags.chartTag', { - defaultMessage: 'chart', - }), - filter: () => - i18n.translate('xpack.canvas.tags.filterTag', { - defaultMessage: 'filter', - }), - graphic: () => - i18n.translate('xpack.canvas.tags.graphicTag', { - defaultMessage: 'graphic', - }), presentation: () => i18n.translate('xpack.canvas.tags.presentationTag', { defaultMessage: 'presentation', }), - proportion: () => - i18n.translate('xpack.canvas.tags.proportionTag', { - defaultMessage: 'proportion', - }), report: () => i18n.translate('xpack.canvas.tags.reportTag', { defaultMessage: 'report', }), - text: () => - i18n.translate('xpack.canvas.tags.textTag', { - defaultMessage: 'text', - }), }; diff --git a/x-pack/legacy/plugins/canvas/public/apps/workpad/workpad_app/workpad_app.scss b/x-pack/legacy/plugins/canvas/public/apps/workpad/workpad_app/workpad_app.scss index c5161439b71c3..c7dae8452a93c 100644 --- a/x-pack/legacy/plugins/canvas/public/apps/workpad/workpad_app/workpad_app.scss +++ b/x-pack/legacy/plugins/canvas/public/apps/workpad/workpad_app/workpad_app.scss @@ -31,7 +31,7 @@ $canvasLayoutFontSize: $euiFontSizeS; .canvasLayout__stageHeader { flex-grow: 0; flex-basis: auto; - padding: ($euiSizeXS + 1px) $euiSize $euiSizeXS; + padding: 1px $euiSize 0; font-size: $canvasLayoutFontSize; border-bottom: $euiBorderThin; background: $euiColorLightestShade; diff --git a/x-pack/legacy/plugins/canvas/public/components/asset_manager/__examples__/__snapshots__/asset.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/asset_manager/__examples__/__snapshots__/asset.examples.storyshot index 5cb64964ee38f..6601f570209e9 100644 --- a/x-pack/legacy/plugins/canvas/public/components/asset_manager/__examples__/__snapshots__/asset.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/public/components/asset_manager/__examples__/__snapshots__/asset.examples.storyshot @@ -18,13 +18,13 @@ exports[`Storyshots components/Assets/Asset airplane 1`] = ` className="canvasAsset__thumb canvasCheckered" >
Asset thumbnail
@@ -192,13 +192,13 @@ exports[`Storyshots components/Assets/Asset marker 1`] = ` className="canvasAsset__thumb canvasCheckered" >
Asset thumbnail
diff --git a/x-pack/legacy/plugins/canvas/public/components/asset_manager/__examples__/__snapshots__/asset_manager.stories.storyshot b/x-pack/legacy/plugins/canvas/public/components/asset_manager/__examples__/__snapshots__/asset_manager.stories.storyshot new file mode 100644 index 0000000000000..aff630b21c770 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/public/components/asset_manager/__examples__/__snapshots__/asset_manager.stories.storyshot @@ -0,0 +1,761 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots components/Assets/AssetManager no assets 1`] = ` +Array [ +
, +
, +
+
+ +
+
+
+ Manage workpad assets +
+
+
+
+
+ +
+ +
+
+
+
+
+
+
+
+
+

+ Below are the image assets in this workpad. Any assets that are currently in use cannot be determined at this time. To reclaim space, delete assets. +

+
+
+
+
+
+
+
+ +

+ Import your assets to get started +

+
+ +
+
+
+
+
+
+
+ +
+
+
+ 0% space used +
+
+
+ +
+
+
+
, +
, +] +`; + +exports[`Storyshots components/Assets/AssetManager two assets 1`] = ` +Array [ +
, +
, +
+
+ +
+
+
+ Manage workpad assets +
+
+
+
+
+ +
+ +
+
+
+
+
+
+
+
+
+

+ Below are the image assets in this workpad. Any assets that are currently in use cannot be determined at this time. To reclaim space, delete assets. +

+
+
+
+
+
+
+
+
+ Asset thumbnail +
+
+
+
+

+ + airplane + +
+ + + ( + 1 + kb) + + +

+
+
+
+
+ + + +
+
+ +
+ +
+
+
+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+
+
+
+ Asset thumbnail +
+
+
+
+

+ + marker + +
+ + + ( + 1 + kb) + + +

+
+
+
+
+ + + +
+
+ +
+ +
+
+
+
+ +
+ +
+
+
+
+ + + +
+
+
+
+
+
+
+
+
+
+ +
+
+
+ 0% space used +
+
+
+ +
+
+
+
, +
, +] +`; diff --git a/x-pack/legacy/plugins/canvas/public/components/asset_manager/__examples__/asset_manager.examples.tsx b/x-pack/legacy/plugins/canvas/public/components/asset_manager/__examples__/asset_manager.stories.tsx similarity index 97% rename from x-pack/legacy/plugins/canvas/public/components/asset_manager/__examples__/asset_manager.examples.tsx rename to x-pack/legacy/plugins/canvas/public/components/asset_manager/__examples__/asset_manager.stories.tsx index 045a3b4f64a44..cb42823ccab7b 100644 --- a/x-pack/legacy/plugins/canvas/public/components/asset_manager/__examples__/asset_manager.examples.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/asset_manager/__examples__/asset_manager.stories.tsx @@ -28,12 +28,12 @@ const MARKER: AssetType = { storiesOf('components/Assets/AssetManager', module) .add('no assets', () => ( - // @ts-ignore @types/react has not been updated to support defaultProps yet. )) .add('two assets', () => ( @@ -43,5 +43,6 @@ storiesOf('components/Assets/AssetManager', module) onAssetAdd={action('onAssetAdd')} onAssetCopy={action('onAssetCopy')} onAssetDelete={action('onAssetDelete')} + onClose={action('onClose')} /> )); diff --git a/x-pack/legacy/plugins/canvas/public/components/asset_manager/asset_manager.tsx b/x-pack/legacy/plugins/canvas/public/components/asset_manager/asset_manager.tsx index 3785f81cc25b9..479e9287d7adf 100644 --- a/x-pack/legacy/plugins/canvas/public/components/asset_manager/asset_manager.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/asset_manager/asset_manager.tsx @@ -4,13 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - EuiButtonEmpty, - // @ts-ignore (elastic/eui#1557) EuiFilePicker is not exported yet - EuiFilePicker, - // @ts-ignore (elastic/eui#1557) EuiImage is not exported yet - EuiImage, -} from '@elastic/eui'; import PropTypes from 'prop-types'; import React, { Fragment, PureComponent } from 'react'; @@ -33,13 +26,13 @@ interface Props { onAssetCopy: () => void; /** Function to invoke when an asset is added */ onAssetAdd: (asset: File) => void; + /** Function to invoke when an asset modal is closed */ + onClose: () => void; } interface State { /** The id of the asset to delete, if applicable. Is set if the viewer clicks the delete icon */ deleteId: string | null; - /** Determines if the modal is currently visible */ - isModalVisible: boolean; /** Indicates if the modal is currently loading */ isLoading: boolean; } @@ -51,6 +44,7 @@ export class AssetManager extends PureComponent { onAssetAdd: PropTypes.func.isRequired, onAssetCopy: PropTypes.func.isRequired, onAssetDelete: PropTypes.func.isRequired, + onClose: PropTypes.func.isRequired, }; public static defaultProps = { @@ -60,12 +54,11 @@ export class AssetManager extends PureComponent { public state = { deleteId: null, isLoading: false, - isModalVisible: false, }; public render() { - const { isModalVisible, isLoading } = this.state; - const { assetValues, onAssetCopy, onAddImageElement } = this.props; + const { isLoading } = this.state; + const { assetValues, onAssetCopy, onAddImageElement, onClose } = this.props; const assetModal = ( { onAssetCopy={onAssetCopy} onAssetCreate={(createdAsset: AssetType) => { onAddImageElement(createdAsset.id); - this.setState({ isModalVisible: false }); + onClose(); }} onAssetDelete={(asset: AssetType) => this.setState({ deleteId: asset.id })} - onClose={() => this.setState({ isModalVisible: false })} + onClose={onClose} onFileUpload={this.handleFileUpload} /> ); @@ -95,14 +88,12 @@ export class AssetManager extends PureComponent { return ( - {strings.getButtonLabel()} - {isModalVisible ? assetModal : null} + {assetModal} {confirmModal} ); } - private showModal = () => this.setState({ isModalVisible: true }); private resetDelete = () => this.setState({ deleteId: null }); private doDelete = () => { diff --git a/x-pack/legacy/plugins/canvas/public/components/asset_manager/asset_modal.tsx b/x-pack/legacy/plugins/canvas/public/components/asset_manager/asset_modal.tsx index 3dfbb1b1fde3c..8637db8e9f962 100644 --- a/x-pack/legacy/plugins/canvas/public/components/asset_manager/asset_modal.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/asset_manager/asset_modal.tsx @@ -98,6 +98,7 @@ export const AssetModal: FunctionComponent = props => { -
-
-
-
- -
-
- - - -
-

- sample description -

-
-
-
-
-
-
- - - -
-
- - - -
-
-
-
-
-
- -
-
- - - -
-

- Aenean eu justo auctor, placerat felis non, scelerisque dolor. -

-
-
-
-
-
-
- - - -
-
- - - -
-
-
-
-
-
- -
-
- - - -
-

- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce lobortis aliquet arcu ut turpis duis. -

-
-
-
-
-
-
- - - -
-
- - - -
-
-
-
-
-`; - -exports[`Storyshots components/Elements/ElementGrid with controls and filter 1`] = ` -
-
-
-
-
- -
-
- - - -
-

- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce lobortis aliquet arcu ut turpis duis. -

-
-
-
-
-
-
- - - -
-
- - - -
-
-
-
-
-`; - -exports[`Storyshots components/Elements/ElementGrid with tags filter 1`] = ` -
-
-
-
-
- -
-
- - - -
-

- A static image -

-
-
-
- - - - graphic - - - -
-
-
-
-
-`; - -exports[`Storyshots components/Elements/ElementGrid with text filter 1`] = ` -
-
-
-
-
- -
-
- - - -
-

- A scrollable grid for displaying data in a tabular format -

-
-
-
- - - - text - - - -
-
-
-
-
-`; - -exports[`Storyshots components/Elements/ElementGrid without controls 1`] = ` -
-
-
-
-
- -
-
- - - -
-

- A line chart with a filled body -

-
-
-
- - - - chart - - - -
-
-
-
-
-
- -
-
- - - -
-

- A static image -

-
-
-
- - - - graphic - - - -
-
-
-
-
-
- -
-
- - - -
-

- A scrollable grid for displaying data in a tabular format -

-
-
-
- - - - text - - - -
-
-
-
-
-`; diff --git a/x-pack/legacy/plugins/canvas/public/components/element_types/element_grid.tsx b/x-pack/legacy/plugins/canvas/public/components/element_types/element_grid.tsx deleted file mode 100644 index 852b987fcfd24..0000000000000 --- a/x-pack/legacy/plugins/canvas/public/components/element_types/element_grid.tsx +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import React from 'react'; -import PropTypes from 'prop-types'; -import { map } from 'lodash'; -import { EuiFlexItem, EuiFlexGrid } from '@elastic/eui'; -import { ElementControls } from './element_controls'; -import { CustomElement, ElementSpec } from '../../../types'; -import { ElementCard } from '../element_card'; - -export interface Props { - /** - * list of elements to generate cards for - */ - elements: Array; - /** - * text to filter out cards - */ - filterText: string; - /** - * tags to filter out cards - */ - filterTags: string[]; - /** - * indicate whether or not edit/delete controls should be displayed - */ - showControls: boolean; - /** - * handler invoked when clicking a card - */ - handleClick: (element: ElementSpec | CustomElement) => void; - /** - * click handler for the edit button - */ - onEdit?: (element: ElementSpec | CustomElement) => void; - /** - * click handler for the delete button - */ - onDelete?: (element: ElementSpec | CustomElement) => void; -} - -export const ElementGrid = ({ - elements, - filterText, - filterTags, - handleClick, - onEdit, - onDelete, - showControls, -}: Props) => { - filterText = filterText.toLowerCase(); - - return ( - - {map(elements, (element: ElementSpec | CustomElement, index) => { - const { name, displayName = '', help = '', image, tags = [] } = element; - const whenClicked = () => handleClick(element); - let textMatch = false; - let tagsMatch = false; - - if ( - !filterText.length || - name.toLowerCase().includes(filterText) || - displayName.toLowerCase().includes(filterText) || - help.toLowerCase().includes(filterText) - ) { - textMatch = true; - } - - if (!filterTags.length || filterTags.every(tag => tags.includes(tag))) { - tagsMatch = true; - } - - if (!textMatch || !tagsMatch) { - return null; - } - - return ( - - - {showControls && onEdit && onDelete && ( - onEdit(element)} onDelete={() => onDelete(element)} /> - )} - - ); - })} - - ); -}; - -ElementGrid.propTypes = { - elements: PropTypes.array.isRequired, - handleClick: PropTypes.func.isRequired, - showControls: PropTypes.bool, -}; - -ElementGrid.defaultProps = { - showControls: false, - filterTags: [], - filterText: '', -}; diff --git a/x-pack/legacy/plugins/canvas/public/components/element_types/element_types.js b/x-pack/legacy/plugins/canvas/public/components/element_types/element_types.js deleted file mode 100644 index dabf06a24aeb6..0000000000000 --- a/x-pack/legacy/plugins/canvas/public/components/element_types/element_types.js +++ /dev/null @@ -1,224 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { Component, Fragment } from 'react'; -import PropTypes from 'prop-types'; -import { - EuiModalBody, - EuiTabbedContent, - EuiEmptyPrompt, - EuiSearchBar, - EuiSpacer, - EuiOverlayMask, -} from '@elastic/eui'; -import { map, sortBy } from 'lodash'; -import { ConfirmModal } from '../confirm_modal/confirm_modal'; -import { CustomElementModal } from '../custom_element_modal'; -import { getTagsFilter } from '../../lib/get_tags_filter'; -import { extractSearch } from '../../lib/extract_search'; -import { ComponentStrings } from '../../../i18n'; -import { ElementGrid } from './element_grid'; - -const { ElementTypes: strings } = ComponentStrings; - -const tagType = 'badge'; -export class ElementTypes extends Component { - static propTypes = { - addCustomElement: PropTypes.func.isRequired, - addElement: PropTypes.func.isRequired, - customElements: PropTypes.array.isRequired, - elements: PropTypes.object, - filterTags: PropTypes.arrayOf(PropTypes.string).isRequired, - findCustomElements: PropTypes.func.isRequired, - onClose: PropTypes.func.isRequired, - removeCustomElement: PropTypes.func.isRequired, - search: PropTypes.string, - setCustomElements: PropTypes.func.isRequired, - setSearch: PropTypes.func.isRequired, - setFilterTags: PropTypes.func.isRequired, - updateCustomElement: PropTypes.func.isRequired, - }; - - state = { - elementToDelete: null, - elementToEdit: null, - }; - - componentDidMount() { - // fetch custom elements - this.props.findCustomElements(); - } - - _showEditModal = elementToEdit => this.setState({ elementToEdit }); - - _hideEditModal = () => this.setState({ elementToEdit: null }); - - _handleEdit = async (name, description, image) => { - const { updateCustomElement } = this.props; - const { elementToEdit } = this.state; - await updateCustomElement(elementToEdit.id, name, description, image); - this._hideEditModal(); - }; - - _showDeleteModal = elementToDelete => this.setState({ elementToDelete }); - - _hideDeleteModal = () => this.setState({ elementToDelete: null }); - - _handleDelete = async () => { - const { removeCustomElement } = this.props; - const { elementToDelete } = this.state; - await removeCustomElement(elementToDelete.id); - this._hideDeleteModal(); - }; - - _renderEditModal = () => { - const { elementToEdit } = this.state; - - if (!elementToEdit) { - return null; - } - - return ( - - - - ); - }; - - _renderDeleteModal = () => { - const { elementToDelete } = this.state; - - if (!elementToDelete) { - return null; - } - - return ( - - ); - }; - - _sortElements = elements => - sortBy( - map(elements, (element, name) => ({ name, ...element })), - 'displayName' - ); - - render() { - const { - search, - setSearch, - addElement, - addCustomElement, - filterTags, - setFilterTags, - } = this.props; - let { elements, customElements } = this.props; - elements = this._sortElements(elements); - - let customElementContent = ( - {strings.getAddNewElementTitle()}} - body={

{strings.getAddNewElementDescription()}

} - titleSize="s" - /> - ); - - if (customElements.length) { - customElements = this._sortElements(customElements); - customElementContent = ( - - ); - } - - const filters = [getTagsFilter(tagType)]; - const onSearch = ({ queryText }) => { - const { searchTerm, filterTags } = extractSearch(queryText); - setSearch(searchTerm); - setFilterTags(filterTags); - }; - - const tabs = [ - { - id: 'elements', - name: strings.getElementsTitle(), - content: ( -
- - - - -
- ), - }, - { - id: 'customElements', - name: strings.getMyElementsTitle(), - content: ( - - - - - {customElementContent} - - ), - }, - ]; - - return ( - - - - - - {this._renderDeleteModal()} - {this._renderEditModal()} - - ); - } -} diff --git a/x-pack/legacy/plugins/canvas/public/components/element_types/index.js b/x-pack/legacy/plugins/canvas/public/components/element_types/index.js deleted file mode 100644 index 8faaf278a07de..0000000000000 --- a/x-pack/legacy/plugins/canvas/public/components/element_types/index.js +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import PropTypes from 'prop-types'; -import { compose, withProps, withState } from 'recompose'; -import { connect } from 'react-redux'; -import { camelCase } from 'lodash'; -import { cloneSubgraphs } from '../../lib/clone_subgraphs'; -import * as customElementService from '../../lib/custom_element_service'; -import { elementsRegistry } from '../../lib/elements_registry'; -import { notify } from '../../lib/notify'; -import { selectToplevelNodes } from '../../state/actions/transient'; -import { insertNodes, addElement } from '../../state/actions/elements'; -import { getSelectedPage } from '../../state/selectors/workpad'; -import { trackCanvasUiMetric, METRIC_TYPE } from '../../lib/ui_metric'; -import { ElementTypes as Component } from './element_types'; - -const customElementAdded = 'elements-custom-added'; - -const mapStateToProps = state => ({ pageId: getSelectedPage(state) }); - -const mapDispatchToProps = dispatch => ({ - selectToplevelNodes: nodes => - dispatch(selectToplevelNodes(nodes.filter(e => !e.position.parent).map(e => e.id))), - insertNodes: (selectedNodes, pageId) => dispatch(insertNodes(selectedNodes, pageId)), - addElement: (pageId, partialElement) => dispatch(addElement(pageId, partialElement)), -}); - -const mergeProps = (stateProps, dispatchProps, ownProps) => { - const { pageId, ...remainingStateProps } = stateProps; - const { addElement, insertNodes, selectToplevelNodes } = dispatchProps; - const { search, setCustomElements, onClose } = ownProps; - - return { - ...remainingStateProps, - ...ownProps, - // add built-in element to the page - addElement: element => { - addElement(pageId, element); - onClose(); - }, - // add custom element to the page - addCustomElement: customElement => { - const { selectedNodes = [] } = JSON.parse(customElement.content) || {}; - const clonedNodes = selectedNodes && cloneSubgraphs(selectedNodes); - if (clonedNodes) { - insertNodes(clonedNodes, pageId); // first clone and persist the new node(s) - selectToplevelNodes(clonedNodes); // then select the cloned node(s) - } - onClose(); - trackCanvasUiMetric(METRIC_TYPE.LOADED, customElementAdded); - }, - // custom element search - findCustomElements: async text => { - try { - const { customElements } = await customElementService.find(text); - setCustomElements(customElements); - } catch (err) { - notify.error(err, { title: `Couldn't find custom elements` }); - } - }, - // remove custom element - removeCustomElement: async id => { - try { - await customElementService.remove(id).then(); - const { customElements } = await customElementService.find(search); - setCustomElements(customElements); - } catch (err) { - notify.error(err, { title: `Couldn't delete custom elements` }); - } - }, - // update custom element - updateCustomElement: async (id, name, description, image) => { - try { - await customElementService.update(id, { - name: camelCase(name), - displayName: name, - image, - help: description, - }); - const { customElements } = await customElementService.find(search); - setCustomElements(customElements); - } catch (err) { - notify.error(err, { title: `Couldn't update custom elements` }); - } - }, - }; -}; - -export const ElementTypes = compose( - withState('search', 'setSearch', ''), - withState('customElements', 'setCustomElements', []), - withState('filterTags', 'setFilterTags', []), - withProps(() => ({ elements: elementsRegistry.toJS() })), - connect(mapStateToProps, mapDispatchToProps, mergeProps) -)(Component); - -ElementTypes.propTypes = { - onClose: PropTypes.func, -}; diff --git a/x-pack/legacy/plugins/canvas/public/components/popover/index.ts b/x-pack/legacy/plugins/canvas/public/components/popover/index.ts index f560da14079b5..63626f08fa43b 100644 --- a/x-pack/legacy/plugins/canvas/public/components/popover/index.ts +++ b/x-pack/legacy/plugins/canvas/public/components/popover/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { Popover } from './popover'; +export { Popover, ClosePopoverFn } from './popover'; diff --git a/x-pack/legacy/plugins/canvas/public/components/popover/popover.tsx b/x-pack/legacy/plugins/canvas/public/components/popover/popover.tsx index 25b2e6587c869..9f3d86576e6a7 100644 --- a/x-pack/legacy/plugins/canvas/public/components/popover/popover.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/popover/popover.tsx @@ -24,6 +24,8 @@ interface Props { className?: string; } +export type ClosePopoverFn = () => void; + interface State { isPopoverOpen: boolean; } @@ -61,7 +63,7 @@ export class Popover extends Component { })); }; - closePopover = () => { + closePopover: ClosePopoverFn = () => { this.setState({ isPopoverOpen: false, }); diff --git a/x-pack/legacy/plugins/canvas/public/components/element_types/__examples__/__snapshots__/element_controls.stories.storyshot b/x-pack/legacy/plugins/canvas/public/components/saved_elements_modal/__examples__/__snapshots__/element_controls.stories.storyshot similarity index 88% rename from x-pack/legacy/plugins/canvas/public/components/element_types/__examples__/__snapshots__/element_controls.stories.storyshot rename to x-pack/legacy/plugins/canvas/public/components/saved_elements_modal/__examples__/__snapshots__/element_controls.stories.storyshot index 5ce6fe8c85589..6f12f68356467 100644 --- a/x-pack/legacy/plugins/canvas/public/components/element_types/__examples__/__snapshots__/element_controls.stories.storyshot +++ b/x-pack/legacy/plugins/canvas/public/components/saved_elements_modal/__examples__/__snapshots__/element_controls.stories.storyshot @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Storyshots components/Elements/ElementControls has two buttons 1`] = ` +exports[`Storyshots components/SavedElementsModal/ElementControls has two buttons 1`] = `
+
+
+
+
+ +
+
+ + + +
+

+ sample description +

+
+
+
+
+
+
+ + + +
+
+ + + +
+
+
+
+
+
+ +
+
+ + + +
+

+ Aenean eu justo auctor, placerat felis non, scelerisque dolor. +

+
+
+
+
+
+
+ + + +
+
+ + + +
+
+
+
+
+
+ +
+
+ + + +
+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce lobortis aliquet arcu ut turpis duis. +

+
+
+
+
+
+
+ + + +
+
+ + + +
+
+
+
+
+`; + +exports[`Storyshots components/SavedElementsModal/ElementGrid with text filter 1`] = ` +
+
+
+`; diff --git a/x-pack/legacy/plugins/canvas/public/components/saved_elements_modal/__examples__/__snapshots__/saved_elements_modal.stories.storyshot b/x-pack/legacy/plugins/canvas/public/components/saved_elements_modal/__examples__/__snapshots__/saved_elements_modal.stories.storyshot new file mode 100644 index 0000000000000..c73309ad8b14c --- /dev/null +++ b/x-pack/legacy/plugins/canvas/public/components/saved_elements_modal/__examples__/__snapshots__/saved_elements_modal.stories.storyshot @@ -0,0 +1,933 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots components/SavedElementsModal no custom elements 1`] = ` +Array [ +
, +
, +
+
+ +
+
+
+ My elements +
+
+
+
+
+
+ +
+ + +
+
+
+
+
+
+ +

+ Add new elements +

+
+
+

+ Group and save workpad elements to create new elements +

+
+ +
+
+
+
+ +
+
+
+
, +
, +] +`; + +exports[`Storyshots components/SavedElementsModal with custom elements 1`] = ` +Array [ +
, +
, +
+
+ +
+
+
+ My elements +
+
+
+
+
+
+ +
+ + +
+
+
+
+
+
+
+ +
+
+ + + +
+

+ sample description +

+
+
+
+
+
+
+ + + +
+
+ + + +
+
+
+
+
+
+ +
+
+ + + +
+

+ Aenean eu justo auctor, placerat felis non, scelerisque dolor. +

+
+
+
+
+
+
+ + + +
+
+ + + +
+
+
+
+
+
+ +
+
+ + + +
+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce lobortis aliquet arcu ut turpis duis. +

+
+
+
+
+
+
+ + + +
+
+ + + +
+
+
+
+
+
+
+ +
+
+
+
, +
, +] +`; + +exports[`Storyshots components/SavedElementsModal with text filter 1`] = ` +Array [ +
, +
, +
+
+ +
+
+
+ My elements +
+
+
+
+
+
+ +
+ + +
+ +
+
+
+
+
+
+
+
+ +
+
+ + + +
+

+ Aenean eu justo auctor, placerat felis non, scelerisque dolor. +

+
+
+
+
+
+
+ + + +
+
+ + + +
+
+
+
+
+
+
+ +
+
+
+
, +
, +] +`; diff --git a/x-pack/legacy/plugins/canvas/public/components/element_types/__examples__/element_controls.stories.tsx b/x-pack/legacy/plugins/canvas/public/components/saved_elements_modal/__examples__/element_controls.stories.tsx similarity index 90% rename from x-pack/legacy/plugins/canvas/public/components/element_types/__examples__/element_controls.stories.tsx rename to x-pack/legacy/plugins/canvas/public/components/saved_elements_modal/__examples__/element_controls.stories.tsx index 52736ac952e53..5210210ebaa74 100644 --- a/x-pack/legacy/plugins/canvas/public/components/element_types/__examples__/element_controls.stories.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/saved_elements_modal/__examples__/element_controls.stories.tsx @@ -9,7 +9,7 @@ import { storiesOf } from '@storybook/react'; import { action } from '@storybook/addon-actions'; import { ElementControls } from '../element_controls'; -storiesOf('components/Elements/ElementControls', module) +storiesOf('components/SavedElementsModal/ElementControls', module) .addDecorator(story => (
(
)) - .add('without controls', () => ( - - )) - .add('with controls', () => ( + .add('default', () => ( )) .add('with text filter', () => ( - - )) - .add('with tags filter', () => ( - - )) - .add('with controls and filter', () => ( diff --git a/x-pack/legacy/plugins/canvas/public/components/element_types/__examples__/fixtures/test_elements.tsx b/x-pack/legacy/plugins/canvas/public/components/saved_elements_modal/__examples__/fixtures/test_elements.tsx similarity index 93% rename from x-pack/legacy/plugins/canvas/public/components/element_types/__examples__/fixtures/test_elements.tsx rename to x-pack/legacy/plugins/canvas/public/components/saved_elements_modal/__examples__/fixtures/test_elements.tsx index eec7a86d52f25..d1ff565b4955a 100644 --- a/x-pack/legacy/plugins/canvas/public/components/element_types/__examples__/fixtures/test_elements.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/saved_elements_modal/__examples__/fixtures/test_elements.tsx @@ -6,41 +6,6 @@ import { elasticLogo } from '../../../../lib/elastic_logo'; -export const testElements = [ - { - name: 'areaChart', - displayName: 'Area chart', - help: 'A line chart with a filled body', - tags: ['chart'], - image: elasticLogo, - expression: `filters - | demodata - | pointseries x="time" y="mean(price)" - | plot defaultStyle={seriesStyle lines=1 fill=1} - | render`, - }, - { - name: 'image', - displayName: 'Image', - help: 'A static image', - tags: ['graphic'], - image: elasticLogo, - expression: `image dataurl=null mode="contain" - | render`, - }, - { - name: 'table', - displayName: 'Data table', - tags: ['text'], - help: 'A scrollable grid for displaying data in a tabular format', - image: elasticLogo, - expression: `filters - | demodata - | table - | render`, - }, -]; - export const testCustomElements = [ { id: 'custom-element-10d625f5-1342-47c9-8f19-d174ea6b65d5', diff --git a/x-pack/legacy/plugins/canvas/public/components/saved_elements_modal/__examples__/saved_elements_modal.stories.tsx b/x-pack/legacy/plugins/canvas/public/components/saved_elements_modal/__examples__/saved_elements_modal.stories.tsx new file mode 100644 index 0000000000000..4941d8cb2efa7 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/public/components/saved_elements_modal/__examples__/saved_elements_modal.stories.tsx @@ -0,0 +1,50 @@ +/* + * 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 { storiesOf } from '@storybook/react'; +import { action } from '@storybook/addon-actions'; +import { SavedElementsModal } from '../saved_elements_modal'; +import { testCustomElements } from './fixtures/test_elements'; +import { CustomElement } from '../../../../types'; + +storiesOf('components/SavedElementsModal', module) + .add('no custom elements', () => ( + + )) + .add('with custom elements', () => ( + + )) + .add('with text filter', () => ( + + )); diff --git a/x-pack/legacy/plugins/canvas/public/components/element_types/element_controls.tsx b/x-pack/legacy/plugins/canvas/public/components/saved_elements_modal/element_controls.tsx similarity index 85% rename from x-pack/legacy/plugins/canvas/public/components/element_types/element_controls.tsx rename to x-pack/legacy/plugins/canvas/public/components/saved_elements_modal/element_controls.tsx index a23274296f64f..998b15c15f487 100644 --- a/x-pack/legacy/plugins/canvas/public/components/element_types/element_controls.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/saved_elements_modal/element_controls.tsx @@ -30,7 +30,12 @@ export const ElementControls: FunctionComponent = ({ onDelete, onEdit }) > - + @@ -40,6 +45,7 @@ export const ElementControls: FunctionComponent = ({ onDelete, onEdit }) iconType="trash" aria-label={strings.getDeleteAriaLabel()} onClick={onDelete} + data-test-subj="canvasElementCard__deleteButton" /> diff --git a/x-pack/legacy/plugins/canvas/public/components/saved_elements_modal/element_grid.tsx b/x-pack/legacy/plugins/canvas/public/components/saved_elements_modal/element_grid.tsx new file mode 100644 index 0000000000000..f86e2c0147035 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/public/components/saved_elements_modal/element_grid.tsx @@ -0,0 +1,81 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import PropTypes from 'prop-types'; +import { map } from 'lodash'; +import { EuiFlexItem, EuiFlexGrid } from '@elastic/eui'; +import { ElementControls } from './element_controls'; +import { CustomElement } from '../../../types'; +import { ElementCard } from '../element_card'; + +export interface Props { + /** + * list of elements to generate cards for + */ + elements: CustomElement[]; + /** + * text to filter out cards + */ + filterText: string; + /** + * handler invoked when clicking a card + */ + onClick: (element: CustomElement) => void; + /** + * click handler for the edit button + */ + onEdit: (element: CustomElement) => void; + /** + * click handler for the delete button + */ + onDelete: (element: CustomElement) => void; +} + +export const ElementGrid = ({ elements, filterText, onClick, onEdit, onDelete }: Props) => { + filterText = filterText.toLowerCase(); + + return ( + + {map(elements, (element: CustomElement, index) => { + const { name, displayName = '', help = '', image } = element; + const whenClicked = () => onClick(element); + + if ( + filterText.length && + !name.toLowerCase().includes(filterText) && + !displayName.toLowerCase().includes(filterText) && + !help.toLowerCase().includes(filterText) + ) { + return null; + } + + return ( + + + onEdit(element)} onDelete={() => onDelete(element)} /> + + ); + })} + + ); +}; + +ElementGrid.propTypes = { + elements: PropTypes.array.isRequired, + filterText: PropTypes.string.isRequired, + onClick: PropTypes.func.isRequired, + onEdit: PropTypes.func.isRequired, + onDelete: PropTypes.func.isRequired, +}; + +ElementGrid.defaultProps = { + filterText: '', +}; diff --git a/x-pack/legacy/plugins/canvas/public/components/saved_elements_modal/index.ts b/x-pack/legacy/plugins/canvas/public/components/saved_elements_modal/index.ts new file mode 100644 index 0000000000000..bb088ad4e0de1 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/public/components/saved_elements_modal/index.ts @@ -0,0 +1,128 @@ +/* + * 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 { connect } from 'react-redux'; +import { Dispatch } from 'redux'; +import { compose, withState } from 'recompose'; +import { camelCase } from 'lodash'; +// @ts-ignore Untyped local +import { cloneSubgraphs } from '../../lib/clone_subgraphs'; +import * as customElementService from '../../lib/custom_element_service'; +// @ts-ignore Untyped local +import { notify } from '../../lib/notify'; +// @ts-ignore Untyped local +import { selectToplevelNodes } from '../../state/actions/transient'; +// @ts-ignore Untyped local +import { insertNodes } from '../../state/actions/elements'; +import { getSelectedPage } from '../../state/selectors/workpad'; +import { trackCanvasUiMetric, METRIC_TYPE } from '../../lib/ui_metric'; +import { SavedElementsModal as Component, Props as ComponentProps } from './saved_elements_modal'; +import { State, PositionedElement, CustomElement } from '../../../types'; + +const customElementAdded = 'elements-custom-added'; + +interface OwnProps { + onClose: () => void; +} + +interface OwnPropsWithState extends OwnProps { + customElements: CustomElement[]; + setCustomElements: (customElements: CustomElement[]) => void; + search: string; + setSearch: (search: string) => void; +} + +interface DispatchProps { + selectToplevelNodes: (nodes: PositionedElement[]) => void; + insertNodes: (selectedNodes: PositionedElement[], pageId: string) => void; +} + +interface StateProps { + pageId: string; +} + +const mapStateToProps = (state: State): StateProps => ({ + pageId: getSelectedPage(state), +}); + +const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => ({ + selectToplevelNodes: (nodes: PositionedElement[]) => + dispatch( + selectToplevelNodes( + nodes + .filter((e: PositionedElement): boolean => !e.position.parent) + .map((e: PositionedElement): string => e.id) + ) + ), + insertNodes: (selectedNodes: PositionedElement[], pageId: string) => + dispatch(insertNodes(selectedNodes, pageId)), +}); + +const mergeProps = ( + stateProps: StateProps, + dispatchProps: DispatchProps, + ownProps: OwnPropsWithState +): ComponentProps => { + const { pageId } = stateProps; + const { onClose, search, setCustomElements } = ownProps; + + const findCustomElements = async () => { + const { customElements } = await customElementService.find(search); + setCustomElements(customElements); + }; + + return { + ...ownProps, + // add custom element to the page + addCustomElement: (customElement: CustomElement) => { + const { selectedNodes = [] } = JSON.parse(customElement.content) || {}; + const clonedNodes = selectedNodes && cloneSubgraphs(selectedNodes); + if (clonedNodes) { + dispatchProps.insertNodes(clonedNodes, pageId); // first clone and persist the new node(s) + dispatchProps.selectToplevelNodes(clonedNodes); // then select the cloned node(s) + } + onClose(); + trackCanvasUiMetric(METRIC_TYPE.LOADED, customElementAdded); + }, + // custom element search + findCustomElements: async (text?: string) => { + try { + await findCustomElements(); + } catch (err) { + notify.error(err, { title: `Couldn't find custom elements` }); + } + }, + // remove custom element + removeCustomElement: async (id: string) => { + try { + await customElementService.remove(id); + await findCustomElements(); + } catch (err) { + notify.error(err, { title: `Couldn't delete custom elements` }); + } + }, + // update custom element + updateCustomElement: async (id: string, name: string, description: string, image: string) => { + try { + await customElementService.update(id, { + name: camelCase(name), + displayName: name, + image, + help: description, + }); + await findCustomElements(); + } catch (err) { + notify.error(err, { title: `Couldn't update custom elements` }); + } + }, + }; +}; + +export const SavedElementsModal = compose( + withState('search', 'setSearch', ''), + withState('customElements', 'setCustomElements', []), + connect(mapStateToProps, mapDispatchToProps, mergeProps) +)(Component); diff --git a/x-pack/legacy/plugins/canvas/public/components/saved_elements_modal/saved_elements_modal.tsx b/x-pack/legacy/plugins/canvas/public/components/saved_elements_modal/saved_elements_modal.tsx new file mode 100644 index 0000000000000..dba97a15fee5c --- /dev/null +++ b/x-pack/legacy/plugins/canvas/public/components/saved_elements_modal/saved_elements_modal.tsx @@ -0,0 +1,217 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { Fragment, ChangeEvent, FunctionComponent, useState, useEffect } from 'react'; +import PropTypes from 'prop-types'; +import { + EuiModal, + EuiModalBody, + EuiModalFooter, + EuiModalHeader, + EuiModalHeaderTitle, + EuiEmptyPrompt, + EuiFieldSearch, + EuiSpacer, + EuiOverlayMask, + EuiButton, +} from '@elastic/eui'; +import { map, sortBy } from 'lodash'; +import { ComponentStrings } from '../../../i18n'; +import { CustomElement } from '../../../types'; +import { ConfirmModal } from '../confirm_modal/confirm_modal'; +import { CustomElementModal } from '../custom_element_modal'; +import { ElementGrid } from './element_grid'; + +const { SavedElementsModal: strings } = ComponentStrings; + +export interface Props { + /** + * Adds the custom element to the workpad + */ + addCustomElement: (customElement: CustomElement) => void; + /** + * Queries ES for custom element saved objects + */ + findCustomElements: () => void; + /** + * Handler invoked when the modal closes + */ + onClose: () => void; + /** + * Deletes the custom element + */ + removeCustomElement: (id: string) => void; + /** + * Saved edits to the custom element + */ + updateCustomElement: (id: string, name: string, description: string, image: string) => void; + /** + * Array of custom elements to display + */ + customElements: CustomElement[]; + /** + * Text used to filter custom elements list + */ + search: string; + /** + * Setter for search text + */ + setSearch: (search: string) => void; +} + +export const SavedElementsModal: FunctionComponent = ({ + search, + setSearch, + customElements, + addCustomElement, + findCustomElements, + onClose, + removeCustomElement, + updateCustomElement, +}) => { + const [elementToDelete, setElementToDelete] = useState(null); + const [elementToEdit, setElementToEdit] = useState(null); + + useEffect(() => { + findCustomElements(); + }); + + const showEditModal = (element: CustomElement) => setElementToEdit(element); + const hideEditModal = () => setElementToEdit(null); + + const handleEdit = async (name: string, description: string, image: string) => { + if (elementToEdit) { + await updateCustomElement(elementToEdit.id, name, description, image); + } + hideEditModal(); + }; + + const showDeleteModal = (element: CustomElement) => setElementToDelete(element); + const hideDeleteModal = () => setElementToDelete(null); + + const handleDelete = async () => { + if (elementToDelete) { + await removeCustomElement(elementToDelete.id); + } + hideDeleteModal(); + }; + + const renderEditModal = () => { + if (!elementToEdit) { + return null; + } + + return ( + + + + ); + }; + + const renderDeleteModal = () => { + if (!elementToDelete) { + return null; + } + + return ( + + ); + }; + + const sortElements = (elements: CustomElement[]): CustomElement[] => + sortBy( + map(elements, (element, name) => ({ name, ...element })), + 'displayName' + ); + + const onSearch = (e: ChangeEvent) => setSearch(e.target.value); + + let customElementContent = ( + {strings.getAddNewElementTitle()}} + body={

{strings.getAddNewElementDescription()}

} + titleSize="s" + /> + ); + + if (customElements.length) { + customElementContent = ( + + ); + } + + return ( + + + + + + {strings.getModalTitle()} + + + + + + + {customElementContent} + + + + {strings.getSavedElementsModalCloseButtonLabel()} + + + + + + {renderDeleteModal()} + {renderEditModal()} + + ); +}; + +SavedElementsModal.propTypes = { + addCustomElement: PropTypes.func.isRequired, + findCustomElements: PropTypes.func.isRequired, + onClose: PropTypes.func.isRequired, + removeCustomElement: PropTypes.func.isRequired, + updateCustomElement: PropTypes.func.isRequired, +}; diff --git a/x-pack/legacy/plugins/canvas/public/components/sidebar_header/__examples__/__snapshots__/sidebar_header.stories.storyshot b/x-pack/legacy/plugins/canvas/public/components/sidebar_header/__examples__/__snapshots__/sidebar_header.stories.storyshot index d46a509251d35..ac25cbe0b6832 100644 --- a/x-pack/legacy/plugins/canvas/public/components/sidebar_header/__examples__/__snapshots__/sidebar_header.stories.storyshot +++ b/x-pack/legacy/plugins/canvas/public/components/sidebar_header/__examples__/__snapshots__/sidebar_header.stories.storyshot @@ -37,6 +37,7 @@ exports[`Storyshots components/Sidebar/SidebarHeader default 1`] = ` + +
+
+ + + +
+
+ + + +
+
+ + + +
@@ -134,6 +235,7 @@ exports[`Storyshots components/Sidebar/SidebarHeader without layer controls 1`] +
+
+`; diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/element_menu/__examples__/element_menu.examples.tsx b/x-pack/legacy/plugins/canvas/public/components/workpad_header/element_menu/__examples__/element_menu.examples.tsx new file mode 100644 index 0000000000000..9aca5ce33ba02 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/element_menu/__examples__/element_menu.examples.tsx @@ -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 { storiesOf } from '@storybook/react'; +import { action } from '@storybook/addon-actions'; +import React from 'react'; +import { ElementSpec } from '../../../../../types'; +import { ElementMenu } from '../element_menu'; + +const testElements: { [key: string]: ElementSpec } = { + areaChart: { + name: 'areaChart', + displayName: 'Area chart', + help: 'A line chart with a filled body', + type: 'chart', + expression: `filters + | demodata + | pointseries x="time" y="mean(price)" + | plot defaultStyle={seriesStyle lines=1 fill=1} + | render`, + }, + debug: { + name: 'debug', + displayName: 'Debug data', + help: 'Just dumps the configuration of the element', + icon: 'bug', + expression: `demodata + | render as=debug`, + }, + dropdownFilter: { + name: 'dropdownFilter', + displayName: 'Dropdown select', + type: 'filter', + help: 'A dropdown from which you can select values for an "exactly" filter', + icon: 'filter', + height: 50, + expression: `demodata + | dropdownControl valueColumn=project filterColumn=project | render`, + filter: '', + }, + filterDebug: { + name: 'filterDebug', + displayName: 'Debug filter', + help: 'Shows the underlying global filters in a workpad', + icon: 'bug', + expression: `filters + | render as=debug`, + }, + image: { + name: 'image', + displayName: 'Image', + help: 'A static image', + type: 'image', + expression: `image dataurl=null mode="contain" + | render`, + }, + markdown: { + name: 'markdown', + displayName: 'Text', + type: 'text', + help: 'Add text using Markdown', + icon: 'visText', + expression: `filters +| demodata +| markdown "### Welcome to the Markdown element + +Good news! You're already connected to some demo data! + +The data table contains +**{{rows.length}} rows**, each containing +the following columns: +{{#each columns}} +**{{name}}** +{{/each}} + +You can use standard Markdown in here, but you can also access your piped-in data using Handlebars. If you want to know more, check out the [Handlebars documentation](https://handlebarsjs.com/guide/expressions.html). + +#### Enjoy!" | render`, + }, + progressGauge: { + name: 'progressGauge', + displayName: 'Gauge', + type: 'progress', + help: 'Displays progress as a portion of a gauge', + width: 200, + height: 200, + icon: 'visGoal', + expression: `filters + | demodata + | math "mean(percent_uptime)" + | progress shape="gauge" label={formatnumber 0%} font={font size=24 family="Helvetica" color="#000000" align=center} + | render`, + }, + revealImage: { + name: 'revealImage', + displayName: 'Image reveal', + type: 'image', + help: 'Reveals a percentage of an image', + expression: `filters + | demodata + | math "mean(percent_uptime)" + | revealImage origin=bottom image=null + | render`, + }, + shape: { + name: 'shape', + displayName: 'Shape', + type: 'shape', + help: 'A customizable shape', + width: 200, + height: 200, + icon: 'node', + expression: + 'shape "square" fill="#4cbce4" border="rgba(255,255,255,0)" borderWidth=0 maintainAspect=false | render', + }, + table: { + name: 'table', + displayName: 'Data table', + type: 'chart', + help: 'A scrollable grid for displaying data in a tabular format', + expression: `filters + | demodata + | table + | render`, + }, + timeFilter: { + name: 'timeFilter', + displayName: 'Time filter', + type: 'filter', + help: 'Set a time window', + icon: 'calendar', + height: 50, + expression: `timefilterControl compact=true column=@timestamp + | render`, + filter: 'timefilter column=@timestamp from=now-24h to=now', + }, +}; + +const mockRenderEmbedPanel = () =>
; + +storiesOf('components/WorkpadHeader/ElementMenu', module).add('default', () => ( + +)); diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/element_menu/element_menu.scss b/x-pack/legacy/plugins/canvas/public/components/workpad_header/element_menu/element_menu.scss new file mode 100644 index 0000000000000..a946ee5519ce4 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/element_menu/element_menu.scss @@ -0,0 +1,3 @@ +.canvasElementMenu__popoverButton { + margin-right: $euiSizeS; +} \ No newline at end of file diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/element_menu/element_menu.tsx b/x-pack/legacy/plugins/canvas/public/components/workpad_header/element_menu/element_menu.tsx new file mode 100644 index 0000000000000..a416adfe77469 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/element_menu/element_menu.tsx @@ -0,0 +1,216 @@ +/* + * 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 { sortBy } from 'lodash'; +import React, { Fragment, FunctionComponent, useState } from 'react'; +import PropTypes from 'prop-types'; +import { + EuiButton, + EuiContextMenu, + EuiIcon, + EuiContextMenuPanelItemDescriptor, +} from '@elastic/eui'; +import { CONTEXT_MENU_TOP_BORDER_CLASSNAME } from '../../../../common/lib'; +import { ComponentStrings } from '../../../../i18n/components'; +import { ElementSpec } from '../../../../types'; +import { flattenPanelTree } from '../../../lib/flatten_panel_tree'; +import { getId } from '../../../lib/get_id'; +import { Popover, ClosePopoverFn } from '../../popover'; +// @ts-ignore Untyped local +import { AssetManager } from '../../asset_manager'; +import { SavedElementsModal } from '../../saved_elements_modal'; + +interface CategorizedElementLists { + [key: string]: ElementSpec[]; +} + +interface ElementTypeMeta { + [key: string]: { name: string; icon: string }; +} + +export const { WorkpadHeaderElementMenu: strings } = ComponentStrings; + +// label and icon for the context menu item for each element type +const elementTypeMeta: ElementTypeMeta = { + chart: { name: strings.getChartMenuItemLabel(), icon: 'visArea' }, + filter: { name: strings.getFilterMenuItemLabel(), icon: 'filter' }, + image: { name: strings.getImageMenuItemLabel(), icon: 'image' }, + other: { name: strings.getOtherMenuItemLabel(), icon: 'empty' }, + progress: { name: strings.getProgressMenuItemLabel(), icon: 'visGoal' }, + shape: { name: strings.getShapeMenuItemLabel(), icon: 'node' }, + text: { name: strings.getTextMenuItemLabel(), icon: 'visText' }, +}; + +const getElementType = (element: ElementSpec): string => + element && element.type && Object.keys(elementTypeMeta).includes(element.type) + ? element.type + : 'other'; + +const categorizeElementsByType = (elements: ElementSpec[]): { [key: string]: ElementSpec[] } => { + elements = sortBy(elements, 'displayName'); + + const categories: CategorizedElementLists = { other: [] }; + + elements.forEach((element: ElementSpec) => { + const type = getElementType(element); + + if (categories[type]) { + categories[type].push(element); + } else { + categories[type] = [element]; + } + }); + + return categories; +}; + +export interface Props { + /** + * Dictionary of elements from elements registry + */ + elements: { [key: string]: ElementSpec }; + /** + * Handler for adding a selected element to the workpad + */ + addElement: (element: ElementSpec) => void; + /** + * Renders embeddable flyout + */ + renderEmbedPanel: (onClose: () => void) => JSX.Element; +} + +export const ElementMenu: FunctionComponent = ({ + elements, + addElement, + renderEmbedPanel, +}) => { + const [isAssetModalVisible, setAssetModalVisible] = useState(false); + const [isEmbedPanelVisible, setEmbedPanelVisible] = useState(false); + const [isSavedElementsModalVisible, setSavedElementsModalVisible] = useState(false); + + const hideAssetModal = () => setAssetModalVisible(false); + const showAssetModal = () => setAssetModalVisible(true); + const showEmbedPanel = () => setEmbedPanelVisible(false); + const hideEmbedPanel = () => setEmbedPanelVisible(false); + const hideSavedElementsModal = () => setSavedElementsModalVisible(false); + const showSavedElementsModal = () => setSavedElementsModalVisible(true); + + const { + chart: chartElements, + filter: filterElements, + image: imageElements, + other: otherElements, + progress: progressElements, + shape: shapeElements, + text: textElements, + } = categorizeElementsByType(Object.values(elements)); + + const getPanelTree = (closePopover: ClosePopoverFn) => { + const elementToMenuItem = (element: ElementSpec): EuiContextMenuPanelItemDescriptor => ({ + name: element.displayName || element.name, + icon: element.icon, + onClick: () => { + addElement(element); + closePopover(); + }, + }); + + const elementListToMenuItems = (elementList: ElementSpec[]) => { + const type = getElementType(elementList[0]); + const { name, icon } = elementTypeMeta[type] || elementTypeMeta.other; + + if (elementList.length > 1) { + return { + name, + icon: , + panel: { + id: getId('element-type'), + title: name, + items: elementList.map(elementToMenuItem), + }, + }; + } + + return elementToMenuItem(elementList[0]); + }; + + return { + id: 0, + title: strings.getElementMenuLabel(), + items: [ + elementListToMenuItems(textElements), + elementListToMenuItems(shapeElements), + elementListToMenuItems(chartElements), + elementListToMenuItems(imageElements), + elementListToMenuItems(filterElements), + elementListToMenuItems(progressElements), + elementListToMenuItems(otherElements), + { + name: strings.getMyElementsMenuItemLabel(), + className: CONTEXT_MENU_TOP_BORDER_CLASSNAME, + 'data-test-subj': 'saved-elements-menu-option', + icon: , + onClick: () => { + showSavedElementsModal(); + closePopover(); + }, + }, + { + name: strings.getAssetsMenuItemLabel(), + icon: , + onClick: () => { + showAssetModal(); + closePopover(); + }, + }, + { + name: strings.getEmbedObjectMenuItemLabel(), + className: CONTEXT_MENU_TOP_BORDER_CLASSNAME, + icon: , + onClick: () => { + showEmbedPanel(); + closePopover(); + }, + }, + ], + }; + }; + + const exportControl = (togglePopover: React.MouseEventHandler) => ( + + {strings.getElementMenuButtonLabel()} + + ); + + return ( + + + {({ closePopover }: { closePopover: ClosePopoverFn }) => ( + + )} + + {isAssetModalVisible ? : null} + {isEmbedPanelVisible ? renderEmbedPanel(hideEmbedPanel) : null} + {isSavedElementsModalVisible ? : null} + + ); +}; + +ElementMenu.propTypes = { + elements: PropTypes.object, + addElement: PropTypes.func.isRequired, +}; diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/element_menu/index.tsx b/x-pack/legacy/plugins/canvas/public/components/workpad_header/element_menu/index.tsx new file mode 100644 index 0000000000000..40571a9341f69 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/element_menu/index.tsx @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { connect } from 'react-redux'; +import { compose, withProps } from 'recompose'; +import { Dispatch } from 'redux'; +import { withKibana } from '../../../../../../../../src/plugins/kibana_react/public/'; +import { State, ElementSpec } from '../../../../types'; +// @ts-ignore Untyped local +import { elementsRegistry } from '../../../lib/elements_registry'; +import { ElementMenu as Component, Props as ComponentProps } from './element_menu'; +// @ts-ignore Untyped local +import { addElement } from '../../../state/actions/elements'; +import { getSelectedPage } from '../../../state/selectors/workpad'; +import { AddEmbeddablePanel } from '../../embeddable_flyout'; + +interface StateProps { + pageId: string; +} + +interface DispatchProps { + addElement: (pageId: string) => (partialElement: ElementSpec) => void; +} + +const mapStateToProps = (state: State) => ({ + pageId: getSelectedPage(state), +}); + +const mapDispatchToProps = (dispatch: Dispatch) => ({ + addElement: (pageId: string) => (element: ElementSpec) => dispatch(addElement(pageId, element)), +}); + +const mergeProps = (stateProps: StateProps, dispatchProps: DispatchProps) => ({ + ...stateProps, + ...dispatchProps, + addElement: dispatchProps.addElement(stateProps.pageId), + // Moved this section out of the main component to enable stories + renderEmbedPanel: (onClose: () => void) => , +}); + +export const ElementMenu = compose( + connect(mapStateToProps, mapDispatchToProps, mergeProps), + withKibana, + withProps(() => ({ elements: elementsRegistry.toJS() })) +)(Component); diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/__examples__/__snapshots__/pdf_panel.stories.storyshot b/x-pack/legacy/plugins/canvas/public/components/workpad_header/share_menu/__examples__/__snapshots__/pdf_panel.stories.storyshot similarity index 93% rename from x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/__examples__/__snapshots__/pdf_panel.stories.storyshot rename to x-pack/legacy/plugins/canvas/public/components/workpad_header/share_menu/__examples__/__snapshots__/pdf_panel.stories.storyshot index 43adcb37c5f4c..9ad2714a78ec9 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/__examples__/__snapshots__/pdf_panel.stories.storyshot +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/share_menu/__examples__/__snapshots__/pdf_panel.stories.storyshot @@ -1,11 +1,11 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Storyshots components/Export/PDFPanel default 1`] = ` +exports[`Storyshots components/WorkpadHeader/ShareMenu/PDFPanel default 1`] = `
+
+
+ +
+
+
+`; diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/__examples__/pdf_panel.stories.tsx b/x-pack/legacy/plugins/canvas/public/components/workpad_header/share_menu/__examples__/pdf_panel.stories.tsx similarity index 92% rename from x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/__examples__/pdf_panel.stories.tsx rename to x-pack/legacy/plugins/canvas/public/components/workpad_header/share_menu/__examples__/pdf_panel.stories.tsx index 76a40f51148a7..eb99dbc494a32 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/__examples__/pdf_panel.stories.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/share_menu/__examples__/pdf_panel.stories.tsx @@ -8,7 +8,7 @@ import { action } from '@storybook/addon-actions'; import React from 'react'; import { PDFPanel } from '../pdf_panel'; -storiesOf('components/Export/PDFPanel', module) +storiesOf('components/WorkpadHeader/ShareMenu/PDFPanel', module) .addParameters({ info: { inline: true, diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/__examples__/workpad_export.examples.tsx b/x-pack/legacy/plugins/canvas/public/components/workpad_header/share_menu/__examples__/share_menu.examples.tsx similarity index 79% rename from x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/__examples__/workpad_export.examples.tsx rename to x-pack/legacy/plugins/canvas/public/components/workpad_header/share_menu/__examples__/share_menu.examples.tsx index 92e7cca40ee3a..ab9137b1676c9 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/__examples__/workpad_export.examples.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/share_menu/__examples__/share_menu.examples.tsx @@ -6,10 +6,10 @@ import { storiesOf } from '@storybook/react'; import { action } from '@storybook/addon-actions'; import React from 'react'; -import { WorkpadExport } from '../workpad_export'; +import { ShareMenu } from '../share_menu'; -storiesOf('components/Export/WorkpadExport', module).add('enabled', () => ( - ( + { diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/flyout/__examples__/share_website_flyout.stories.tsx b/x-pack/legacy/plugins/canvas/public/components/workpad_header/share_menu/flyout/__examples__/share_website_flyout.stories.tsx similarity index 93% rename from x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/flyout/__examples__/share_website_flyout.stories.tsx rename to x-pack/legacy/plugins/canvas/public/components/workpad_header/share_menu/flyout/__examples__/share_website_flyout.stories.tsx index af30d8d4fc20b..886ddcfd763e1 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/flyout/__examples__/share_website_flyout.stories.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/share_menu/flyout/__examples__/share_website_flyout.stories.tsx @@ -8,7 +8,7 @@ import { action } from '@storybook/addon-actions'; import React from 'react'; import { ShareWebsiteFlyout } from '../share_website_flyout'; -storiesOf('components/Export/ShareWebsiteFlyout', module) +storiesOf('components/WorkpadHeader/ShareMenu/ShareWebsiteFlyout', module) .addParameters({ info: { inline: true, diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/flyout/index.ts b/x-pack/legacy/plugins/canvas/public/components/workpad_header/share_menu/flyout/index.ts similarity index 91% rename from x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/flyout/index.ts rename to x-pack/legacy/plugins/canvas/public/components/workpad_header/share_menu/flyout/index.ts index 2bf3e1f0ef1f4..6ab419656a7ee 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/flyout/index.ts +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/share_menu/flyout/index.ts @@ -6,7 +6,6 @@ import { connect } from 'react-redux'; import { compose, withProps } from 'recompose'; -// @ts-ignore Untyped local import { getWorkpad, getRenderedWorkpad, @@ -14,26 +13,23 @@ import { } from '../../../../state/selectors/workpad'; // @ts-ignore Untyped local import { notify } from '../../../../lib/notify'; -// @ts-ignore Untyped local import { downloadRenderedWorkpad, downloadRuntime, downloadZippedRuntime, - // @ts-ignore Untyped local } from '../../../../lib/download_workpad'; import { ShareWebsiteFlyout as Component, Props as ComponentProps } from './share_website_flyout'; import { State, CanvasWorkpad } from '../../../../../types'; import { CanvasRenderedWorkpad } from '../../../../../shareable_runtime/types'; -// @ts-ignore Untyped local. -import { fetch, arrayBufferFetch } from '../../../../../common/lib/fetch'; +import { arrayBufferFetch } from '../../../../../common/lib/fetch'; import { API_ROUTE_SHAREABLE_ZIP } from '../../../../../common/lib/constants'; import { renderFunctionNames } from '../../../../../shareable_runtime/supported_renderers'; import { ComponentStrings } from '../../../../../i18n/components'; import { withKibana } from '../../../../../../../../../src/plugins/kibana_react/public/'; -import { OnCloseFn } from '../workpad_export'; +import { OnCloseFn } from '../share_menu'; import { WithKibanaProps } from '../../../../index'; -const { WorkpadHeaderWorkpadExport: strings } = ComponentStrings; +const { WorkpadHeaderShareMenu: strings } = ComponentStrings; const getUnsupportedRenderers = (state: State) => { const renderers: string[] = []; diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/flyout/runtime_step.tsx b/x-pack/legacy/plugins/canvas/public/components/workpad_header/share_menu/flyout/runtime_step.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/flyout/runtime_step.tsx rename to x-pack/legacy/plugins/canvas/public/components/workpad_header/share_menu/flyout/runtime_step.tsx diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/flyout/share_website_flyout.tsx b/x-pack/legacy/plugins/canvas/public/components/workpad_header/share_menu/flyout/share_website_flyout.tsx similarity index 98% rename from x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/flyout/share_website_flyout.tsx rename to x-pack/legacy/plugins/canvas/public/components/workpad_header/share_menu/flyout/share_website_flyout.tsx index 8dcbb18ffed86..5fd381baa73f5 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/flyout/share_website_flyout.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/share_menu/flyout/share_website_flyout.tsx @@ -24,7 +24,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { ComponentStrings } from '../../../../../i18n/components'; import { ZIP, CANVAS, HTML } from '../../../../../i18n/constants'; -import { OnCloseFn } from '../workpad_export'; +import { OnCloseFn } from '../share_menu'; import { WorkpadStep } from './workpad_step'; import { RuntimeStep } from './runtime_step'; import { SnippetsStep } from './snippets_step'; diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/flyout/snippets_step.tsx b/x-pack/legacy/plugins/canvas/public/components/workpad_header/share_menu/flyout/snippets_step.tsx similarity index 98% rename from x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/flyout/snippets_step.tsx rename to x-pack/legacy/plugins/canvas/public/components/workpad_header/share_menu/flyout/snippets_step.tsx index c19ad6d77b131..81f559651eb25 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/flyout/snippets_step.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/share_menu/flyout/snippets_step.tsx @@ -42,7 +42,7 @@ export const SnippetsStep: FC<{ onCopy: OnCopyFn }> = ({ onCopy }) => ( ({ workpad: getWorkpad(state), @@ -50,7 +43,7 @@ interface Props { pageCount: number; } -export const WorkpadExport = compose( +export const ShareMenu = compose( connect(mapStateToProps), withKibana, withProps( diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/pdf_panel.tsx b/x-pack/legacy/plugins/canvas/public/components/workpad_header/share_menu/pdf_panel.tsx similarity index 92% rename from x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/pdf_panel.tsx rename to x-pack/legacy/plugins/canvas/public/components/workpad_header/share_menu/pdf_panel.tsx index ef70079cf697b..a178964e0b566 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/pdf_panel.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/share_menu/pdf_panel.tsx @@ -9,7 +9,7 @@ import { EuiButton, EuiSpacer, EuiText } from '@elastic/eui'; import { Clipboard } from '../../clipboard'; import { ComponentStrings } from '../../../../i18n/components'; -const { WorkpadHeaderWorkpadExport: strings } = ComponentStrings; +const { WorkpadHeaderShareMenu: strings } = ComponentStrings; interface Props { /** The URL that will invoke PDF Report generation. */ @@ -24,7 +24,7 @@ interface Props { * A panel displayed in the Export Menu with options in which to generate PDF Reports. */ export const PDFPanel = ({ pdfURL, onExport, onCopy }: Props) => ( -
+

{strings.getPDFPanelGenerateDescription()}

diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/workpad_export.scss b/x-pack/legacy/plugins/canvas/public/components/workpad_header/share_menu/share_menu.scss similarity index 61% rename from x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/workpad_export.scss rename to x-pack/legacy/plugins/canvas/public/components/workpad_header/share_menu/share_menu.scss index 44209aaa72d63..03227f77e0de5 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/workpad_export.scss +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/share_menu/share_menu.scss @@ -1,8 +1,8 @@ -.canvasWorkpadExport__panelContent { +.canvasShareMenu__panelContent { padding: $euiSize; } -.canvasWorkpadExport__reportingConfig { +.canvasShareMenu__reportingConfig { .euiCodeBlock__pre { @include euiScrollBar; overflow-x: auto; diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/workpad_export.tsx b/x-pack/legacy/plugins/canvas/public/components/workpad_header/share_menu/share_menu.tsx similarity index 71% rename from x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/workpad_export.tsx rename to x-pack/legacy/plugins/canvas/public/components/workpad_header/share_menu/share_menu.tsx index 522be043ec457..621077c29c368 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/workpad_export.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/share_menu/share_menu.tsx @@ -6,16 +6,14 @@ import React, { FunctionComponent, useState } from 'react'; import PropTypes from 'prop-types'; -import { EuiButtonIcon, EuiContextMenu, EuiIcon } from '@elastic/eui'; -// @ts-ignore Untyped local -import { Popover } from '../../popover'; +import { EuiButtonEmpty, EuiContextMenu, EuiIcon } from '@elastic/eui'; +import { ComponentStrings } from '../../../../i18n/components'; +import { flattenPanelTree } from '../../../lib/flatten_panel_tree'; +import { Popover, ClosePopoverFn } from '../../popover'; import { PDFPanel } from './pdf_panel'; import { ShareWebsiteFlyout } from './flyout'; -import { ComponentStrings } from '../../../../i18n/components'; -const { WorkpadHeaderWorkpadExport: strings } = ComponentStrings; - -type ClosePopoverFn = () => void; +const { WorkpadHeaderShareMenu: strings } = ComponentStrings; type CopyTypes = 'pdf' | 'reportingConfig'; type ExportTypes = 'pdf' | 'json'; @@ -39,31 +37,13 @@ export interface Props { /** * The Menu for Exporting a Workpad from Canvas. */ -export const WorkpadExport: FunctionComponent = ({ onCopy, onExport, getExportUrl }) => { +export const ShareMenu: FunctionComponent = ({ onCopy, onExport, getExportUrl }) => { const [showFlyout, setShowFlyout] = useState(false); const onClose = () => { setShowFlyout(false); }; - // TODO: Fix all of this magic from EUI; this code is boilerplate from - // EUI examples and isn't easily typed. - const flattenPanelTree = (tree: any, array: any[] = []) => { - array.push(tree); - - if (tree.items) { - tree.items.forEach((item: any) => { - const { panel } = item; - if (panel) { - flattenPanelTree(panel, array); - item.panel = panel.id; - } - }); - } - - return array; - }; - const getPDFPanel = (closePopover: ClosePopoverFn) => { return ( = ({ onCopy, onExport, getE ], }); - const exportControl = (togglePopover: React.MouseEventHandler) => ( - + const shareControl = (togglePopover: React.MouseEventHandler) => ( + + {strings.getShareMenuButtonLabel()} + ); const flyout = showFlyout ? : null; return (
- + {({ closePopover }: { closePopover: ClosePopoverFn }) => ( = ({ onCopy, onExport, getE ); }; -WorkpadExport.propTypes = { +ShareMenu.propTypes = { onCopy: PropTypes.func.isRequired, onExport: PropTypes.func.isRequired, getExportUrl: PropTypes.func.isRequired, diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/utils.test.ts b/x-pack/legacy/plugins/canvas/public/components/workpad_header/share_menu/utils.test.ts similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/utils.test.ts rename to x-pack/legacy/plugins/canvas/public/components/workpad_header/share_menu/utils.test.ts diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/utils.ts b/x-pack/legacy/plugins/canvas/public/components/workpad_header/share_menu/utils.ts similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/utils.ts rename to x-pack/legacy/plugins/canvas/public/components/workpad_header/share_menu/utils.ts diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/view_menu/__examples__/__snapshots__/view_menu.stories.storyshot b/x-pack/legacy/plugins/canvas/public/components/workpad_header/view_menu/__examples__/__snapshots__/view_menu.stories.storyshot new file mode 100644 index 0000000000000..e1ecee0e152be --- /dev/null +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/view_menu/__examples__/__snapshots__/view_menu.stories.storyshot @@ -0,0 +1,67 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots components/WorkpadHeader/ViewMenu edit mode 1`] = ` +
+
+ +
+
+`; + +exports[`Storyshots components/WorkpadHeader/ViewMenu read only mode 1`] = ` +
+
+ +
+
+`; diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/view_menu/__examples__/view_menu.stories.tsx b/x-pack/legacy/plugins/canvas/public/components/workpad_header/view_menu/__examples__/view_menu.stories.tsx new file mode 100644 index 0000000000000..60837ac1218e6 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/view_menu/__examples__/view_menu.stories.tsx @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { storiesOf } from '@storybook/react'; +import { action } from '@storybook/addon-actions'; +import React from 'react'; +import { ViewMenu } from '../view_menu'; + +storiesOf('components/WorkpadHeader/ViewMenu', module) + .add('edit mode', () => ( + + )) + .add('read only mode', () => ( + + )); diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/view_menu/index.ts b/x-pack/legacy/plugins/canvas/public/components/workpad_header/view_menu/index.ts new file mode 100644 index 0000000000000..c5aa8278ecf55 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/view_menu/index.ts @@ -0,0 +1,85 @@ +/* + * 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 { connect } from 'react-redux'; +import { compose, withHandlers } from 'recompose'; +import { Dispatch } from 'redux'; +import { withKibana } from '../../../../../../../../src/plugins/kibana_react/public/'; +import { zoomHandlerCreators } from '../../../lib/app_handler_creators'; +// @ts-ignore Untyped local +import { notify } from '../../../lib/notify'; +import { State, CanvasWorkpadBoundingBox } from '../../../../types'; +// @ts-ignore Untyped local +import { fetchAllRenderables } from '../../../state/actions/elements'; +// @ts-ignore Untyped local +import { setZoomScale, setFullscreen, selectToplevelNodes } from '../../../state/actions/transient'; +// @ts-ignore Untyped local +import { setWriteable } from '../../../state/actions/workpad'; +import { getZoomScale, canUserWrite } from '../../../state/selectors/app'; +import { + getWorkpadBoundingBox, + getWorkpadWidth, + getWorkpadHeight, + isWriteable, +} from '../../../state/selectors/workpad'; +import { ViewMenu as Component, Props as ComponentProps } from './view_menu'; +import { getFitZoomScale } from './lib/get_fit_zoom_scale'; + +interface StateProps { + zoomScale: number; + boundingBox: CanvasWorkpadBoundingBox; + workpadWidth: number; + workpadHeight: number; + isWriteable: boolean; +} + +interface DispatchProps { + setWriteable: (isWorkpadWriteable: boolean) => void; + setZoomScale: (scale: number) => void; + setFullscreen: (showFullscreen: boolean) => void; +} + +const mapStateToProps = (state: State) => ({ + zoomScale: getZoomScale(state), + boundingBox: getWorkpadBoundingBox(state), + workpadWidth: getWorkpadWidth(state), + workpadHeight: getWorkpadHeight(state), + isWriteable: isWriteable(state) && canUserWrite(state), +}); + +const mapDispatchToProps = (dispatch: Dispatch) => ({ + setZoomScale: (scale: number) => dispatch(setZoomScale(scale)), + setWriteable: (isWorkpadWriteable: boolean) => dispatch(setWriteable(isWorkpadWriteable)), + setFullscreen: (value: boolean) => { + dispatch(setFullscreen(value)); + if (value) { + dispatch(selectToplevelNodes([])); + } + }, + doRefresh: () => dispatch(fetchAllRenderables()), +}); + +const mergeProps = ( + stateProps: StateProps, + dispatchProps: DispatchProps, + ownProps: ComponentProps +): ComponentProps => { + const { boundingBox, workpadWidth, workpadHeight, ...remainingStateProps } = stateProps; + return { + ...remainingStateProps, + ...dispatchProps, + ...ownProps, + toggleWriteable: () => dispatchProps.setWriteable(!stateProps.isWriteable), + enterFullscreen: () => dispatchProps.setFullscreen(true), + fitToWindow: () => getFitZoomScale(boundingBox, workpadWidth, workpadHeight), + }; +}; + +export const ViewMenu = compose( + connect(mapStateToProps, mapDispatchToProps, mergeProps), + withKibana, + withHandlers(zoomHandlerCreators) +)(Component); diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/view_menu/lib/get_fit_zoom_scale.ts b/x-pack/legacy/plugins/canvas/public/components/workpad_header/view_menu/lib/get_fit_zoom_scale.ts new file mode 100644 index 0000000000000..783d6340c33c4 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/view_menu/lib/get_fit_zoom_scale.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + CANVAS_LAYOUT_STAGE_CONTENT_SELECTOR, + WORKPAD_CANVAS_BUFFER, +} from '../../../../../common/lib'; +import { CanvasWorkpadBoundingBox } from '../../../../../types'; + +export const getFitZoomScale = ( + boundingBox: CanvasWorkpadBoundingBox, + workpadWidth: number, + workpadHeight: number +) => { + const canvasLayoutContent = document.querySelector( + `#${CANVAS_LAYOUT_STAGE_CONTENT_SELECTOR}` + ) as HTMLElement; + const layoutWidth = canvasLayoutContent.clientWidth; + const layoutHeight = canvasLayoutContent.clientHeight; + const offsetLeft = boundingBox.left; + const offsetTop = boundingBox.top; + const offsetRight = boundingBox.right - workpadWidth; + const offsetBottom = boundingBox.bottom - workpadHeight; + const boundingWidth = + workpadWidth + + Math.max(Math.abs(offsetLeft), Math.abs(offsetRight)) * 2 + + WORKPAD_CANVAS_BUFFER; + const boundingHeight = + workpadHeight + + Math.max(Math.abs(offsetTop), Math.abs(offsetBottom)) * 2 + + WORKPAD_CANVAS_BUFFER; + const xScale = layoutWidth / boundingWidth; + const yScale = layoutHeight / boundingHeight; + + return Math.min(xScale, yScale); +}; diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/view_menu/view_menu.tsx b/x-pack/legacy/plugins/canvas/public/components/workpad_header/view_menu/view_menu.tsx new file mode 100644 index 0000000000000..d1e08c5809579 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/view_menu/view_menu.tsx @@ -0,0 +1,172 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FunctionComponent } from 'react'; +import PropTypes from 'prop-types'; +import { + EuiButtonEmpty, + EuiContextMenu, + EuiIcon, + EuiContextMenuPanelItemDescriptor, +} from '@elastic/eui'; +import { MAX_ZOOM_LEVEL, MIN_ZOOM_LEVEL } from '../../../../common/lib/constants'; +import { ComponentStrings } from '../../../../i18n/components'; +import { flattenPanelTree } from '../../../lib/flatten_panel_tree'; +import { Popover, ClosePopoverFn } from '../../popover'; + +const { WorkpadHeaderViewMenu: strings } = ComponentStrings; + +const QUICK_ZOOM_LEVELS = [0.5, 1, 2]; + +export interface Props { + /** + * Is the workpad edittable? + */ + isWriteable: boolean; + /** + * current workpad zoom level + */ + zoomScale: number; + /** + * zooms to fit entire workpad into view + */ + fitToWindow: () => void; + /** + * handler to set the workpad zoom level to a specific value + */ + setZoomScale: (scale: number) => void; + /** + * handler to increase the workpad zoom level + */ + zoomIn: () => void; + /** + * handler to decrease workpad zoom level + */ + zoomOut: () => void; + /** + * reset zoom to 100% + */ + resetZoom: () => void; + /** + * toggle edit/read only mode + */ + toggleWriteable: () => void; + /** + * enter fullscreen mode + */ + enterFullscreen: () => void; + /** + * triggers a refresh of the workpad + */ + doRefresh: () => void; +} + +export const ViewMenu: FunctionComponent = ({ + doRefresh, + enterFullscreen, + fitToWindow, + isWriteable, + resetZoom, + setZoomScale, + toggleWriteable, + zoomIn, + zoomOut, + zoomScale, +}) => { + const viewControl = (togglePopover: React.MouseEventHandler) => ( + + {strings.getViewMenuButtonLabel()} + + ); + + const getScaleMenuItems = (): EuiContextMenuPanelItemDescriptor[] => + QUICK_ZOOM_LEVELS.map((scale: number) => ({ + name: strings.getZoomPercentage(scale), + icon: 'empty', + onClick: () => setZoomScale(scale), + })); + + const getZoomMenuItems = (): EuiContextMenuPanelItemDescriptor[] => [ + { + name: strings.getZoomFitToWindowText(), + icon: 'empty', + onClick: fitToWindow, + disabled: zoomScale === MAX_ZOOM_LEVEL, + }, + ...getScaleMenuItems(), + { + name: strings.getZoomInText(), + icon: 'magnifyWithPlus', + onClick: zoomIn, + disabled: zoomScale === MAX_ZOOM_LEVEL, + className: 'canvasContextMenu--topBorder', + }, + { + name: strings.getZoomOutText(), + icon: 'magnifyWithMinus', + onClick: zoomOut, + disabled: zoomScale <= MIN_ZOOM_LEVEL, + }, + { + name: strings.getZoomResetText(), + icon: 'empty', + onClick: resetZoom, + disabled: zoomScale >= MAX_ZOOM_LEVEL, + className: 'canvasContextMenu--topBorder', + }, + ]; + + const getPanelTree = (closePopover: ClosePopoverFn) => ({ + id: 0, + title: strings.getViewMenuLabel(), + items: [ + { + name: strings.getFullscreenMenuItemLabel(), + icon: , + onClick: () => { + enterFullscreen(); + closePopover(); + }, + }, + { + name: isWriteable ? strings.getHideEditModeLabel() : strings.getShowEditModeLabel(), + icon: , + onClick: () => { + toggleWriteable(); + closePopover(); + }, + }, + { + name: strings.getRefreshMenuItemLabel(), + icon: 'refresh', + onClick: () => { + doRefresh(); + }, + }, + { + name: strings.getZoomMenuItemLabel(), + icon: 'magnifyWithPlus', + panel: { + id: 1, + title: strings.getZoomMenuItemLabel(), + items: getZoomMenuItems(), + }, + }, + ], + }); + + return ( + + {({ closePopover }: { closePopover: ClosePopoverFn }) => ( + + )} + + ); +}; + +ViewMenu.propTypes = { + isWriteable: PropTypes.bool.isRequired, +}; diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/__examples__/__snapshots__/workpad_export.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/__examples__/__snapshots__/workpad_export.examples.storyshot deleted file mode 100644 index ef96320e7bc65..0000000000000 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/__examples__/__snapshots__/workpad_export.examples.storyshot +++ /dev/null @@ -1,41 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Storyshots components/Export/WorkpadExport enabled 1`] = ` -
-
-
- - - -
-
-
-`; diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_header.tsx b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_header.tsx index 31ad0593f58bb..253e6c68cfc9e 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_header.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_header.tsx @@ -4,38 +4,21 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { FunctionComponent } from 'react'; import PropTypes from 'prop-types'; // @ts-ignore no @types definition import { Shortcuts } from 'react-shortcuts'; -import { - EuiFlexItem, - EuiFlexGroup, - EuiButtonIcon, - EuiButton, - EuiButtonEmpty, - EuiOverlayMask, - EuiModal, - EuiModalFooter, - EuiToolTip, -} from '@elastic/eui'; - +import { EuiFlexItem, EuiFlexGroup, EuiButtonIcon, EuiToolTip } from '@elastic/eui'; import { ComponentStrings } from '../../../i18n'; - -// @ts-ignore untyped local -import { AssetManager } from '../asset_manager'; -// @ts-ignore untyped local -import { ElementTypes } from '../element_types'; import { ToolTipShortcut } from '../tool_tip_shortcut/'; -import { AddEmbeddablePanel } from '../embeddable_flyout'; -// @ts-ignore untyped local import { ControlSettings } from './control_settings'; // @ts-ignore untyped local import { RefreshControl } from './refresh_control'; // @ts-ignore untyped local import { FullscreenControl } from './fullscreen_control'; -import { WorkpadExport } from './workpad_export'; -import { WorkpadZoom } from './workpad_zoom'; +import { ElementMenu } from './element_menu'; +import { ShareMenu } from './share_menu'; +import { ViewMenu } from './view_menu'; const { WorkpadHeader: strings } = ComponentStrings; @@ -43,23 +26,20 @@ export interface Props { isWriteable: boolean; toggleWriteable: () => void; canUserWrite: boolean; - selectedPage: string; } -interface State { - isModalVisible: boolean; - isPanelVisible: boolean; -} - -export class WorkpadHeader extends React.PureComponent { - static propTypes = { - isWriteable: PropTypes.bool, - toggleWriteable: PropTypes.func, +export const WorkpadHeader: FunctionComponent = ({ + isWriteable, + canUserWrite, + toggleWriteable, +}) => { + const keyHandler = (action: string) => { + if (action === 'EDITING') { + toggleWriteable(); + } }; - state = { isModalVisible: false, isPanelVisible: false }; - - _fullscreenButton = ({ toggleFullscreen }: { toggleFullscreen: () => void }) => ( + const fullscreenButton = ({ toggleFullscreen }: { toggleFullscreen: () => void }) => ( { ); - _keyHandler = (action: string) => { - if (action === 'EDITING') { - this.props.toggleWriteable(); - } - }; - - _hideElementModal = () => this.setState({ isModalVisible: false }); - _showElementModal = () => this.setState({ isModalVisible: true }); - - _hideEmbeddablePanel = () => this.setState({ isPanelVisible: false }); - _showEmbeddablePanel = () => this.setState({ isPanelVisible: true }); - - _elementAdd = () => ( - - - - - - {strings.getAddElementModalCloseButtonLabel()} - - - - - ); - - _embeddableAdd = () => ; - - _getEditToggleToolTipText = () => { - if (!this.props.canUserWrite) { + const getEditToggleToolTipText = () => { + if (!canUserWrite) { return strings.getNoWritePermissionTooltipText(); } - const content = this.props.isWriteable + const content = isWriteable ? strings.getHideEditControlTooltip() : strings.getShowEditControlTooltip(); return content; }; - _getEditToggleToolTip = ({ textOnly } = { textOnly: false }) => { - const content = this._getEditToggleToolTipText(); + const getEditToggleToolTip = ({ textOnly } = { textOnly: false }) => { + const content = getEditToggleToolTipText(); if (textOnly) { return content; @@ -135,86 +83,67 @@ export class WorkpadHeader extends React.PureComponent { ); }; - render() { - const { isWriteable, canUserWrite, toggleWriteable } = this.props; - const { isModalVisible, isPanelVisible } = this.state; - - return ( -
- {isModalVisible ? this._elementAdd() : null} - {isPanelVisible ? this._embeddableAdd() : null} - - - - - - - - - - - {this._fullscreenButton} - - - - - - - - - {canUserWrite && ( - - )} - - - - - - - {isWriteable ? ( + return ( + + + + {isWriteable && ( - - - - - - - {strings.getEmbedObjectButtonLabel()} - - - - - {strings.getAddElementButtonLabel()} - - - + - ) : null} + )} + + + + + + + + + -
- ); - } -} + + + + + {canUserWrite && ( + + )} + + + + + + + + + {fullscreenButton} + + + + + ); +}; + +WorkpadHeader.propTypes = { + isWriteable: PropTypes.bool, + toggleWriteable: PropTypes.func, + canUserWrite: PropTypes.bool, +}; diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_zoom/index.tsx b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_zoom/index.tsx deleted file mode 100644 index b22a9d35aa793..0000000000000 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_zoom/index.tsx +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { compose, withHandlers } from 'recompose'; -import { connect } from 'react-redux'; -import { Dispatch } from 'redux'; -import { getZoomScale } from '../../../state/selectors/app'; -import { - getWorkpadBoundingBox, - getWorkpadWidth, - getWorkpadHeight, -} from '../../../state/selectors/workpad'; -// @ts-ignore unconverted local file -import { setZoomScale } from '../../../state/actions/transient'; -import { zoomHandlerCreators } from '../../../lib/app_handler_creators'; -import { WorkpadZoom as Component, Props as ComponentProps } from './workpad_zoom'; -import { State } from '../../../../types'; - -const mapStateToProps = (state: State) => { - return { - zoomScale: getZoomScale(state), - boundingBox: getWorkpadBoundingBox(state), - workpadWidth: getWorkpadWidth(state), - workpadHeight: getWorkpadHeight(state), - }; -}; - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - setZoomScale: (scale: number) => dispatch(setZoomScale(scale)), -}); - -export const WorkpadZoom = compose( - connect(mapStateToProps, mapDispatchToProps), - withHandlers(zoomHandlerCreators) -)(Component); diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_zoom/workpad_zoom.scss b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_zoom/workpad_zoom.scss deleted file mode 100644 index 44209aaa72d63..0000000000000 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_zoom/workpad_zoom.scss +++ /dev/null @@ -1,11 +0,0 @@ -.canvasWorkpadExport__panelContent { - padding: $euiSize; -} - -.canvasWorkpadExport__reportingConfig { - .euiCodeBlock__pre { - @include euiScrollBar; - overflow-x: auto; - white-space: pre; - } -} diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_zoom/workpad_zoom.tsx b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_zoom/workpad_zoom.tsx deleted file mode 100644 index 4e37a525761cd..0000000000000 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_zoom/workpad_zoom.tsx +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { MouseEventHandler, PureComponent } from 'react'; -import PropTypes from 'prop-types'; -import { - EuiButtonIcon, - EuiContextMenu, - EuiContextMenuPanelDescriptor, - EuiContextMenuPanelItemDescriptor, -} from '@elastic/eui'; -// @ts-ignore untyped local -import { Popover } from '../../popover'; -import { - MAX_ZOOM_LEVEL, - MIN_ZOOM_LEVEL, - WORKPAD_CANVAS_BUFFER, - CANVAS_LAYOUT_STAGE_CONTENT_SELECTOR, -} from '../../../../common/lib/constants'; - -import { ComponentStrings } from '../../../../i18n'; -const { WorkpadHeaderWorkpadZoom: strings } = ComponentStrings; - -export interface Props { - /** - * current workpad zoom level - */ - zoomScale: number; - /** - * minimum bounding box for the workpad - */ - boundingBox: { left: number; right: number; top: number; bottom: number }; - /** - * width of the workpad page - */ - workpadWidth: number; - /** - * height of the workpad page - */ - workpadHeight: number; - /** - * handler to set the workpad zoom level to a specific value - */ - setZoomScale: (scale: number) => void; - /** - * handler to increase the workpad zoom level - */ - zoomIn: () => void; - /** - * handler to decrease workpad zoom level - */ - zoomOut: () => void; - /** - * reset zoom to 100% - */ - resetZoom: () => void; -} - -const QUICK_ZOOM_LEVELS = [0.5, 1, 2]; - -export class WorkpadZoom extends PureComponent { - static propTypes = { - zoomScale: PropTypes.number.isRequired, - setZoomScale: PropTypes.func.isRequired, - zoomIn: PropTypes.func.isRequired, - zoomOut: PropTypes.func.isRequired, - resetZoom: PropTypes.func.isRequired, - boundingBox: PropTypes.shape({ - left: PropTypes.number.isRequired, - right: PropTypes.number.isRequired, - top: PropTypes.number.isRequired, - bottom: PropTypes.number.isRequired, - }), - workpadWidth: PropTypes.number.isRequired, - workpadHeight: PropTypes.number.isRequired, - }; - - _fitToWindow = () => { - const { boundingBox, setZoomScale, workpadWidth, workpadHeight } = this.props; - const canvasLayoutContent = document.querySelector( - `#${CANVAS_LAYOUT_STAGE_CONTENT_SELECTOR}` - ) as HTMLElement; - const layoutWidth = canvasLayoutContent.clientWidth; - const layoutHeight = canvasLayoutContent.clientHeight; - const offsetLeft = boundingBox.left; - const offsetTop = boundingBox.top; - const offsetRight = boundingBox.right - workpadWidth; - const offsetBottom = boundingBox.bottom - workpadHeight; - const boundingWidth = - workpadWidth + - Math.max(Math.abs(offsetLeft), Math.abs(offsetRight)) * 2 + - WORKPAD_CANVAS_BUFFER; - const boundingHeight = - workpadHeight + - Math.max(Math.abs(offsetTop), Math.abs(offsetBottom)) * 2 + - WORKPAD_CANVAS_BUFFER; - const xScale = layoutWidth / boundingWidth; - const yScale = layoutHeight / boundingHeight; - - setZoomScale(Math.min(xScale, yScale)); - }; - - _button = (togglePopover: MouseEventHandler) => ( - - ); - - _getScaleMenuItems = (): EuiContextMenuPanelItemDescriptor[] => - QUICK_ZOOM_LEVELS.map(scale => ({ - name: strings.getZoomPercentage(scale), - icon: 'empty', - onClick: () => this.props.setZoomScale(scale), - })); - - _getPanels = (): EuiContextMenuPanelDescriptor[] => { - const { zoomScale, zoomIn, zoomOut, resetZoom } = this.props; - const items: EuiContextMenuPanelItemDescriptor[] = [ - { - name: strings.getZoomFitToWindowText(), - icon: 'empty', - onClick: this._fitToWindow, - disabled: zoomScale === MAX_ZOOM_LEVEL, - }, - ...this._getScaleMenuItems(), - { - name: strings.getZoomInText(), - icon: 'magnifyWithPlus', - onClick: zoomIn, - disabled: zoomScale === MAX_ZOOM_LEVEL, - className: 'canvasContextMenu--topBorder', - }, - { - name: strings.getZoomOutText(), - icon: 'magnifyWithMinus', - onClick: zoomOut, - disabled: zoomScale <= MIN_ZOOM_LEVEL, - }, - { - name: strings.getZoomResetText(), - icon: 'empty', - onClick: resetZoom, - disabled: zoomScale >= MAX_ZOOM_LEVEL, - className: 'canvasContextMenu--topBorder', - }, - ]; - - const panels: EuiContextMenuPanelDescriptor[] = [ - { - id: 0, - title: strings.getZoomPanelTitle(), - items, - }, - ]; - - return panels; - }; - - render() { - return ( - - {() => } - - ); - } -} diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_loader/index.js b/x-pack/legacy/plugins/canvas/public/components/workpad_loader/index.js index 429d27afb3f0d..226ad420535bd 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_loader/index.js +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_loader/index.js @@ -6,7 +6,8 @@ import PropTypes from 'prop-types'; import { connect } from 'react-redux'; -import { compose, withState, getContext, withHandlers } from 'recompose'; +import { compose, withState, getContext, withHandlers, withProps } from 'recompose'; +import moment from 'moment'; import * as workpadService from '../../lib/workpad_service'; import { notify } from '../../lib/notify'; import { canUserWrite } from '../../state/selectors/app'; @@ -14,6 +15,7 @@ import { getWorkpad } from '../../state/selectors/workpad'; import { getId } from '../../lib/get_id'; import { downloadWorkpad } from '../../lib/download_workpad'; import { ComponentStrings, ErrorStrings } from '../../../i18n'; +import { withKibana } from '../../../../../../../src/plugins/kibana_react/public'; import { WorkpadLoader as Component } from './workpad_loader'; const { WorkpadLoader: strings } = ComponentStrings; @@ -127,5 +129,12 @@ export const WorkpadLoader = compose( return errors.map(({ id }) => id); }); }, - }) + }), + withKibana, + withProps(props => ({ + formatDate: date => { + const dateFormat = props.kibana.services.uiSettings.get('dateFormat'); + return date && moment(date).format(dateFormat); + }, + })) )(Component); diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_loader/workpad_loader.js b/x-pack/legacy/plugins/canvas/public/components/workpad_loader/workpad_loader.js index 30d4ded8571c5..04378e5603c4b 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_loader/workpad_loader.js +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_loader/workpad_loader.js @@ -20,12 +20,10 @@ import { EuiLink, } from '@elastic/eui'; import { sortByOrder } from 'lodash'; -import moment from 'moment'; import { ConfirmModal } from '../confirm_modal'; import { Link } from '../link'; import { Paginate } from '../paginate'; import { ComponentStrings } from '../../../i18n'; -import { getAdvancedSettings } from '../../lib/kibana_advanced_settings'; import { WorkpadDropzone } from './workpad_dropzone'; import { WorkpadCreate } from './workpad_create'; import { WorkpadSearch } from './workpad_search'; @@ -33,8 +31,6 @@ import { uploadWorkpad } from './upload_workpad'; const { WorkpadLoader: strings } = ComponentStrings; -const formatDate = date => date && moment(date).format(getAdvancedSettings().get('dateFormat')); - const getDisplayName = (name, workpad, loadedWorkpad) => { const workpadName = name.length ? name : {workpad.id}; return workpad.id === loadedWorkpad ? {workpadName} : workpadName; @@ -51,6 +47,7 @@ export class WorkpadLoader extends React.PureComponent { removeWorkpads: PropTypes.func.isRequired, onClose: PropTypes.func.isRequired, workpads: PropTypes.object, + formatDate: PropTypes.func.isRequired, }; state = { @@ -205,7 +202,7 @@ export class WorkpadLoader extends React.PureComponent { sortable: true, dataType: 'date', width: '20%', - render: date => formatDate(date), + render: date => this.props.formatDate(date), }, { field: '@timestamp', @@ -213,7 +210,7 @@ export class WorkpadLoader extends React.PureComponent { sortable: true, dataType: 'date', width: '20%', - render: date => formatDate(date), + render: date => this.props.formatDate(date), }, { name: '', actions, width: '5%' }, ]; diff --git a/x-pack/legacy/plugins/canvas/public/lib/custom_element_service.ts b/x-pack/legacy/plugins/canvas/public/lib/custom_element_service.ts index 478e2f8f18cf5..4118bb81b8363 100644 --- a/x-pack/legacy/plugins/canvas/public/lib/custom_element_service.ts +++ b/x-pack/legacy/plugins/canvas/public/lib/custom_element_service.ts @@ -5,9 +5,7 @@ */ import { AxiosPromise } from 'axios'; -// @ts-ignore unconverted local file import { API_ROUTE_CUSTOM_ELEMENT } from '../../common/lib/constants'; -// @ts-ignore unconverted local file import { fetch } from '../../common/lib/fetch'; import { CustomElement } from '../../types'; import { getCoreStart } from '../legacy'; @@ -25,7 +23,7 @@ export const get = (customElementId: string): Promise => .get(`${getApiPath()}/${customElementId}`) .then(({ data: element }: { data: CustomElement }) => element); -export const update = (id: string, element: CustomElement): AxiosPromise => +export const update = (id: string, element: Partial): AxiosPromise => fetch.put(`${getApiPath()}/${id}`, element); export const remove = (id: string): AxiosPromise => fetch.delete(`${getApiPath()}/${id}`); diff --git a/x-pack/legacy/plugins/canvas/public/lib/default_header.png b/x-pack/legacy/plugins/canvas/public/lib/default_header.png deleted file mode 100644 index 0b5c5b8f58f9b..0000000000000 Binary files a/x-pack/legacy/plugins/canvas/public/lib/default_header.png and /dev/null differ diff --git a/x-pack/legacy/plugins/canvas/public/lib/element.ts b/x-pack/legacy/plugins/canvas/public/lib/element.ts index 121c253668ed9..ef1cf601b6e26 100644 --- a/x-pack/legacy/plugins/canvas/public/lib/element.ts +++ b/x-pack/legacy/plugins/canvas/public/lib/element.ts @@ -5,18 +5,16 @@ */ import { ElementSpec } from '../../types'; -import defaultHeader from './default_header.png'; -import { tagsRegistry } from './tags_registry'; export class Element { /** The name of the Element. This must match the name of the function that is used to create the `type: render` object */ public name: string; /** A more friendly name for the Element */ public displayName: string; - /** Relevant labels to help identify the elements */ - public tags: string[]; - /** An image to use in the Element type selector */ - public image: string; + /** The type of the Element */ + public type?: string; + /** The name of the EUI icon to display in the element menu */ + public icon: string; /** A sentence or few about what this Element does */ public help: string; /** A default expression that allows Canvas to render the Element */ @@ -28,23 +26,17 @@ export class Element { public height?: number; constructor(config: ElementSpec) { - const { name, image, displayName, tags, expression, filter, help, width, height } = config; + const { name, icon, displayName, type, expression, filter, help, width, height } = config; this.name = name; this.displayName = displayName || name; - this.image = image || defaultHeader; + this.icon = icon || 'empty'; this.help = help || ''; if (!config.expression) { throw new Error('Element types must have a default expression'); } - this.tags = tags || []; - - this.tags.forEach(tag => { - if (!tagsRegistry.get(tag)) { - tagsRegistry.register(() => ({ name: tag, color: '#666666' })); - } - }); + this.type = type; this.expression = expression; this.filter = filter; this.width = width || 500; diff --git a/x-pack/legacy/plugins/canvas/public/lib/flatten_panel_tree.ts b/x-pack/legacy/plugins/canvas/public/lib/flatten_panel_tree.ts new file mode 100644 index 0000000000000..a059d07725727 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/public/lib/flatten_panel_tree.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +// TODO: Fix all of this magic from EUI; this code is boilerplate from +// EUI examples and isn't easily typed. +export const flattenPanelTree = (tree: any, array: any[] = []) => { + array.push(tree); + + if (tree.items) { + tree.items.forEach((item: any) => { + const { panel } = item; + if (panel) { + flattenPanelTree(panel, array); + item.panel = panel.id; + } + }); + } + + return array; +}; diff --git a/x-pack/legacy/plugins/canvas/public/lib/window_error_handler.js b/x-pack/legacy/plugins/canvas/public/lib/window_error_handler.js index bf97928452c71..75307816b4371 100644 --- a/x-pack/legacy/plugins/canvas/public/lib/window_error_handler.js +++ b/x-pack/legacy/plugins/canvas/public/lib/window_error_handler.js @@ -47,9 +47,16 @@ window.canvasInitErrorHandler = () => { window.onerror = (...args) => { const [message, , , , err] = args; - const isKnownError = Object.keys(knownErrors).find(errorName => { - return err.constructor.name === errorName || message.indexOf(errorName) >= 0; - }); + // ResizeObserver error does not have an `err` object + // It is thrown during workpad loading due to layout thrashing + // https://stackoverflow.com/questions/49384120/resizeobserver-loop-limit-exceeded + // https://github.com/elastic/eui/issues/3346 + console.log(message); + const isKnownError = + message.includes('ResizeObserver loop') || + Object.keys(knownErrors).find(errorName => { + return err.constructor.name === errorName || message.indexOf(errorName) >= 0; + }); if (isKnownError) { return; } diff --git a/x-pack/legacy/plugins/canvas/public/state/selectors/workpad.ts b/x-pack/legacy/plugins/canvas/public/state/selectors/workpad.ts index 84fab0cb0ae6d..1623035bd25ba 100644 --- a/x-pack/legacy/plugins/canvas/public/state/selectors/workpad.ts +++ b/x-pack/legacy/plugins/canvas/public/state/selectors/workpad.ts @@ -11,7 +11,12 @@ import { safeElementFromExpression, fromExpression } from '@kbn/interpreter/comm import { append } from '../../lib/modify_path'; import { getAssets } from './assets'; import { State, CanvasWorkpad, CanvasPage, CanvasElement, ResolvedArgType } from '../../../types'; -import { ExpressionContext, CanvasGroup, PositionedElement } from '../../../types'; +import { + ExpressionContext, + CanvasGroup, + PositionedElement, + CanvasWorkpadBoundingBox, +} from '../../../types'; import { ExpressionAstArgument, ExpressionAstFunction, @@ -91,7 +96,7 @@ export function getWorkpadWidth(state: State): number { return get(state, append(workpadRoot, 'width')); } -export function getWorkpadBoundingBox(state: State) { +export function getWorkpadBoundingBox(state: State): CanvasWorkpadBoundingBox { return getPages(state).reduce( (boundingBox, page) => { page.elements.forEach(({ position }) => { diff --git a/x-pack/legacy/plugins/canvas/public/style/index.scss b/x-pack/legacy/plugins/canvas/public/style/index.scss index 56f9ed8d18cbe..ba0845862368a 100644 --- a/x-pack/legacy/plugins/canvas/public/style/index.scss +++ b/x-pack/legacy/plugins/canvas/public/style/index.scss @@ -52,7 +52,8 @@ @import '../components/tooltip_annotation/tooltip_annotation'; @import '../components/workpad/workpad'; @import '../components/workpad_header/control_settings/control_settings'; -@import '../components/workpad_header/workpad_export/workpad_export'; +@import '../components/workpad_header/element_menu/element_menu'; +@import '../components/workpad_header/share_menu/share_menu'; @import '../components/workpad_loader/workpad_loader'; @import '../components/workpad_loader/workpad_dropzone/workpad_dropzone'; @import '../components/workpad_page/workpad_page'; diff --git a/x-pack/legacy/plugins/canvas/shareable_runtime/test/workpads/austin.json b/x-pack/legacy/plugins/canvas/shareable_runtime/test/workpads/austin.json index f9f999440c7ce..b725afab2b10f 100644 --- a/x-pack/legacy/plugins/canvas/shareable_runtime/test/workpads/austin.json +++ b/x-pack/legacy/plugins/canvas/shareable_runtime/test/workpads/austin.json @@ -28878,7 +28878,7 @@ "type": "render", "as": "markdown", "value": { - "content": "```\nexport const githubLimitGauge = () => ({\n name: 'githubLimitGauge',\n displayName: 'Github Limit Gauge',\n help: 'A progress pill displaying...',\n image: header,\n expression: `github_rate_limit \n | filterrows fn={getCell name | eq core} \n | if \n condition={math limit | eq 0} \n then=0 \n else={math \"remaining / limit\"}\n | progress \n label=\"Core\"\n shape=\"horizontalPill\" \n | render\n `,\n };\n}\n```", + "content": "```\nexport const githubLimitGauge = () => ({\n name: 'githubLimitGauge',\n displayName: 'Github Limit Gauge',\n help: 'A progress pill displaying...',\n expression: `github_rate_limit \n | filterrows fn={getCell name | eq core} \n | if \n condition={math limit | eq 0} \n then=0 \n else={math \"remaining / limit\"}\n | progress \n label=\"Core\"\n shape=\"horizontalPill\" \n | render\n `,\n };\n}\n```", "font": { "type": "style", "spec": { @@ -28919,7 +28919,7 @@ "type": "render", "as": "markdown", "value": { - "content": "```\nexport function randomNumber() {\n return {\n name: 'randomNumber',\n displayName: 'Random Number',\n help: 'A random number between 0 and 1.',\n image: header,\n expression: \n 'random \n | math \"round(value, 3)\" \n | metric \"Random Number\"\n ',\n };\n}\n```", + "content": "```\nexport function randomNumber() {\n return {\n name: 'randomNumber',\n displayName: 'Random Number',\n help: 'A random number between 0 and 1.',\n expression: \n 'random \n | math \"round(value, 3)\" \n | metric \"Random Number\"\n ',\n };\n}\n```", "font": { "type": "style", "spec": { diff --git a/x-pack/legacy/plugins/canvas/tasks/mocks/customElementService.js b/x-pack/legacy/plugins/canvas/tasks/mocks/customElementService.js new file mode 100644 index 0000000000000..3162638cb6c5d --- /dev/null +++ b/x-pack/legacy/plugins/canvas/tasks/mocks/customElementService.js @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const testCustomElements = [ + { + id: 'custom-element-10d625f5-1342-47c9-8f19-d174ea6b65d5', + name: 'customElement1', + displayName: 'Custom Element 1', + help: 'sample description', + image: '', + content: `{\"selectedNodes\":[{\"id\":\"element-3383b40a-de5d-4efb-8719-f4d8cffbfa74\",\"position\":{\"left\":142,\"top\":146,\"width\":700,\"height\":300,\"angle\":0,\"parent\":null,\"type\":\"element\"},\"expression\":\"filters\\n| demodata\\n| pointseries x=\\\"project\\\" y=\\\"sum(price)\\\" color=\\\"state\\\" size=\\\"size(username)\\\"\\n| plot defaultStyle={seriesStyle points=5 fill=1}\\n| render\",\"ast\":{\"type\":\"expression\",\"chain\":[{\"type\":\"function\",\"function\":\"filters\",\"arguments\":{}},{\"type\":\"function\",\"function\":\"demodata\",\"arguments\":{}},{\"type\":\"function\",\"function\":\"pointseries\",\"arguments\":{\"x\":[\"project\"],\"y\":[\"sum(price)\"],\"color\":[\"state\"],\"size\":[\"size(username)\"]}},{\"type\":\"function\",\"function\":\"plot\",\"arguments\":{\"defaultStyle\":[{\"type\":\"expression\",\"chain\":[{\"type\":\"function\",\"function\":\"seriesStyle\",\"arguments\":{\"points\":[5],\"fill\":[1]}}]}]}},{\"type\":\"function\",\"function\":\"render\",\"arguments\":{}}]}}]}`, + }, + { + id: 'custom-element-b22d8d10-6116-46fb-9b46-c3f3340d3aaa', + name: 'customElement2', + displayName: 'Custom Element 2', + help: 'Aenean eu justo auctor, placerat felis non, scelerisque dolor. ', + image: '', + content: `{\"selectedNodes\":[{\"id\":\"group-dccf4ed7-1593-49a0-9902-caf4d4a4b7f5\",\"position\":{\"left\":250,\"top\":119,\"width\":340,\"height\":517,\"angle\":0,\"parent\":null,\"type\":\"group\"},\"expression\":\"shape fill=\\\"rgba(255,255,255,0)\\\" | render\",\"ast\":{\"type\":\"expression\",\"chain\":[{\"type\":\"function\",\"function\":\"shape\",\"arguments\":{\"fill\":[\"rgba(255,255,255,0)\"]}},{\"type\":\"function\",\"function\":\"render\",\"arguments\":{}}]}},{\"id\":\"element-e2c658ee-7614-4d92-a46e-2b1a81a24485\",\"position\":{\"left\":250,\"top\":405,\"width\":340,\"height\":75,\"angle\":0,\"parent\":\"group-dccf4ed7-1593-49a0-9902-caf4d4a4b7f5\",\"type\":\"element\"},\"expression\":\"filters\\n| demodata\\n| markdown \\\"## Jane Doe\\\" \\n font={font family=\\\"'Open Sans', Helvetica, Arial, sans-serif\\\" size=14 align=\\\"center\\\" color=\\\"#000000\\\" weight=\\\"normal\\\" underline=false italic=false}\\n| render\",\"ast\":{\"type\":\"expression\",\"chain\":[{\"type\":\"function\",\"function\":\"filters\",\"arguments\":{}},{\"type\":\"function\",\"function\":\"demodata\",\"arguments\":{}},{\"type\":\"function\",\"function\":\"markdown\",\"arguments\":{\"_\":[\"## Jane Doe\"],\"font\":[{\"type\":\"expression\",\"chain\":[{\"type\":\"function\",\"function\":\"font\",\"arguments\":{\"family\":[\"'Open Sans', Helvetica, Arial, sans-serif\"],\"size\":[14],\"align\":[\"center\"],\"color\":[\"#000000\"],\"weight\":[\"normal\"],\"underline\":[false],\"italic\":[false]}}]}]}},{\"type\":\"function\",\"function\":\"render\",\"arguments\":{}}]}},{\"id\":\"element-3d16765e-5251-4954-8e2a-6c64ed465b73\",\"position\":{\"left\":250,\"top\":480,\"width\":340,\"height\":75,\"angle\":0,\"parent\":\"group-dccf4ed7-1593-49a0-9902-caf4d4a4b7f5\",\"type\":\"element\"},\"expression\":\"filters\\n| demodata\\n| markdown \\\"### Developer\\\" \\n font={font family=\\\"'Open Sans', Helvetica, Arial, sans-serif\\\" size=14 align=\\\"center\\\" color=\\\"#000000\\\" weight=\\\"normal\\\" underline=false italic=false}\\n| render css=\\\".canvasRenderEl h3 {\\ncolor: #444444;\\n}\\\"\",\"ast\":{\"type\":\"expression\",\"chain\":[{\"type\":\"function\",\"function\":\"filters\",\"arguments\":{}},{\"type\":\"function\",\"function\":\"demodata\",\"arguments\":{}},{\"type\":\"function\",\"function\":\"markdown\",\"arguments\":{\"_\":[\"### Developer\"],\"font\":[{\"type\":\"expression\",\"chain\":[{\"type\":\"function\",\"function\":\"font\",\"arguments\":{\"family\":[\"'Open Sans', Helvetica, Arial, sans-serif\"],\"size\":[14],\"align\":[\"center\"],\"color\":[\"#000000\"],\"weight\":[\"normal\"],\"underline\":[false],\"italic\":[false]}}]}]}},{\"type\":\"function\",\"function\":\"render\",\"arguments\":{\"css\":[\".canvasRenderEl h3 {\\ncolor: #444444;\\n}\"]}}]}},{\"id\":\"element-624675cf-46e9-4545-b86a-5409bbe53ac1\",\"position\":{\"left\":250,\"top\":555,\"width\":340,\"height\":81,\"angle\":0,\"parent\":\"group-dccf4ed7-1593-49a0-9902-caf4d4a4b7f5\",\"type\":\"element\"},\"expression\":\"filters\\n| demodata\\n| markdown \\n \\\"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vel sollicitudin mauris, ut scelerisque urna. \\\" \\n font={font family=\\\"'Open Sans', Helvetica, Arial, sans-serif\\\" size=14 align=\\\"center\\\" color=\\\"#000000\\\" weight=\\\"normal\\\" underline=false italic=false}\\n| render\",\"ast\":{\"type\":\"expression\",\"chain\":[{\"type\":\"function\",\"function\":\"filters\",\"arguments\":{}},{\"type\":\"function\",\"function\":\"demodata\",\"arguments\":{}},{\"type\":\"function\",\"function\":\"markdown\",\"arguments\":{\"_\":[\"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vel sollicitudin mauris, ut scelerisque urna. \"],\"font\":[{\"type\":\"expression\",\"chain\":[{\"type\":\"function\",\"function\":\"font\",\"arguments\":{\"family\":[\"'Open Sans', Helvetica, Arial, sans-serif\"],\"size\":[14],\"align\":[\"center\"],\"color\":[\"#000000\"],\"weight\":[\"normal\"],\"underline\":[false],\"italic\":[false]}}]}]}},{\"type\":\"function\",\"function\":\"render\",\"arguments\":{}}]}},{\"id\":\"element-c2916246-26dd-4c65-91c6-d1ad3f1791ee\",\"position\":{\"left\":293,\"top\":119,\"width\":254,\"height\":252,\"angle\":0,\"parent\":\"group-dccf4ed7-1593-49a0-9902-caf4d4a4b7f5\",\"type\":\"element\"},\"expression\":\"image dataurl={asset \\\"asset-0c6f377f-771e-432e-8e2e-15c3e9142ad6\\\"} mode=\\\"contain\\\"\\n| render\",\"ast\":{\"type\":\"expression\",\"chain\":[{\"type\":\"function\",\"function\":\"image\",\"arguments\":{\"dataurl\":[{\"type\":\"expression\",\"chain\":[{\"type\":\"function\",\"function\":\"asset\",\"arguments\":{\"_\":[\"asset-0c6f377f-771e-432e-8e2e-15c3e9142ad6\"]}}]}],\"mode\":[\"contain\"]}},{\"type\":\"function\",\"function\":\"render\",\"arguments\":{}}]}}]}`, + }, + { + id: 'custom-element-', + name: 'customElement3', + displayName: 'Custom Element 3', + help: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce lobortis aliquet arcu ut turpis duis.', + image: '', + content: `{\"selectedNodes\":[{\"id\":\"group-dccf4ed7-1593-49a0-9902-caf4d4a4b7f5\",\"position\":{\"left\":250,\"top\":119,\"width\":340,\"height\":517,\"angle\":0,\"parent\":null,\"type\":\"group\"},\"expression\":\"shape fill=\\\"rgba(255,255,255,0)\\\" | render\",\"ast\":{\"type\":\"expression\",\"chain\":[{\"type\":\"function\",\"function\":\"shape\",\"arguments\":{\"fill\":[\"rgba(255,255,255,0)\"]}},{\"type\":\"function\",\"function\":\"render\",\"arguments\":{}}]}},{\"id\":\"element-e2c658ee-7614-4d92-a46e-2b1a81a24485\",\"position\":{\"left\":250,\"top\":405,\"width\":340,\"height\":75,\"angle\":0,\"parent\":\"group-dccf4ed7-1593-49a0-9902-caf4d4a4b7f5\",\"type\":\"element\"},\"expression\":\"filters\\n| demodata\\n| markdown \\\"## Jane Doe\\\" \\n font={font family=\\\"'Open Sans', Helvetica, Arial, sans-serif\\\" size=14 align=\\\"center\\\" color=\\\"#000000\\\" weight=\\\"normal\\\" underline=false italic=false}\\n| render\",\"ast\":{\"type\":\"expression\",\"chain\":[{\"type\":\"function\",\"function\":\"filters\",\"arguments\":{}},{\"type\":\"function\",\"function\":\"demodata\",\"arguments\":{}},{\"type\":\"function\",\"function\":\"markdown\",\"arguments\":{\"_\":[\"## Jane Doe\"],\"font\":[{\"type\":\"expression\",\"chain\":[{\"type\":\"function\",\"function\":\"font\",\"arguments\":{\"family\":[\"'Open Sans', Helvetica, Arial, sans-serif\"],\"size\":[14],\"align\":[\"center\"],\"color\":[\"#000000\"],\"weight\":[\"normal\"],\"underline\":[false],\"italic\":[false]}}]}]}},{\"type\":\"function\",\"function\":\"render\",\"arguments\":{}}]}},{\"id\":\"element-3d16765e-5251-4954-8e2a-6c64ed465b73\",\"position\":{\"left\":250,\"top\":480,\"width\":340,\"height\":75,\"angle\":0,\"parent\":\"group-dccf4ed7-1593-49a0-9902-caf4d4a4b7f5\",\"type\":\"element\"},\"expression\":\"filters\\n| demodata\\n| markdown \\\"### Developer\\\" \\n font={font family=\\\"'Open Sans', Helvetica, Arial, sans-serif\\\" size=14 align=\\\"center\\\" color=\\\"#000000\\\" weight=\\\"normal\\\" underline=false italic=false}\\n| render css=\\\".canvasRenderEl h3 {\\ncolor: #444444;\\n}\\\"\",\"ast\":{\"type\":\"expression\",\"chain\":[{\"type\":\"function\",\"function\":\"filters\",\"arguments\":{}},{\"type\":\"function\",\"function\":\"demodata\",\"arguments\":{}},{\"type\":\"function\",\"function\":\"markdown\",\"arguments\":{\"_\":[\"### Developer\"],\"font\":[{\"type\":\"expression\",\"chain\":[{\"type\":\"function\",\"function\":\"font\",\"arguments\":{\"family\":[\"'Open Sans', Helvetica, Arial, sans-serif\"],\"size\":[14],\"align\":[\"center\"],\"color\":[\"#000000\"],\"weight\":[\"normal\"],\"underline\":[false],\"italic\":[false]}}]}]}},{\"type\":\"function\",\"function\":\"render\",\"arguments\":{\"css\":[\".canvasRenderEl h3 {\\ncolor: #444444;\\n}\"]}}]}},{\"id\":\"element-624675cf-46e9-4545-b86a-5409bbe53ac1\",\"position\":{\"left\":250,\"top\":555,\"width\":340,\"height\":81,\"angle\":0,\"parent\":\"group-dccf4ed7-1593-49a0-9902-caf4d4a4b7f5\",\"type\":\"element\"},\"expression\":\"filters\\n| demodata\\n| markdown \\n \\\"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vel sollicitudin mauris, ut scelerisque urna. \\\" \\n font={font family=\\\"'Open Sans', Helvetica, Arial, sans-serif\\\" size=14 align=\\\"center\\\" color=\\\"#000000\\\" weight=\\\"normal\\\" underline=false italic=false}\\n| render\",\"ast\":{\"type\":\"expression\",\"chain\":[{\"type\":\"function\",\"function\":\"filters\",\"arguments\":{}},{\"type\":\"function\",\"function\":\"demodata\",\"arguments\":{}},{\"type\":\"function\",\"function\":\"markdown\",\"arguments\":{\"_\":[\"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vel sollicitudin mauris, ut scelerisque urna. \"],\"font\":[{\"type\":\"expression\",\"chain\":[{\"type\":\"function\",\"function\":\"font\",\"arguments\":{\"family\":[\"'Open Sans', Helvetica, Arial, sans-serif\"],\"size\":[14],\"align\":[\"center\"],\"color\":[\"#000000\"],\"weight\":[\"normal\"],\"underline\":[false],\"italic\":[false]}}]}]}},{\"type\":\"function\",\"function\":\"render\",\"arguments\":{}}]}},{\"id\":\"element-c2916246-26dd-4c65-91c6-d1ad3f1791ee\",\"position\":{\"left\":293,\"top\":119,\"width\":254,\"height\":252,\"angle\":0,\"parent\":\"group-dccf4ed7-1593-49a0-9902-caf4d4a4b7f5\",\"type\":\"element\"},\"expression\":\"image dataurl={asset \\\"asset-0c6f377f-771e-432e-8e2e-15c3e9142ad6\\\"} mode=\\\"contain\\\"\\n| render\",\"ast\":{\"type\":\"expression\",\"chain\":[{\"type\":\"function\",\"function\":\"image\",\"arguments\":{\"dataurl\":[{\"type\":\"expression\",\"chain\":[{\"type\":\"function\",\"function\":\"asset\",\"arguments\":{\"_\":[\"asset-0c6f377f-771e-432e-8e2e-15c3e9142ad6\"]}}]}],\"mode\":[\"contain\"]}},{\"type\":\"function\",\"function\":\"render\",\"arguments\":{}}]}}]}`, + }, +]; + +export const create = () => {}; + +export const get = () => {}; + +export const update = () => {}; + +export const remove = () => {}; + +export const find = () => {}; diff --git a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/lib/error_wrappers/index.ts b/x-pack/legacy/plugins/canvas/tasks/mocks/uiMetric.js similarity index 83% rename from x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/lib/error_wrappers/index.ts rename to x-pack/legacy/plugins/canvas/tasks/mocks/uiMetric.js index 3756b0c74fb10..c7e7088812148 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/lib/error_wrappers/index.ts +++ b/x-pack/legacy/plugins/canvas/tasks/mocks/uiMetric.js @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { wrapEsError } from './wrap_es_error'; +export const trackCanvasUiMetric = () => {}; diff --git a/x-pack/legacy/plugins/canvas/types/canvas.ts b/x-pack/legacy/plugins/canvas/types/canvas.ts index f0137479a0b7f..0250b921aadb6 100644 --- a/x-pack/legacy/plugins/canvas/types/canvas.ts +++ b/x-pack/legacy/plugins/canvas/types/canvas.ts @@ -56,3 +56,10 @@ export type CanvasTemplate = CanvasWorkpad & { help: string; tags: string[]; }; + +export interface CanvasWorkpadBoundingBox { + left: number; + right: number; + top: number; + bottom: number; +} diff --git a/x-pack/legacy/plugins/canvas/types/elements.ts b/x-pack/legacy/plugins/canvas/types/elements.ts index acb1cb9cd7625..86356f5bd32a9 100644 --- a/x-pack/legacy/plugins/canvas/types/elements.ts +++ b/x-pack/legacy/plugins/canvas/types/elements.ts @@ -9,10 +9,10 @@ import { CanvasElement } from '.'; export interface ElementSpec { name: string; - image: string; + icon?: string; expression: string; displayName?: string; - tags?: string[]; + type?: string; help?: string; filter?: string; width?: number; @@ -42,10 +42,6 @@ export interface CustomElement { * base 64 data URL string of the preview image */ image?: string; - /** - * tags associated with the element - */ - tags?: string[]; /** * the element object stringified */ diff --git a/x-pack/legacy/plugins/cross_cluster_replication/common/constants/base_path.ts b/x-pack/legacy/plugins/cross_cluster_replication/common/constants/base_path.ts deleted file mode 100644 index 0a948793e07db..0000000000000 --- a/x-pack/legacy/plugins/cross_cluster_replication/common/constants/base_path.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export const BASE_PATH = '/management/elasticsearch/cross_cluster_replication'; -export const BASE_PATH_REMOTE_CLUSTERS = '/management/elasticsearch/remote_clusters'; -export const API_BASE_PATH = '/api/cross_cluster_replication'; -export const API_REMOTE_CLUSTERS_BASE_PATH = '/api/remote_clusters'; -export const API_INDEX_MANAGEMENT_BASE_PATH = '/api/index_management'; diff --git a/x-pack/legacy/plugins/cross_cluster_replication/common/constants/index.ts b/x-pack/legacy/plugins/cross_cluster_replication/common/constants/index.ts deleted file mode 100644 index 300afb4e2d2ff..0000000000000 --- a/x-pack/legacy/plugins/cross_cluster_replication/common/constants/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export * from './plugin'; -export * from './base_path'; -export * from './app'; -export * from './settings'; diff --git a/x-pack/legacy/plugins/cross_cluster_replication/common/constants/settings.ts b/x-pack/legacy/plugins/cross_cluster_replication/common/constants/settings.ts deleted file mode 100644 index 0993a74c8f1fd..0000000000000 --- a/x-pack/legacy/plugins/cross_cluster_replication/common/constants/settings.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export const FOLLOWER_INDEX_ADVANCED_SETTINGS = { - maxReadRequestOperationCount: 5120, - maxOutstandingReadRequests: 12, - maxReadRequestSize: '32mb', - maxWriteRequestOperationCount: 5120, - maxWriteRequestSize: '9223372036854775807b', - maxOutstandingWriteRequests: 9, - maxWriteBufferCount: 2147483647, - maxWriteBufferSize: '512mb', - maxRetryDelay: '500ms', - readPollTimeout: '1m', -}; diff --git a/x-pack/legacy/plugins/cross_cluster_replication/common/services/__snapshots__/follower_index_serialization.test.js.snap b/x-pack/legacy/plugins/cross_cluster_replication/common/services/__snapshots__/follower_index_serialization.test.js.snap deleted file mode 100644 index d001459e8234d..0000000000000 --- a/x-pack/legacy/plugins/cross_cluster_replication/common/services/__snapshots__/follower_index_serialization.test.js.snap +++ /dev/null @@ -1,128 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`[CCR] follower index serialization deserializeFollowerIndex() deserializes Elasticsearch follower index object 1`] = ` -Object { - "leaderIndex": undefined, - "maxOutstandingReadRequests": undefined, - "maxOutstandingWriteRequests": undefined, - "maxReadRequestOperationCount": undefined, - "maxReadRequestSize": undefined, - "maxRetryDelay": undefined, - "maxWriteBufferCount": undefined, - "maxWriteBufferSize": undefined, - "maxWriteRequestOperationCount": undefined, - "maxWriteRequestSize": undefined, - "name": undefined, - "readPollTimeout": undefined, - "remoteCluster": undefined, - "shards": Array [ - Object { - "bytesReadCount": undefined, - "failedReadRequestsCount": undefined, - "failedWriteRequestsCount": undefined, - "followerGlobalCheckpoint": undefined, - "followerMappingVersion": undefined, - "followerMaxSequenceNum": undefined, - "followerSettingsVersion": undefined, - "id": "shard 1", - "lastRequestedSequenceNum": undefined, - "leaderGlobalCheckpoint": undefined, - "leaderIndex": undefined, - "leaderMaxSequenceNum": undefined, - "operationsReadCount": undefined, - "operationsWrittenCount": undefined, - "outstandingReadRequestsCount": undefined, - "outstandingWriteRequestsCount": undefined, - "readExceptions": undefined, - "remoteCluster": undefined, - "successfulReadRequestCount": undefined, - "successfulWriteRequestsCount": undefined, - "timeSinceLastReadMs": undefined, - "totalReadRemoteExecTimeMs": undefined, - "totalReadTimeMs": undefined, - "totalWriteTimeMs": undefined, - "writeBufferOperationsCount": undefined, - "writeBufferSizeBytes": undefined, - }, - Object { - "bytesReadCount": undefined, - "failedReadRequestsCount": undefined, - "failedWriteRequestsCount": undefined, - "followerGlobalCheckpoint": undefined, - "followerMappingVersion": undefined, - "followerMaxSequenceNum": undefined, - "followerSettingsVersion": undefined, - "id": "shard 2", - "lastRequestedSequenceNum": undefined, - "leaderGlobalCheckpoint": undefined, - "leaderIndex": undefined, - "leaderMaxSequenceNum": undefined, - "operationsReadCount": undefined, - "operationsWrittenCount": undefined, - "outstandingReadRequestsCount": undefined, - "outstandingWriteRequestsCount": undefined, - "readExceptions": undefined, - "remoteCluster": undefined, - "successfulReadRequestCount": undefined, - "successfulWriteRequestsCount": undefined, - "timeSinceLastReadMs": undefined, - "totalReadRemoteExecTimeMs": undefined, - "totalReadTimeMs": undefined, - "totalWriteTimeMs": undefined, - "writeBufferOperationsCount": undefined, - "writeBufferSizeBytes": undefined, - }, - ], - "status": "active", -} -`; - -exports[`[CCR] follower index serialization deserializeShard() deserializes shard 1`] = ` -Object { - "bytesReadCount": "bytes read", - "failedReadRequestsCount": "failed read requests", - "failedWriteRequestsCount": "failed write requests", - "followerGlobalCheckpoint": "follower global checkpoint", - "followerMappingVersion": "follower mapping version", - "followerMaxSequenceNum": "follower max seq no", - "followerSettingsVersion": "follower settings version", - "id": "shard id", - "lastRequestedSequenceNum": "last requested seq no", - "leaderGlobalCheckpoint": "leader global checkpoint", - "leaderIndex": "leader index", - "leaderMaxSequenceNum": "leader max seq no", - "operationsReadCount": "operations read", - "operationsWrittenCount": "operations written", - "outstandingReadRequestsCount": "outstanding read requests", - "outstandingWriteRequestsCount": "outstanding write requests", - "readExceptions": Array [ - "read exception", - ], - "remoteCluster": "remote cluster", - "successfulReadRequestCount": "successful read requests", - "successfulWriteRequestsCount": "successful write requests", - "timeSinceLastReadMs": "time since last read millis", - "totalReadRemoteExecTimeMs": "total read remote exec time millis", - "totalReadTimeMs": "total read time millis", - "totalWriteTimeMs": "total write time millis", - "writeBufferOperationsCount": "write buffer operation count", - "writeBufferSizeBytes": "write buffer size in bytes", -} -`; - -exports[`[CCR] follower index serialization serializeFollowerIndex() serializes object to Elasticsearch follower index object 1`] = ` -Object { - "leader_index": "leader index", - "max_outstanding_read_requests": "foo", - "max_outstanding_write_requests": "foo", - "max_read_request_operation_count": "foo", - "max_read_request_size": "foo", - "max_retry_delay": "foo", - "max_write_buffer_count": "foo", - "max_write_buffer_size": "foo", - "max_write_request_operation_count": "foo", - "max_write_request_size": "foo", - "read_poll_timeout": "foo", - "remote_cluster": "remote cluster", -} -`; diff --git a/x-pack/legacy/plugins/cross_cluster_replication/common/services/auto_follow_pattern_serialization.js b/x-pack/legacy/plugins/cross_cluster_replication/common/services/auto_follow_pattern_serialization.js deleted file mode 100644 index ae13c625a7d80..0000000000000 --- a/x-pack/legacy/plugins/cross_cluster_replication/common/services/auto_follow_pattern_serialization.js +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export const deserializeAutoFollowPattern = ( - { - name, - pattern: { - active, - // eslint-disable-next-line camelcase - remote_cluster, - // eslint-disable-next-line camelcase - leader_index_patterns, - // eslint-disable-next-line camelcase - follow_index_pattern, - }, - } = { - pattern: {}, - } -) => ({ - name, - active, - remoteCluster: remote_cluster, - leaderIndexPatterns: leader_index_patterns, - followIndexPattern: follow_index_pattern, -}); - -export const deserializeListAutoFollowPatterns = autoFollowPatterns => - autoFollowPatterns.map(deserializeAutoFollowPattern); - -export const serializeAutoFollowPattern = ({ - remoteCluster, - leaderIndexPatterns, - followIndexPattern, -}) => ({ - remote_cluster: remoteCluster, - leader_index_patterns: leaderIndexPatterns, - follow_index_pattern: followIndexPattern, -}); diff --git a/x-pack/legacy/plugins/cross_cluster_replication/common/services/follower_index_serialization.test.js b/x-pack/legacy/plugins/cross_cluster_replication/common/services/follower_index_serialization.test.js deleted file mode 100644 index e1df917d899ad..0000000000000 --- a/x-pack/legacy/plugins/cross_cluster_replication/common/services/follower_index_serialization.test.js +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { - deserializeShard, - deserializeFollowerIndex, - deserializeListFollowerIndices, - serializeFollowerIndex, -} from './follower_index_serialization'; - -describe('[CCR] follower index serialization', () => { - describe('deserializeShard()', () => { - it('deserializes shard', () => { - const serializedShard = { - remote_cluster: 'remote cluster', - leader_index: 'leader index', - shard_id: 'shard id', - leader_global_checkpoint: 'leader global checkpoint', - leader_max_seq_no: 'leader max seq no', - follower_global_checkpoint: 'follower global checkpoint', - follower_max_seq_no: 'follower max seq no', - last_requested_seq_no: 'last requested seq no', - outstanding_read_requests: 'outstanding read requests', - outstanding_write_requests: 'outstanding write requests', - write_buffer_operation_count: 'write buffer operation count', - write_buffer_size_in_bytes: 'write buffer size in bytes', - follower_mapping_version: 'follower mapping version', - follower_settings_version: 'follower settings version', - total_read_time_millis: 'total read time millis', - total_read_remote_exec_time_millis: 'total read remote exec time millis', - successful_read_requests: 'successful read requests', - failed_read_requests: 'failed read requests', - operations_read: 'operations read', - bytes_read: 'bytes read', - total_write_time_millis: 'total write time millis', - successful_write_requests: 'successful write requests', - failed_write_requests: 'failed write requests', - operations_written: 'operations written', - read_exceptions: ['read exception'], - time_since_last_read_millis: 'time since last read millis', - }; - - expect(deserializeShard(serializedShard)).toMatchSnapshot(); - }); - }); - - describe('deserializeFollowerIndex()', () => { - it('deserializes Elasticsearch follower index object', () => { - const serializedFollowerIndex = { - index: 'follower index name', - status: 'active', - shards: [ - { - shard_id: 'shard 1', - }, - { - shard_id: 'shard 2', - }, - ], - }; - - expect(deserializeFollowerIndex(serializedFollowerIndex)).toMatchSnapshot(); - }); - }); - - describe('deserializeListFollowerIndices()', () => { - it('deserializes list of Elasticsearch follower index objects', () => { - const serializedFollowerIndexList = [ - { - follower_index: 'follower index 1', - remote_cluster: 'cluster 1', - leader_index: 'leader 1', - status: 'active', - parameters: { - max_read_request_operation_count: 1, - max_outstanding_read_requests: 1, - max_read_request_size: 1, - max_write_request_operation_count: 1, - max_write_request_size: 1, - max_outstanding_write_requests: 1, - max_write_buffer_count: 1, - max_write_buffer_size: 1, - max_retry_delay: 1, - read_poll_timeout: 1, - }, - shards: [], - }, - { - follower_index: 'follower index 2', - remote_cluster: 'cluster 2', - leader_index: 'leader 2', - status: 'paused', - parameters: { - max_read_request_operation_count: 2, - max_outstanding_read_requests: 2, - max_read_request_size: 2, - max_write_request_operation_count: 2, - max_write_request_size: 2, - max_outstanding_write_requests: 2, - max_write_buffer_count: 2, - max_write_buffer_size: 2, - max_retry_delay: 2, - read_poll_timeout: 2, - }, - shards: [], - }, - ]; - - const deserializedFollowerIndexList = [ - { - name: 'follower index 1', - remoteCluster: 'cluster 1', - leaderIndex: 'leader 1', - status: 'active', - maxReadRequestOperationCount: 1, - maxOutstandingReadRequests: 1, - maxReadRequestSize: 1, - maxWriteRequestOperationCount: 1, - maxWriteRequestSize: 1, - maxOutstandingWriteRequests: 1, - maxWriteBufferCount: 1, - maxWriteBufferSize: 1, - maxRetryDelay: 1, - readPollTimeout: 1, - shards: [], - }, - { - name: 'follower index 2', - remoteCluster: 'cluster 2', - leaderIndex: 'leader 2', - status: 'paused', - maxReadRequestOperationCount: 2, - maxOutstandingReadRequests: 2, - maxReadRequestSize: 2, - maxWriteRequestOperationCount: 2, - maxWriteRequestSize: 2, - maxOutstandingWriteRequests: 2, - maxWriteBufferCount: 2, - maxWriteBufferSize: 2, - maxRetryDelay: 2, - readPollTimeout: 2, - shards: [], - }, - ]; - - expect(deserializeListFollowerIndices(serializedFollowerIndexList)).toEqual( - deserializedFollowerIndexList - ); - }); - }); - - describe('serializeFollowerIndex()', () => { - it('serializes object to Elasticsearch follower index object', () => { - const deserializedFollowerIndex = { - remoteCluster: 'remote cluster', - leaderIndex: 'leader index', - maxReadRequestOperationCount: 'foo', - maxOutstandingReadRequests: 'foo', - maxReadRequestSize: 'foo', - maxWriteRequestOperationCount: 'foo', - maxWriteRequestSize: 'foo', - maxOutstandingWriteRequests: 'foo', - maxWriteBufferCount: 'foo', - maxWriteBufferSize: 'foo', - maxRetryDelay: 'foo', - readPollTimeout: 'foo', - }; - - expect(serializeFollowerIndex(deserializedFollowerIndex)).toMatchSnapshot(); - }); - }); -}); diff --git a/x-pack/legacy/plugins/cross_cluster_replication/fixtures/auto_follow_pattern.js b/x-pack/legacy/plugins/cross_cluster_replication/fixtures/auto_follow_pattern.js deleted file mode 100644 index 804fe80cd27b4..0000000000000 --- a/x-pack/legacy/plugins/cross_cluster_replication/fixtures/auto_follow_pattern.js +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { getRandomString } from '../../../../test_utils'; - -export const getAutoFollowPatternMock = ( - name = getRandomString(), - remoteCluster = getRandomString(), - leaderIndexPatterns = [getRandomString()], - followIndexPattern = getRandomString() -) => ({ - name, - pattern: { - remote_cluster: remoteCluster, - leader_index_patterns: leaderIndexPatterns, - follow_index_pattern: followIndexPattern, - }, -}); - -export const getAutoFollowPatternListMock = (total = 3) => { - const list = { - patterns: [], - }; - - let i = total; - while (i--) { - list.patterns.push(getAutoFollowPatternMock()); - } - - return list; -}; - -// ----------------- -// Client test mock -// ----------------- -export const getAutoFollowPatternClientMock = ({ - name = getRandomString(), - remoteCluster = getRandomString(), - leaderIndexPatterns = [`${getRandomString()}-*`], - followIndexPattern = getRandomString(), -}) => ({ - name, - remoteCluster, - leaderIndexPatterns, - followIndexPattern, -}); diff --git a/x-pack/legacy/plugins/cross_cluster_replication/fixtures/es_errors.js b/x-pack/legacy/plugins/cross_cluster_replication/fixtures/es_errors.js deleted file mode 100644 index a042375e82715..0000000000000 --- a/x-pack/legacy/plugins/cross_cluster_replication/fixtures/es_errors.js +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -/** - * Errors mocks to throw during development to help visualizing - * the different flows in the UI - * - * TODO: Consult the ES team and make sure the error shapes are correct - * for each statusCode. - */ - -const error400 = new Error('Something went wrong'); -error400.statusCode = 400; -error400.response = ` - { - "error": { - "root_cause": [ - { - "type": "x_content_parse_exception", - "reason": "[2:3] [put_auto_follow_pattern_request] unknown field [remote_clusterxxxxx], parser not found" - } - ], - "type": "x_content_parse_exception", - "reason": "[2:3] [put_auto_follow_pattern_request] unknown field [remote_clusterxxxxx], parser not found" - }, - "status": 400 -}`; - -const error403 = new Error('Unauthorized'); -error403.statusCode = 403; -error403.response = ` - { - "acknowledged": true, - "trial_was_started": false, - "error_message": "Operation failed: Trial was already activated." - } -`; - -export const esErrors = { - 400: error400, - 403: error403, -}; diff --git a/x-pack/legacy/plugins/cross_cluster_replication/fixtures/follower_index.js b/x-pack/legacy/plugins/cross_cluster_replication/fixtures/follower_index.js deleted file mode 100644 index 6c535a665978c..0000000000000 --- a/x-pack/legacy/plugins/cross_cluster_replication/fixtures/follower_index.js +++ /dev/null @@ -1,216 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -const Chance = require('chance'); // eslint-disable-line import/no-extraneous-dependencies -const chance = new Chance(); -import { getRandomString } from '../../../../test_utils'; - -const serializeShard = ({ - id, - remoteCluster, - leaderIndex, - leaderGlobalCheckpoint, - leaderMaxSequenceNum, - followerGlobalCheckpoint, - followerMaxSequenceNum, - lastRequestedSequenceNum, - outstandingReadRequestsCount, - outstandingWriteRequestsCount, - writeBufferOperationsCount, - writeBufferSizeBytes, - followerMappingVersion, - followerSettingsVersion, - totalReadTimeMs, - totalReadRemoteExecTimeMs, - successfulReadRequestCount, - failedReadRequestsCount, - operationsReadCount, - bytesReadCount, - totalWriteTimeMs, - successfulWriteRequestsCount, - failedWriteRequestsCount, - operationsWrittenCount, - readExceptions, - timeSinceLastReadMs, -}) => ({ - shard_id: id, - remote_cluster: remoteCluster, - leader_index: leaderIndex, - leader_global_checkpoint: leaderGlobalCheckpoint, - leader_max_seq_no: leaderMaxSequenceNum, - follower_global_checkpoint: followerGlobalCheckpoint, - follower_max_seq_no: followerMaxSequenceNum, - last_requested_seq_no: lastRequestedSequenceNum, - outstanding_read_requests: outstandingReadRequestsCount, - outstanding_write_requests: outstandingWriteRequestsCount, - write_buffer_operation_count: writeBufferOperationsCount, - write_buffer_size_in_bytes: writeBufferSizeBytes, - follower_mapping_version: followerMappingVersion, - follower_settings_version: followerSettingsVersion, - total_read_time_millis: totalReadTimeMs, - total_read_remote_exec_time_millis: totalReadRemoteExecTimeMs, - successful_read_requests: successfulReadRequestCount, - failed_read_requests: failedReadRequestsCount, - operations_read: operationsReadCount, - bytes_read: bytesReadCount, - total_write_time_millis: totalWriteTimeMs, - successful_write_requests: successfulWriteRequestsCount, - failed_write_requests: failedWriteRequestsCount, - operations_written: operationsWrittenCount, - read_exceptions: readExceptions, - time_since_last_read_millis: timeSinceLastReadMs, -}); - -export const getFollowerIndexStatsMock = ( - name = chance.string(), - shards = [ - { - id: chance.string(), - remoteCluster: chance.string(), - leaderIndex: chance.string(), - leaderGlobalCheckpoint: chance.integer(), - leaderMaxSequenceNum: chance.integer(), - followerGlobalCheckpoint: chance.integer(), - followerMaxSequenceNum: chance.integer(), - lastRequestedSequenceNum: chance.integer(), - outstandingReadRequestsCount: chance.integer(), - outstandingWriteRequestsCount: chance.integer(), - writeBufferOperationsCount: chance.integer(), - writeBufferSizeBytes: chance.integer(), - followerMappingVersion: chance.integer(), - followerSettingsVersion: chance.integer(), - totalReadTimeMs: chance.integer(), - totalReadRemoteExecTimeMs: chance.integer(), - successfulReadRequestCount: chance.integer(), - failedReadRequestsCount: chance.integer(), - operationsReadCount: chance.integer(), - bytesReadCount: chance.integer(), - totalWriteTimeMs: chance.integer(), - successfulWriteRequestsCount: chance.integer(), - failedWriteRequestsCount: chance.integer(), - operationsWrittenCount: chance.integer(), - readExceptions: [chance.string()], - timeSinceLastReadMs: chance.integer(), - }, - ] -) => ({ - index: name, - shards: shards.map(serializeShard), -}); - -export const getFollowerIndexListStatsMock = (total = 3, names) => { - const list = { - follow_stats: { - indices: [], - }, - }; - - for (let i = 0; i < total; i++) { - list.follow_stats.indices.push(getFollowerIndexStatsMock(names[i])); - } - - return list; -}; - -export const getFollowerIndexInfoMock = ( - name = chance.string(), - status = chance.string(), - parameters = { - maxReadRequestOperationCount: chance.string(), - maxOutstandingReadRequests: chance.string(), - maxReadRequestSize: chance.string(), - maxWriteRequestOperationCount: chance.string(), - maxWriteRequestSize: chance.string(), - maxOutstandingWriteRequests: chance.string(), - maxWriteBufferCount: chance.string(), - maxWriteBufferSize: chance.string(), - maxRetryDelay: chance.string(), - readPollTimeout: chance.string(), - } -) => { - return { - follower_index: name, - status, - max_read_request_operation_count: parameters.maxReadRequestOperationCount, - max_outstanding_read_requests: parameters.maxOutstandingReadRequests, - max_read_request_size: parameters.maxReadRequestSize, - max_write_request_operation_count: parameters.maxWriteRequestOperationCount, - max_write_request_size: parameters.maxWriteRequestSize, - max_outstanding_write_requests: parameters.maxOutstandingWriteRequests, - max_write_buffer_count: parameters.maxWriteBufferCount, - max_write_buffer_size: parameters.maxWriteBufferSize, - max_retry_delay: parameters.maxRetryDelay, - read_poll_timeout: parameters.readPollTimeout, - }; -}; - -export const getFollowerIndexListInfoMock = (total = 3) => { - const list = { - follower_indices: [], - }; - - for (let i = 0; i < total; i++) { - list.follower_indices.push(getFollowerIndexInfoMock()); - } - - return list; -}; - -// ----------------- -// Client test mock -// ----------------- - -export const getFollowerIndexMock = ({ - name = getRandomString(), - remoteCluster = getRandomString(), - leaderIndex = getRandomString(), - status = 'Active', -} = {}) => ({ - name, - remoteCluster, - leaderIndex, - status, - maxReadRequestOperationCount: chance.integer(), - maxOutstandingReadRequests: chance.integer(), - maxReadRequestSize: getRandomString({ length: 5 }), - maxWriteRequestOperationCount: chance.integer(), - maxWriteRequestSize: '9223372036854775807b', - maxOutstandingWriteRequests: chance.integer(), - maxWriteBufferCount: chance.integer(), - maxWriteBufferSize: getRandomString({ length: 5 }), - maxRetryDelay: getRandomString({ length: 5 }), - readPollTimeout: getRandomString({ length: 5 }), - shards: [ - { - id: 0, - remoteCluster: remoteCluster, - leaderIndex: leaderIndex, - leaderGlobalCheckpoint: chance.integer(), - leaderMaxSequenceNum: chance.integer(), - followerGlobalCheckpoint: chance.integer(), - followerMaxSequenceNum: chance.integer(), - lastRequestedSequenceNum: chance.integer(), - outstandingReadRequestsCount: chance.integer(), - outstandingWriteRequestsCount: chance.integer(), - writeBufferOperationsCount: chance.integer(), - writeBufferSizeBytes: chance.integer(), - followerMappingVersion: chance.integer(), - followerSettingsVersion: chance.integer(), - totalReadTimeMs: chance.integer(), - totalReadRemoteExecTimeMs: chance.integer(), - successfulReadRequestCount: chance.integer(), - failedReadRequestsCount: chance.integer(), - operationsReadCount: chance.integer(), - bytesReadCount: chance.integer(), - totalWriteTimeMs: chance.integer(), - successfulWriteRequestsCount: chance.integer(), - failedWriteRequestsCount: chance.integer(), - operationsWrittenCount: chance.integer(), - readExceptions: [], - timeSinceLastReadMs: chance.integer(), - }, - ], -}); diff --git a/x-pack/legacy/plugins/cross_cluster_replication/fixtures/index.js b/x-pack/legacy/plugins/cross_cluster_replication/fixtures/index.js deleted file mode 100644 index ccfdf8b19f3ee..0000000000000 --- a/x-pack/legacy/plugins/cross_cluster_replication/fixtures/index.js +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { getAutoFollowPatternMock, getAutoFollowPatternListMock } from './auto_follow_pattern'; - -export { esErrors } from './es_errors'; - -export { - getFollowerIndexStatsMock, - getFollowerIndexListStatsMock, - getFollowerIndexInfoMock, - getFollowerIndexListInfoMock, -} from './follower_index'; diff --git a/x-pack/legacy/plugins/cross_cluster_replication/index.js b/x-pack/legacy/plugins/cross_cluster_replication/index.js deleted file mode 100644 index aff4cc5b56738..0000000000000 --- a/x-pack/legacy/plugins/cross_cluster_replication/index.js +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { resolve } from 'path'; -import { PLUGIN } from './common/constants'; -import { plugin } from './server/np_ready'; - -export function crossClusterReplication(kibana) { - return new kibana.Plugin({ - id: PLUGIN.ID, - configPrefix: 'xpack.ccr', - publicDir: resolve(__dirname, 'public'), - require: ['kibana', 'elasticsearch', 'xpack_main', 'remoteClusters', 'index_management'], - uiExports: { - styleSheetPaths: resolve(__dirname, 'public/index.scss'), - managementSections: ['plugins/cross_cluster_replication'], - injectDefaultVars(server) { - const config = server.config(); - return { - ccrUiEnabled: - config.get('xpack.ccr.ui.enabled') && config.get('xpack.remote_clusters.ui.enabled'), - }; - }, - }, - - config(Joi) { - return Joi.object({ - // display menu item - ui: Joi.object({ - enabled: Joi.boolean().default(true), - }).default(), - - // enable plugin - enabled: Joi.boolean().default(true), - }).default(); - }, - isEnabled(config) { - return ( - config.get('xpack.ccr.enabled') && - config.get('xpack.index_management.enabled') && - config.get('xpack.remote_clusters.enabled') - ); - }, - init: function initCcrPlugin(server) { - plugin({}).setup(server.newPlatform.setup.core, { - indexManagement: server.newPlatform.setup.plugins.indexManagement, - __LEGACY: { - server, - ccrUIEnabled: server.config().get('xpack.ccr.ui.enabled'), - }, - }); - }, - }); -} diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/index.scss b/x-pack/legacy/plugins/cross_cluster_replication/public/index.scss deleted file mode 100644 index 31317e16e3e9f..0000000000000 --- a/x-pack/legacy/plugins/cross_cluster_replication/public/index.scss +++ /dev/null @@ -1,13 +0,0 @@ -// Import the EUI global scope so we can use EUI constants -@import 'src/legacy/ui/public/styles/_styling_constants'; - -// Cross-Cluster Replication plugin styles - -// Prefix all styles with "ccr" to avoid conflicts. -// Examples -// ccrChart -// ccrChart__legend -// ccrChart__legend--small -// ccrChart__legend-isLoading - -@import 'np_ready/app/app'; diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/main.html b/x-pack/legacy/plugins/cross_cluster_replication/public/main.html deleted file mode 100644 index 2129f26267827..0000000000000 --- a/x-pack/legacy/plugins/cross_cluster_replication/public/main.html +++ /dev/null @@ -1,3 +0,0 @@ - -
-
diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/_app.scss b/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/_app.scss deleted file mode 100644 index 5ee862b1d9e44..0000000000000 --- a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/_app.scss +++ /dev/null @@ -1,14 +0,0 @@ -.ccrFollowerIndicesFormRow { - padding-bottom: 0; -} - -.ccrFollowerIndicesHelpText { - transform: translateY(-3px); -} - -/** - * 1. Prevent context menu popover appearing above confirmation modal - */ -.ccrFollowerIndicesDetailPanel { - z-index: $euiZMask - 1; /* 1 */ -} diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/index.js b/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/index.js deleted file mode 100644 index cc81fce4eebe7..0000000000000 --- a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/index.js +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import React from 'react'; -import { render } from 'react-dom'; -import { Provider } from 'react-redux'; -import { HashRouter } from 'react-router-dom'; - -import { App } from './app'; -import { ccrStore } from './store'; - -export const renderReact = async (elem, I18nContext) => { - render( - - - - - - - , - elem - ); -}; diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/services/documentation_links.ts b/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/services/documentation_links.ts deleted file mode 100644 index f17926d2bee10..0000000000000 --- a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/services/documentation_links.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -let esBase: string; - -export const setDocLinks = ({ - DOC_LINK_VERSION, - ELASTIC_WEBSITE_URL, -}: { - ELASTIC_WEBSITE_URL: string; - DOC_LINK_VERSION: string; -}) => { - esBase = `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/reference/${DOC_LINK_VERSION}`; -}; - -export const getAutoFollowPatternUrl = () => `${esBase}/ccr-put-auto-follow-pattern.html`; -export const getFollowerIndexUrl = () => `${esBase}/ccr-put-follow.html`; -export const getByteUnitsUrl = () => `${esBase}/common-options.html#byte-units`; -export const getTimeUnitsUrl = () => `${esBase}/common-options.html#time-units`; diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/services/notifications.ts b/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/services/notifications.ts deleted file mode 100644 index 5e1c3e9e99437..0000000000000 --- a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/services/notifications.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import { NotificationsSetup, IToasts, FatalErrorsSetup } from 'src/core/public'; - -let _notifications: IToasts; -let _fatalErrors: FatalErrorsSetup; - -export const setNotifications = ( - notifications: NotificationsSetup, - fatalErrorsSetup: FatalErrorsSetup -) => { - _notifications = notifications.toasts; - _fatalErrors = fatalErrorsSetup; -}; - -export const getNotifications = () => _notifications; -export const getFatalErrors = () => _fatalErrors; diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/services/track_ui_metric.js b/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/services/track_ui_metric.js deleted file mode 100644 index 36b9c185b487d..0000000000000 --- a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/services/track_ui_metric.js +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { - createUiStatsReporter, - METRIC_TYPE, -} from '../../../../../../../../src/legacy/core_plugins/ui_metric/public'; -import { UIM_APP_NAME } from '../constants'; - -export const trackUiMetric = createUiStatsReporter(UIM_APP_NAME); -export { METRIC_TYPE }; -/** - * Transparently return provided request Promise, while allowing us to track - * a successful completion of the request. - */ -export function trackUserRequest(request, actionType) { - // Only track successful actions. - return request.then(response => { - trackUiMetric(METRIC_TYPE.LOADED, actionType); - // We return the response immediately without waiting for the tracking request to resolve, - // to avoid adding additional latency. - return response; - }); -} diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/extend_index_management.ts b/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/extend_index_management.ts deleted file mode 100644 index 4ffe0db4e3c4e..0000000000000 --- a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/extend_index_management.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { i18n } from '@kbn/i18n'; -import { get } from 'lodash'; -import { IndexManagementPluginSetup } from '../../../../../plugins/index_management/public'; - -const propertyPath = 'isFollowerIndex'; - -const followerBadgeExtension = { - matchIndex: (index: any) => { - return get(index, propertyPath); - }, - label: i18n.translate('xpack.crossClusterReplication.indexMgmtBadge.followerLabel', { - defaultMessage: 'Follower', - }), - color: 'default', - filterExpression: 'isFollowerIndex:true', -}; - -export const extendIndexManagement = (indexManagement?: IndexManagementPluginSetup) => { - if (indexManagement) { - indexManagement.extensionsService.addBadge(followerBadgeExtension); - } -}; diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/plugin.ts b/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/plugin.ts deleted file mode 100644 index 46259c698b282..0000000000000 --- a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/plugin.ts +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import { - ChromeBreadcrumb, - CoreSetup, - Plugin, - PluginInitializerContext, - DocLinksStart, -} from 'src/core/public'; - -import { IndexManagementPluginSetup } from '../../../../../plugins/index_management/public'; - -// @ts-ignore; -import { setHttpClient } from './app/services/api'; -import { setBreadcrumbSetter } from './app/services/breadcrumbs'; -import { setDocLinks } from './app/services/documentation_links'; -import { setNotifications } from './app/services/notifications'; -import { extendIndexManagement } from './extend_index_management'; - -interface PluginDependencies { - indexManagement: IndexManagementPluginSetup; - __LEGACY: { - chrome: any; - MANAGEMENT_BREADCRUMB: ChromeBreadcrumb; - docLinks: DocLinksStart; - }; -} - -export class CrossClusterReplicationUIPlugin implements Plugin { - // @ts-ignore - constructor(private readonly ctx: PluginInitializerContext) {} - setup({ http, notifications, fatalErrors }: CoreSetup, deps: PluginDependencies) { - setHttpClient(http); - setBreadcrumbSetter(deps); - setDocLinks(deps.__LEGACY.docLinks); - setNotifications(notifications, fatalErrors); - extendIndexManagement(deps.indexManagement); - } - - start() {} -} diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/register_routes.js b/x-pack/legacy/plugins/cross_cluster_replication/public/register_routes.js deleted file mode 100644 index 838939f46e523..0000000000000 --- a/x-pack/legacy/plugins/cross_cluster_replication/public/register_routes.js +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { unmountComponentAtNode } from 'react-dom'; -import chrome from 'ui/chrome'; -import { management, MANAGEMENT_BREADCRUMB } from 'ui/management'; -import { npSetup, npStart } from 'ui/new_platform'; -import routes from 'ui/routes'; -import { xpackInfo } from 'plugins/xpack_main/services/xpack_info'; -import { i18n } from '@kbn/i18n'; - -import template from './main.html'; -import { BASE_PATH } from '../common/constants'; - -import { plugin } from './np_ready'; - -/** - * TODO: When this file is deleted, use the management section for rendering - */ -import { renderReact } from './np_ready/app'; - -const isAvailable = xpackInfo.get('features.crossClusterReplication.isAvailable'); -const isActive = xpackInfo.get('features.crossClusterReplication.isActive'); -const isLicenseOK = isAvailable && isActive; -const isCcrUiEnabled = chrome.getInjected('ccrUiEnabled'); - -if (isLicenseOK && isCcrUiEnabled) { - const esSection = management.getSection('elasticsearch'); - - esSection.register('ccr', { - visible: true, - display: i18n.translate('xpack.crossClusterReplication.appTitle', { - defaultMessage: 'Cross-Cluster Replication', - }), - order: 4, - url: `#${BASE_PATH}`, - }); - - let elem; - - const CCR_REACT_ROOT = 'ccrReactRoot'; - - plugin({}).setup(npSetup.core, { - ...npSetup.plugins, - __LEGACY: { - chrome, - docLinks: npStart.core.docLinks, - MANAGEMENT_BREADCRUMB, - }, - }); - - const unmountReactApp = () => elem && unmountComponentAtNode(elem); - - routes.when(`${BASE_PATH}/:section?/:subsection?/:view?/:id?`, { - template, - controllerAs: 'ccr', - controller: class CrossClusterReplicationController { - constructor($scope, $route) { - // React-router's does not play well with the angular router. It will cause this controller - // to re-execute without the $destroy handler being called. This means that the app will be mounted twice - // creating a memory leak when leaving (only 1 app will be unmounted). - // To avoid this, we unmount the React app each time we enter the controller. - unmountReactApp(); - - $scope.$$postDigest(() => { - elem = document.getElementById(CCR_REACT_ROOT); - renderReact(elem, npStart.core.i18n.Context); - - // Angular Lifecycle - const appRoute = $route.current; - const stopListeningForLocationChange = $scope.$on('$locationChangeSuccess', () => { - const currentRoute = $route.current; - const isNavigationInApp = currentRoute.$$route.template === appRoute.$$route.template; - - // When we navigate within CCR, prevent Angular from re-matching the route and rebuild the app - if (isNavigationInApp) { - $route.current = appRoute; - } else { - // Any clean up when User leaves the CCR - } - - $scope.$on('$destroy', () => { - stopListeningForLocationChange && stopListeningForLocationChange(); - unmountReactApp(); - }); - }); - }); - } - }, - }); -} diff --git a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/cross_cluster_replication_data.ts b/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/cross_cluster_replication_data.ts deleted file mode 100644 index ae15073b979e1..0000000000000 --- a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/cross_cluster_replication_data.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import { APICaller } from 'src/core/server'; -import { Index } from '../../../../../plugins/index_management/server'; - -export const ccrDataEnricher = async (indicesList: Index[], callWithRequest: APICaller) => { - if (!indicesList?.length) { - return indicesList; - } - const params = { - path: '/_all/_ccr/info', - method: 'GET', - }; - try { - const { follower_indices: followerIndices } = await callWithRequest( - 'transport.request', - params - ); - return indicesList.map(index => { - const isFollowerIndex = !!followerIndices.find( - (followerIndex: { follower_index: string }) => { - return followerIndex.follower_index === index.name; - } - ); - return { - ...index, - isFollowerIndex, - }; - }); - } catch (e) { - return indicesList; - } -}; diff --git a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/index.ts b/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/index.ts deleted file mode 100644 index 7a38d024d99a2..0000000000000 --- a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { PluginInitializerContext } from 'src/core/server'; -import { CrossClusterReplicationServerPlugin } from './plugin'; - -export const plugin = (ctx: PluginInitializerContext) => - new CrossClusterReplicationServerPlugin(ctx); diff --git a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/lib/call_with_request_factory/call_with_request_factory.js b/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/lib/call_with_request_factory/call_with_request_factory.js deleted file mode 100644 index 99d72ce1a0e6e..0000000000000 --- a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/lib/call_with_request_factory/call_with_request_factory.js +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { once } from 'lodash'; -import { elasticsearchJsPlugin } from '../../client/elasticsearch_ccr'; - -const callWithRequest = once(server => { - const config = { plugins: [elasticsearchJsPlugin] }; - const cluster = server.plugins.elasticsearch.createCluster('ccr', config); - return cluster.callWithRequest; -}); - -export const callWithRequestFactory = (server, request) => { - return (...args) => { - return callWithRequest(server)(request, ...args); - }; -}; diff --git a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/lib/call_with_request_factory/index.js b/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/lib/call_with_request_factory/index.js deleted file mode 100644 index 787814d87dff9..0000000000000 --- a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/lib/call_with_request_factory/index.js +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { callWithRequestFactory } from './call_with_request_factory'; diff --git a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/lib/check_license/check_license.js b/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/lib/check_license/check_license.js deleted file mode 100644 index 6cf12896fa472..0000000000000 --- a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/lib/check_license/check_license.js +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { i18n } from '@kbn/i18n'; - -export function checkLicense(xpackLicenseInfo) { - const pluginName = 'Cross-Cluster Replication'; - - // If, for some reason, we cannot get the license information - // from Elasticsearch, assume worst case and disable - if (!xpackLicenseInfo || !xpackLicenseInfo.isAvailable()) { - return { - isAvailable: false, - showLinks: true, - enableLinks: false, - message: i18n.translate( - 'xpack.crossClusterReplication.checkLicense.errorUnavailableMessage', - { - defaultMessage: - 'You cannot use {pluginName} because license information is not available at this time.', - values: { pluginName }, - } - ), - }; - } - - const VALID_LICENSE_MODES = ['trial', 'platinum', 'enterprise']; - - const isLicenseModeValid = xpackLicenseInfo.license.isOneOf(VALID_LICENSE_MODES); - const isLicenseActive = xpackLicenseInfo.license.isActive(); - const licenseType = xpackLicenseInfo.license.getType(); - - // License is not valid - if (!isLicenseModeValid) { - return { - isAvailable: false, - isActive: false, - message: i18n.translate( - 'xpack.crossClusterReplication.checkLicense.errorUnsupportedMessage', - { - defaultMessage: - 'Your {licenseType} license does not support {pluginName}. Please upgrade your license.', - values: { licenseType, pluginName }, - } - ), - }; - } - - // License is valid but not active - if (!isLicenseActive) { - return { - isAvailable: true, - isActive: false, - message: i18n.translate('xpack.crossClusterReplication.checkLicense.errorExpiredMessage', { - defaultMessage: - 'You cannot use {pluginName} because your {licenseType} license has expired', - values: { licenseType, pluginName }, - }), - }; - } - - // License is valid and active - return { - isAvailable: true, - isActive: true, - }; -} diff --git a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/lib/error_wrappers/__tests__/wrap_es_error.test.js b/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/lib/error_wrappers/__tests__/wrap_es_error.test.js deleted file mode 100644 index 11a6fd4e1d816..0000000000000 --- a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/lib/error_wrappers/__tests__/wrap_es_error.test.js +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { wrapEsError } from '../wrap_es_error'; - -describe('wrap_es_error', () => { - describe('#wrapEsError', () => { - let originalError; - beforeEach(() => { - originalError = new Error('I am an error'); - originalError.statusCode = 404; - originalError.response = '{}'; - }); - - it('should return the correct object', () => { - const wrappedError = wrapEsError(originalError); - - expect(wrappedError.statusCode).to.be(originalError.statusCode); - expect(wrappedError.message).to.be(originalError.message); - }); - - it('should return the correct object with custom message', () => { - const wrappedError = wrapEsError(originalError, { 404: 'No encontrado!' }); - - expect(wrappedError.statusCode).to.be(originalError.statusCode); - expect(wrappedError.message).to.be('No encontrado!'); - }); - }); -}); diff --git a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/lib/is_es_error_factory/__tests__/is_es_error_factory.js b/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/lib/is_es_error_factory/__tests__/is_es_error_factory.js deleted file mode 100644 index 5f2141cce9395..0000000000000 --- a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/lib/is_es_error_factory/__tests__/is_es_error_factory.js +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { isEsErrorFactory } from '../is_es_error_factory'; -import { set } from 'lodash'; - -class MockAbstractEsError {} - -describe('is_es_error_factory', () => { - let mockServer; - let isEsError; - - beforeEach(() => { - const mockEsErrors = { - _Abstract: MockAbstractEsError, - }; - mockServer = {}; - set(mockServer, 'plugins.elasticsearch.getCluster', () => ({ errors: mockEsErrors })); - - isEsError = isEsErrorFactory(mockServer); - }); - - describe('#isEsErrorFactory', () => { - it('should return a function', () => { - expect(isEsError).to.be.a(Function); - }); - - describe('returned function', () => { - it('should return true if passed-in err is a known esError', () => { - const knownEsError = new MockAbstractEsError(); - expect(isEsError(knownEsError)).to.be(true); - }); - - it('should return false if passed-in err is not a known esError', () => { - const unknownEsError = {}; - expect(isEsError(unknownEsError)).to.be(false); - }); - }); - }); -}); diff --git a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/lib/is_es_error_factory/is_es_error_factory.ts b/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/lib/is_es_error_factory/is_es_error_factory.ts deleted file mode 100644 index fc6405b8e7513..0000000000000 --- a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/lib/is_es_error_factory/is_es_error_factory.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { memoize } from 'lodash'; - -const esErrorsFactory = memoize((server: any) => { - return server.plugins.elasticsearch.getCluster('admin').errors; -}); - -export function isEsErrorFactory(server: any) { - const esErrors = esErrorsFactory(server); - return function isEsError(err: any) { - return err instanceof esErrors._Abstract; - }; -} diff --git a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/lib/license_pre_routing_factory/__jest__/license_pre_routing_factory.test.ts b/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/lib/license_pre_routing_factory/__jest__/license_pre_routing_factory.test.ts deleted file mode 100644 index d22505f0e315a..0000000000000 --- a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/lib/license_pre_routing_factory/__jest__/license_pre_routing_factory.test.ts +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import { kibanaResponseFactory } from '../../../../../../../../../src/core/server'; -import { licensePreRoutingFactory } from '../license_pre_routing_factory'; - -describe('license_pre_routing_factory', () => { - describe('#reportingFeaturePreRoutingFactory', () => { - let mockDeps: any; - let mockLicenseCheckResults: any; - - const anyContext: any = {}; - const anyRequest: any = {}; - - beforeEach(() => { - mockDeps = { - __LEGACY: { - server: { - plugins: { - xpack_main: { - info: { - feature: () => ({ - getLicenseCheckResults: () => mockLicenseCheckResults, - }), - }, - }, - }, - }, - }, - requestHandler: jest.fn(), - }; - }); - - describe('isAvailable is false', () => { - beforeEach(() => { - mockLicenseCheckResults = { - isAvailable: false, - }; - }); - - it('replies with 403', async () => { - const licensePreRouting = licensePreRoutingFactory(mockDeps); - const response = await licensePreRouting(anyContext, anyRequest, kibanaResponseFactory); - expect(response.status).toBe(403); - }); - }); - - describe('isAvailable is true', () => { - beforeEach(() => { - mockLicenseCheckResults = { - isAvailable: true, - }; - }); - - it('it calls the wrapped handler', async () => { - const licensePreRouting = licensePreRoutingFactory(mockDeps); - await licensePreRouting(anyContext, anyRequest, kibanaResponseFactory); - expect(mockDeps.requestHandler).toHaveBeenCalledTimes(1); - }); - }); - }); -}); diff --git a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/lib/license_pre_routing_factory/license_pre_routing_factory.ts b/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/lib/license_pre_routing_factory/license_pre_routing_factory.ts deleted file mode 100644 index c47faa940a650..0000000000000 --- a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/lib/license_pre_routing_factory/license_pre_routing_factory.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { RequestHandler } from 'src/core/server'; -import { PLUGIN } from '../../../../common/constants'; - -export const licensePreRoutingFactory = ({ - __LEGACY, - requestHandler, -}: { - __LEGACY: { server: any }; - requestHandler: RequestHandler; -}) => { - const xpackMainPlugin = __LEGACY.server.plugins.xpack_main; - - // License checking and enable/disable logic - const licensePreRouting: RequestHandler = (ctx, request, response) => { - const licenseCheckResults = xpackMainPlugin.info.feature(PLUGIN.ID).getLicenseCheckResults(); - if (!licenseCheckResults.isAvailable) { - return response.forbidden({ - body: licenseCheckResults.message, - }); - } else { - return requestHandler(ctx, request, response); - } - }; - - return licensePreRouting; -}; diff --git a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/lib/register_license_checker/index.js b/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/lib/register_license_checker/index.js deleted file mode 100644 index 7b0f97c38d129..0000000000000 --- a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/lib/register_license_checker/index.js +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { registerLicenseChecker } from './register_license_checker'; diff --git a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/lib/register_license_checker/register_license_checker.js b/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/lib/register_license_checker/register_license_checker.js deleted file mode 100644 index b9bb34a80ce79..0000000000000 --- a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/lib/register_license_checker/register_license_checker.js +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { mirrorPluginStatus } from '../../../../../../server/lib/mirror_plugin_status'; -import { PLUGIN } from '../../../../common/constants'; -import { checkLicense } from '../check_license'; - -export function registerLicenseChecker(__LEGACY) { - const xpackMainPlugin = __LEGACY.server.plugins.xpack_main; - const ccrPluggin = __LEGACY.server.plugins[PLUGIN.ID]; - - mirrorPluginStatus(xpackMainPlugin, ccrPluggin); - xpackMainPlugin.status.once('green', () => { - // Register a function that is called whenever the xpack info changes, - // to re-compute the license check results for this plugin - xpackMainPlugin.info.feature(PLUGIN.ID).registerLicenseCheckResultsGenerator(checkLicense); - }); -} diff --git a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/plugin.ts b/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/plugin.ts deleted file mode 100644 index 829de10ad0177..0000000000000 --- a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/plugin.ts +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { Plugin, PluginInitializerContext, CoreSetup } from 'src/core/server'; - -import { IndexManagementPluginSetup } from '../../../../../plugins/index_management/server'; - -// @ts-ignore -import { registerLicenseChecker } from './lib/register_license_checker'; -// @ts-ignore -import { registerRoutes } from './routes/register_routes'; -import { ccrDataEnricher } from './cross_cluster_replication_data'; - -interface PluginDependencies { - indexManagement: IndexManagementPluginSetup; - __LEGACY: { - server: any; - ccrUIEnabled: boolean; - }; -} - -export class CrossClusterReplicationServerPlugin implements Plugin { - // @ts-ignore - constructor(private readonly ctx: PluginInitializerContext) {} - setup({ http }: CoreSetup, { indexManagement, __LEGACY }: PluginDependencies) { - registerLicenseChecker(__LEGACY); - - const router = http.createRouter(); - registerRoutes({ router, __LEGACY }); - if (__LEGACY.ccrUIEnabled && indexManagement && indexManagement.indexDataEnricher) { - indexManagement.indexDataEnricher.add(ccrDataEnricher); - } - } - start() {} -} diff --git a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/routes/api/__jest__/auto_follow_pattern.test.js b/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/routes/api/__jest__/auto_follow_pattern.test.js deleted file mode 100644 index f3024515c7213..0000000000000 --- a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/routes/api/__jest__/auto_follow_pattern.test.js +++ /dev/null @@ -1,330 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import { deserializeAutoFollowPattern } from '../../../../../common/services/auto_follow_pattern_serialization'; -import { callWithRequestFactory } from '../../../lib/call_with_request_factory'; -import { isEsErrorFactory } from '../../../lib/is_es_error_factory'; -import { getAutoFollowPatternMock, getAutoFollowPatternListMock } from '../../../../../fixtures'; -import { registerAutoFollowPatternRoutes } from '../auto_follow_pattern'; - -import { createRouter, callRoute } from './helpers'; - -jest.mock('../../../lib/call_with_request_factory'); -jest.mock('../../../lib/is_es_error_factory'); -jest.mock('../../../lib/license_pre_routing_factory', () => ({ - licensePreRoutingFactory: ({ requestHandler }) => requestHandler, -})); - -const DESERIALIZED_KEYS = Object.keys(deserializeAutoFollowPattern(getAutoFollowPatternMock())); - -let routeRegistry; - -/** - * Helper to extract all the different server route handler so we can easily call them in our tests. - * - * Important: This method registers the handlers in the order that they appear in the file, so - * if a "server.route()" call is moved or deleted, then the HANDLER_INDEX_TO_ACTION must be updated here. - */ -const registerHandlers = () => { - const HANDLER_INDEX_TO_ACTION = { - 0: 'list', - 1: 'create', - 2: 'update', - 3: 'get', - 4: 'delete', - 5: 'pause', - 6: 'resume', - }; - - routeRegistry = createRouter(HANDLER_INDEX_TO_ACTION); - - registerAutoFollowPatternRoutes({ - __LEGACY: {}, - router: routeRegistry.router, - }); -}; - -/** - * Queue to save request response and errors - * It allows us to fake multiple responses from the - * callWithRequestFactory() when the request handler call it - * multiple times. - */ -let requestResponseQueue = []; - -/** - * Helper to mock the response from the call to Elasticsearch - * - * @param {*} err The mock error to throw - * @param {*} response The response to return - */ -const setHttpRequestResponse = (error, response) => { - requestResponseQueue.push({ error, response }); -}; - -const resetHttpRequestResponses = () => (requestResponseQueue = []); - -const getNextResponseFromQueue = () => { - if (!requestResponseQueue.length) { - return null; - } - - const next = requestResponseQueue.shift(); - if (next.error) { - return Promise.reject(next.error); - } - return Promise.resolve(next.response); -}; - -describe('[CCR API Routes] Auto Follow Pattern', () => { - let routeHandler; - - beforeAll(() => { - isEsErrorFactory.mockReturnValue(() => false); - callWithRequestFactory.mockReturnValue(getNextResponseFromQueue); - registerHandlers(); - }); - - describe('list()', () => { - beforeEach(() => { - routeHandler = routeRegistry.getRoutes().list; - }); - - it('should deserialize the response from Elasticsearch', async () => { - const totalResult = 2; - setHttpRequestResponse(null, getAutoFollowPatternListMock(totalResult)); - - const { - options: { body: response }, - } = await callRoute(routeHandler); - const autoFollowPattern = response.patterns[0]; - - expect(response.patterns.length).toEqual(totalResult); - expect(Object.keys(autoFollowPattern)).toEqual(DESERIALIZED_KEYS); - }); - }); - - describe('create()', () => { - beforeEach(() => { - resetHttpRequestResponses(); - routeHandler = routeRegistry.getRoutes().create; - }); - - it('should throw a 409 conflict error if id already exists', async () => { - setHttpRequestResponse(null, { acknowledge: true }); - setHttpRequestResponse(null, { acknowledge: true }); - - const response = await callRoute( - routeHandler, - {}, - { - body: { - id: 'some-id', - foo: 'bar', - }, - } - ); - - expect(response.status).toEqual(409); - }); - - it('should return 200 status when the id does not exist', async () => { - const error = new Error('Resource not found.'); - error.statusCode = 404; - setHttpRequestResponse(error); - setHttpRequestResponse(null, { acknowledge: true }); - - const { - options: { body: response }, - } = await callRoute( - routeHandler, - {}, - { - body: { - id: 'some-id', - foo: 'bar', - }, - } - ); - - expect(response).toEqual({ acknowledge: true }); - }); - }); - - describe('update()', () => { - beforeEach(() => { - routeHandler = routeRegistry.getRoutes().update; - }); - - it('should serialize the payload before sending it to Elasticsearch', async () => { - callWithRequestFactory.mockReturnValueOnce((_, payload) => payload); - - const request = { - params: { id: 'foo' }, - body: { - remoteCluster: 'bar1', - leaderIndexPatterns: ['bar2'], - followIndexPattern: 'bar3', - }, - }; - - const response = await callRoute(routeHandler, {}, request); - - expect(response.options.body).toEqual({ - id: 'foo', - body: { - remote_cluster: 'bar1', - leader_index_patterns: ['bar2'], - follow_index_pattern: 'bar3', - }, - }); - }); - }); - - describe('get()', () => { - beforeEach(() => { - routeHandler = routeRegistry.getRoutes().get; - }); - - it('should return a single resource even though ES return an array with 1 item', async () => { - const autoFollowPattern = getAutoFollowPatternMock(); - const esResponse = { patterns: [autoFollowPattern] }; - - setHttpRequestResponse(null, esResponse); - - const response = await callRoute(routeHandler, {}, { params: { id: 1 } }); - expect(Object.keys(response.options.body)).toEqual(DESERIALIZED_KEYS); - }); - }); - - describe('delete()', () => { - beforeEach(() => { - resetHttpRequestResponses(); - routeHandler = routeRegistry.getRoutes().delete; - }); - - it('should delete a single item', async () => { - setHttpRequestResponse(null, { acknowledge: true }); - - const { - options: { body: response }, - } = await callRoute(routeHandler, {}, { params: { id: 'a' } }); - - expect(response.itemsDeleted).toEqual(['a']); - expect(response.errors).toEqual([]); - }); - - it('should accept a list of ids to delete', async () => { - setHttpRequestResponse(null, { acknowledge: true }); - setHttpRequestResponse(null, { acknowledge: true }); - setHttpRequestResponse(null, { acknowledge: true }); - - const response = await callRoute(routeHandler, {}, { params: { id: 'a,b,c' } }); - - expect(response.options.body.itemsDeleted).toEqual(['a', 'b', 'c']); - }); - - it('should catch error and return them in array', async () => { - const error = new Error('something went wrong'); - error.response = '{ "error": {} }'; - - setHttpRequestResponse(null, { acknowledge: true }); - setHttpRequestResponse(error); - - const { - options: { body: response }, - } = await callRoute(routeHandler, {}, { params: { id: 'a,b' } }); - - expect(response.itemsDeleted).toEqual(['a']); - expect(response.errors[0].id).toEqual('b'); - }); - }); - - describe('pause()', () => { - beforeEach(() => { - resetHttpRequestResponses(); - routeHandler = routeRegistry.getRoutes().pause; - }); - - it('accept a single item', async () => { - setHttpRequestResponse(null, { acknowledge: true }); - - const { - options: { body: response }, - } = await callRoute(routeHandler, {}, { params: { id: 'a' } }); - - expect(response.itemsPaused).toEqual(['a']); - expect(response.errors).toEqual([]); - }); - - it('should accept a list of items to pause', async () => { - setHttpRequestResponse(null, { acknowledge: true }); - setHttpRequestResponse(null, { acknowledge: true }); - setHttpRequestResponse(null, { acknowledge: true }); - - const response = await callRoute(routeHandler, {}, { params: { id: 'a,b,c' } }); - - expect(response.options.body.itemsPaused).toEqual(['a', 'b', 'c']); - }); - - it('should catch error and return them in array', async () => { - const error = new Error('something went wrong'); - error.response = '{ "error": {} }'; - - setHttpRequestResponse(null, { acknowledge: true }); - setHttpRequestResponse(error); - - const { - options: { body: response }, - } = await callRoute(routeHandler, {}, { params: { id: 'a,b' } }); - - expect(response.itemsPaused).toEqual(['a']); - expect(response.errors[0].id).toEqual('b'); - }); - }); - - describe('resume()', () => { - beforeEach(() => { - resetHttpRequestResponses(); - routeHandler = routeRegistry.getRoutes().resume; - }); - - it('accept a single item', async () => { - setHttpRequestResponse(null, { acknowledge: true }); - - const { - options: { body: response }, - } = await callRoute(routeHandler, {}, { params: { id: 'a' } }); - - expect(response.itemsResumed).toEqual(['a']); - expect(response.errors).toEqual([]); - }); - - it('should accept a list of items to pause', async () => { - setHttpRequestResponse(null, { acknowledge: true }); - setHttpRequestResponse(null, { acknowledge: true }); - setHttpRequestResponse(null, { acknowledge: true }); - - const response = await callRoute(routeHandler, {}, { params: { id: 'a,b,c' } }); - - expect(response.options.body.itemsResumed).toEqual(['a', 'b', 'c']); - }); - - it('should catch error and return them in array', async () => { - const error = new Error('something went wrong'); - error.response = '{ "error": {} }'; - - setHttpRequestResponse(null, { acknowledge: true }); - setHttpRequestResponse(error); - - const { - options: { body: response }, - } = await callRoute(routeHandler, {}, { params: { id: 'a,b' } }); - - expect(response.itemsResumed).toEqual(['a']); - expect(response.errors[0].id).toEqual('b'); - }); - }); -}); diff --git a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/routes/api/__jest__/follower_index.test.js b/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/routes/api/__jest__/follower_index.test.js deleted file mode 100644 index f0139e5bd7011..0000000000000 --- a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/routes/api/__jest__/follower_index.test.js +++ /dev/null @@ -1,312 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import { deserializeFollowerIndex } from '../../../../../common/services/follower_index_serialization'; -import { - getFollowerIndexStatsMock, - getFollowerIndexListStatsMock, - getFollowerIndexInfoMock, - getFollowerIndexListInfoMock, -} from '../../../../../fixtures'; -import { callWithRequestFactory } from '../../../lib/call_with_request_factory'; -import { isEsErrorFactory } from '../../../lib/is_es_error_factory'; -import { registerFollowerIndexRoutes } from '../follower_index'; -import { createRouter, callRoute } from './helpers'; - -jest.mock('../../../lib/call_with_request_factory'); -jest.mock('../../../lib/is_es_error_factory'); -jest.mock('../../../lib/license_pre_routing_factory', () => ({ - licensePreRoutingFactory: ({ requestHandler }) => requestHandler, -})); - -const DESERIALIZED_KEYS = Object.keys( - deserializeFollowerIndex({ - ...getFollowerIndexInfoMock(), - ...getFollowerIndexStatsMock(), - }) -); - -let routeRegistry; - -/** - * Helper to extract all the different server route handler so we can easily call them in our tests. - * - * Important: This method registers the handlers in the order that they appear in the file, so - * if a 'server.route()' call is moved or deleted, then the HANDLER_INDEX_TO_ACTION must be updated here. - */ -const registerHandlers = () => { - const HANDLER_INDEX_TO_ACTION = { - 0: 'list', - 1: 'get', - 2: 'create', - 3: 'edit', - 4: 'pause', - 5: 'resume', - 6: 'unfollow', - }; - - routeRegistry = createRouter(HANDLER_INDEX_TO_ACTION); - registerFollowerIndexRoutes({ - __LEGACY: {}, - router: routeRegistry.router, - }); -}; - -/** - * Queue to save request response and errors - * It allows us to fake multiple responses from the - * callWithRequestFactory() when the request handler call it - * multiple times. - */ -let requestResponseQueue = []; - -/** - * Helper to mock the response from the call to Elasticsearch - * - * @param {*} err The mock error to throw - * @param {*} response The response to return - */ -const setHttpRequestResponse = (error, response) => { - requestResponseQueue.push({ error, response }); -}; - -const resetHttpRequestResponses = () => (requestResponseQueue = []); - -const getNextResponseFromQueue = () => { - if (!requestResponseQueue.length) { - return null; - } - - const next = requestResponseQueue.shift(); - if (next.error) { - return Promise.reject(next.error); - } - return Promise.resolve(next.response); -}; - -describe('[CCR API Routes] Follower Index', () => { - let routeHandler; - - beforeAll(() => { - isEsErrorFactory.mockReturnValue(() => false); - callWithRequestFactory.mockReturnValue(getNextResponseFromQueue); - registerHandlers(); - }); - - describe('list()', () => { - beforeEach(() => { - routeHandler = routeRegistry.getRoutes().list; - }); - - it('deserializes the response from Elasticsearch', async () => { - const totalResult = 2; - const infoResult = getFollowerIndexListInfoMock(totalResult); - const statsResult = getFollowerIndexListStatsMock( - totalResult, - infoResult.follower_indices.map(index => index.follower_index) - ); - setHttpRequestResponse(null, infoResult); - setHttpRequestResponse(null, statsResult); - - const { - options: { body: response }, - } = await callRoute(routeHandler); - const followerIndex = response.indices[0]; - - expect(response.indices.length).toEqual(totalResult); - expect(Object.keys(followerIndex)).toEqual(DESERIALIZED_KEYS); - }); - }); - - describe('get()', () => { - beforeEach(() => { - routeHandler = routeRegistry.getRoutes().get; - }); - - it('should return a single resource even though ES return an array with 1 item', async () => { - const mockId = 'test1'; - const followerIndexInfo = getFollowerIndexInfoMock(mockId); - const followerIndexStats = getFollowerIndexStatsMock(mockId); - - setHttpRequestResponse(null, { follower_indices: [followerIndexInfo] }); - setHttpRequestResponse(null, { indices: [followerIndexStats] }); - - const { - options: { body: response }, - } = await callRoute(routeHandler, {}, { params: { id: mockId } }); - expect(Object.keys(response)).toEqual(DESERIALIZED_KEYS); - }); - }); - - describe('create()', () => { - beforeEach(() => { - resetHttpRequestResponses(); - routeHandler = routeRegistry.getRoutes().create; - }); - - it('should return 200 status when follower index is created', async () => { - setHttpRequestResponse(null, { acknowledge: true }); - - const response = await callRoute( - routeHandler, - {}, - { - body: { - name: 'follower_index', - remoteCluster: 'remote_cluster', - leaderIndex: 'leader_index', - }, - } - ); - - expect(response.options.body).toEqual({ acknowledge: true }); - }); - }); - - describe('pause()', () => { - beforeEach(() => { - resetHttpRequestResponses(); - routeHandler = routeRegistry.getRoutes().pause; - }); - - it('should pause a single item', async () => { - setHttpRequestResponse(null, { acknowledge: true }); - - const { - options: { body: response }, - } = await callRoute(routeHandler, {}, { params: { id: '1' } }); - - expect(response.itemsPaused).toEqual(['1']); - expect(response.errors).toEqual([]); - }); - - it('should accept a list of ids to pause', async () => { - setHttpRequestResponse(null, { acknowledge: true }); - setHttpRequestResponse(null, { acknowledge: true }); - setHttpRequestResponse(null, { acknowledge: true }); - - const response = await callRoute(routeHandler, {}, { params: { id: '1,2,3' } }); - - expect(response.options.body.itemsPaused).toEqual(['1', '2', '3']); - }); - - it('should catch error and return them in array', async () => { - const error = new Error('something went wrong'); - error.response = '{ "error": {} }'; - - setHttpRequestResponse(null, { acknowledge: true }); - setHttpRequestResponse(error); - - const { - options: { body: response }, - } = await callRoute(routeHandler, {}, { params: { id: '1,2' } }); - - expect(response.itemsPaused).toEqual(['1']); - expect(response.errors[0].id).toEqual('2'); - }); - }); - - describe('resume()', () => { - beforeEach(() => { - resetHttpRequestResponses(); - routeHandler = routeRegistry.getRoutes().resume; - }); - - it('should resume a single item', async () => { - setHttpRequestResponse(null, { acknowledge: true }); - - const { - options: { body: response }, - } = await callRoute(routeHandler, {}, { params: { id: '1' } }); - - expect(response.itemsResumed).toEqual(['1']); - expect(response.errors).toEqual([]); - }); - - it('should accept a list of ids to resume', async () => { - setHttpRequestResponse(null, { acknowledge: true }); - setHttpRequestResponse(null, { acknowledge: true }); - setHttpRequestResponse(null, { acknowledge: true }); - - const response = await callRoute(routeHandler, {}, { params: { id: '1,2,3' } }); - - expect(response.options.body.itemsResumed).toEqual(['1', '2', '3']); - }); - - it('should catch error and return them in array', async () => { - const error = new Error('something went wrong'); - error.response = '{ "error": {} }'; - - setHttpRequestResponse(null, { acknowledge: true }); - setHttpRequestResponse(error); - - const { - options: { body: response }, - } = await callRoute(routeHandler, {}, { params: { id: '1,2' } }); - - expect(response.itemsResumed).toEqual(['1']); - expect(response.errors[0].id).toEqual('2'); - }); - }); - - describe('unfollow()', () => { - beforeEach(() => { - resetHttpRequestResponses(); - routeHandler = routeRegistry.getRoutes().unfollow; - }); - - it('should unfollow await single item', async () => { - setHttpRequestResponse(null, { acknowledge: true }); - setHttpRequestResponse(null, { acknowledge: true }); - setHttpRequestResponse(null, { acknowledge: true }); - setHttpRequestResponse(null, { acknowledge: true }); - - const { - options: { body: response }, - } = await callRoute(routeHandler, {}, { params: { id: '1' } }); - - expect(response.itemsUnfollowed).toEqual(['1']); - expect(response.errors).toEqual([]); - }); - - it('should accept a list of ids to unfollow', async () => { - setHttpRequestResponse(null, { acknowledge: true }); - setHttpRequestResponse(null, { acknowledge: true }); - setHttpRequestResponse(null, { acknowledge: true }); - setHttpRequestResponse(null, { acknowledge: true }); - setHttpRequestResponse(null, { acknowledge: true }); - setHttpRequestResponse(null, { acknowledge: true }); - setHttpRequestResponse(null, { acknowledge: true }); - setHttpRequestResponse(null, { acknowledge: true }); - setHttpRequestResponse(null, { acknowledge: true }); - setHttpRequestResponse(null, { acknowledge: true }); - setHttpRequestResponse(null, { acknowledge: true }); - setHttpRequestResponse(null, { acknowledge: true }); - - const response = await callRoute(routeHandler, {}, { params: { id: '1,2,3' } }); - - expect(response.options.body.itemsUnfollowed).toEqual(['1', '2', '3']); - }); - - it('should catch error and return them in array', async () => { - const error = new Error('something went wrong'); - error.response = '{ "error": {} }'; - - setHttpRequestResponse(null, { acknowledge: true }); - setHttpRequestResponse(null, { acknowledge: true }); - setHttpRequestResponse(null, { acknowledge: true }); - setHttpRequestResponse(null, { acknowledge: true }); - setHttpRequestResponse(null, { acknowledge: true }); - setHttpRequestResponse(error); - - const { - options: { body: response }, - } = await callRoute(routeHandler, {}, { params: { id: '1,2' } }); - - expect(response.itemsUnfollowed).toEqual(['1']); - expect(response.errors[0].id).toEqual('2'); - }); - }); -}); diff --git a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/routes/api/__jest__/helpers.ts b/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/routes/api/__jest__/helpers.ts deleted file mode 100644 index 555fc0937c0ad..0000000000000 --- a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/routes/api/__jest__/helpers.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { RequestHandler } from 'src/core/server'; -import { kibanaResponseFactory } from '../../../../../../../../../src/core/server'; - -export const callRoute = ( - route: RequestHandler, - ctx = {}, - request = {}, - response = kibanaResponseFactory -) => { - return route(ctx as any, request as any, response); -}; - -export const createRouter = (indexToActionMap: Record) => { - let index = 0; - const routeHandlers: Record> = {}; - const addHandler = (ignoreCtxForNow: any, handler: RequestHandler) => { - // Save handler and increment index - routeHandlers[indexToActionMap[index]] = handler; - index++; - }; - - return { - getRoutes: () => routeHandlers, - router: { - get: addHandler, - post: addHandler, - put: addHandler, - delete: addHandler, - }, - }; -}; diff --git a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/routes/api/auto_follow_pattern.ts b/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/routes/api/auto_follow_pattern.ts deleted file mode 100644 index d458f1ccb354b..0000000000000 --- a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/routes/api/auto_follow_pattern.ts +++ /dev/null @@ -1,301 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import { schema } from '@kbn/config-schema'; -// @ts-ignore -import { callWithRequestFactory } from '../../lib/call_with_request_factory'; -import { isEsError } from '../../lib/is_es_error'; -// @ts-ignore -import { - deserializeAutoFollowPattern, - deserializeListAutoFollowPatterns, - serializeAutoFollowPattern, - // @ts-ignore -} from '../../../../common/services/auto_follow_pattern_serialization'; - -import { licensePreRoutingFactory } from '../../lib/license_pre_routing_factory'; -import { API_BASE_PATH } from '../../../../common/constants'; - -import { RouteDependencies } from '../types'; -import { mapErrorToKibanaHttpResponse } from '../map_to_kibana_http_error'; - -export const registerAutoFollowPatternRoutes = ({ router, __LEGACY }: RouteDependencies) => { - /** - * Returns a list of all auto-follow patterns - */ - router.get( - { - path: `${API_BASE_PATH}/auto_follow_patterns`, - validate: false, - }, - licensePreRoutingFactory({ - __LEGACY, - requestHandler: async (ctx, request, response) => { - const callWithRequest = callWithRequestFactory(__LEGACY.server, request); - - try { - const result = await callWithRequest('ccr.autoFollowPatterns'); - return response.ok({ - body: { - patterns: deserializeListAutoFollowPatterns(result.patterns), - }, - }); - } catch (err) { - return mapErrorToKibanaHttpResponse(err); - } - }, - }) - ); - - /** - * Create an auto-follow pattern - */ - router.post( - { - path: `${API_BASE_PATH}/auto_follow_patterns`, - validate: { - body: schema.object( - { - id: schema.string(), - }, - { unknowns: 'allow' } - ), - }, - }, - licensePreRoutingFactory({ - __LEGACY, - requestHandler: async (ctx, request, response) => { - const callWithRequest = callWithRequestFactory(__LEGACY.server, request); - const { id, ...rest } = request.body; - const body = serializeAutoFollowPattern(rest); - - /** - * First let's make sur that an auto-follow pattern with - * the same id does not exist. - */ - try { - await callWithRequest('ccr.autoFollowPattern', { id }); - // If we get here it means that an auto-follow pattern with the same id exists - return response.conflict({ - body: `An auto-follow pattern with the name "${id}" already exists.`, - }); - } catch (err) { - if (err.statusCode !== 404) { - return mapErrorToKibanaHttpResponse(err); - } - } - - try { - return response.ok({ - body: await callWithRequest('ccr.saveAutoFollowPattern', { id, body }), - }); - } catch (err) { - return mapErrorToKibanaHttpResponse(err); - } - }, - }) - ); - - /** - * Update an auto-follow pattern - */ - router.put( - { - path: `${API_BASE_PATH}/auto_follow_patterns/{id}`, - validate: { - params: schema.object({ - id: schema.string(), - }), - body: schema.object({}, { unknowns: 'allow' }), - }, - }, - licensePreRoutingFactory({ - __LEGACY, - requestHandler: async (ctx, request, response) => { - const callWithRequest = callWithRequestFactory(__LEGACY.server, request); - const { id } = request.params; - const body = serializeAutoFollowPattern(request.body); - - try { - return response.ok({ - body: await callWithRequest('ccr.saveAutoFollowPattern', { id, body }), - }); - } catch (err) { - return mapErrorToKibanaHttpResponse(err); - } - }, - }) - ); - - /** - * Returns a single auto-follow pattern - */ - router.get( - { - path: `${API_BASE_PATH}/auto_follow_patterns/{id}`, - validate: { - params: schema.object({ - id: schema.string(), - }), - }, - }, - licensePreRoutingFactory({ - __LEGACY, - requestHandler: async (ctx, request, response) => { - const callWithRequest = callWithRequestFactory(__LEGACY.server, request); - const { id } = request.params; - - try { - const result = await callWithRequest('ccr.autoFollowPattern', { id }); - const autoFollowPattern = result.patterns[0]; - - return response.ok({ - body: deserializeAutoFollowPattern(autoFollowPattern), - }); - } catch (err) { - return mapErrorToKibanaHttpResponse(err); - } - }, - }) - ); - - /** - * Delete an auto-follow pattern - */ - router.delete( - { - path: `${API_BASE_PATH}/auto_follow_patterns/{id}`, - validate: { - params: schema.object({ - id: schema.string(), - }), - }, - }, - licensePreRoutingFactory({ - __LEGACY, - requestHandler: async (ctx, request, response) => { - const callWithRequest = callWithRequestFactory(__LEGACY.server, request); - const { id } = request.params; - const ids = id.split(','); - - const itemsDeleted: string[] = []; - const errors: Array<{ id: string; error: any }> = []; - - await Promise.all( - ids.map(_id => - callWithRequest('ccr.deleteAutoFollowPattern', { id: _id }) - .then(() => itemsDeleted.push(_id)) - .catch((err: Error) => { - if (isEsError(err)) { - errors.push({ id: _id, error: mapErrorToKibanaHttpResponse(err) }); - } else { - errors.push({ id: _id, error: mapErrorToKibanaHttpResponse(err) }); - } - }) - ) - ); - - return response.ok({ - body: { - itemsDeleted, - errors, - }, - }); - }, - }) - ); - - /** - * Pause auto-follow pattern(s) - */ - router.post( - { - path: `${API_BASE_PATH}/auto_follow_patterns/{id}/pause`, - validate: { - params: schema.object({ - id: schema.string(), - }), - }, - }, - licensePreRoutingFactory({ - __LEGACY, - requestHandler: async (ctx, request, response) => { - const callWithRequest = callWithRequestFactory(__LEGACY.server, request); - const { id } = request.params; - const ids = id.split(','); - - const itemsPaused: string[] = []; - const errors: Array<{ id: string; error: any }> = []; - - await Promise.all( - ids.map(_id => - callWithRequest('ccr.pauseAutoFollowPattern', { id: _id }) - .then(() => itemsPaused.push(_id)) - .catch((err: Error) => { - if (isEsError(err)) { - errors.push({ id: _id, error: mapErrorToKibanaHttpResponse(err) }); - } else { - errors.push({ id: _id, error: mapErrorToKibanaHttpResponse(err) }); - } - }) - ) - ); - - return response.ok({ - body: { - itemsPaused, - errors, - }, - }); - }, - }) - ); - - /** - * Resume auto-follow pattern(s) - */ - router.post( - { - path: `${API_BASE_PATH}/auto_follow_patterns/{id}/resume`, - validate: { - params: schema.object({ - id: schema.string(), - }), - }, - }, - licensePreRoutingFactory({ - __LEGACY, - requestHandler: async (ctx, request, response) => { - const callWithRequest = callWithRequestFactory(__LEGACY.server, request); - const { id } = request.params; - const ids = id.split(','); - - const itemsResumed: string[] = []; - const errors: Array<{ id: string; error: any }> = []; - - await Promise.all( - ids.map(_id => - callWithRequest('ccr.resumeAutoFollowPattern', { id: _id }) - .then(() => itemsResumed.push(_id)) - .catch((err: Error) => { - if (isEsError(err)) { - errors.push({ id: _id, error: mapErrorToKibanaHttpResponse(err) }); - } else { - errors.push({ id: _id, error: mapErrorToKibanaHttpResponse(err) }); - } - }) - ) - ); - - return response.ok({ - body: { - itemsResumed, - errors, - }, - }); - }, - }) - ); -}; diff --git a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/routes/api/ccr.ts b/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/routes/api/ccr.ts deleted file mode 100644 index b08b056ad2c8a..0000000000000 --- a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/routes/api/ccr.ts +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { API_BASE_PATH } from '../../../../common/constants'; -// @ts-ignore -import { callWithRequestFactory } from '../../lib/call_with_request_factory'; -// @ts-ignore -import { deserializeAutoFollowStats } from '../../lib/ccr_stats_serialization'; -import { licensePreRoutingFactory } from '../../lib/license_pre_routing_factory'; - -import { mapErrorToKibanaHttpResponse } from '../map_to_kibana_http_error'; -import { RouteDependencies } from '../types'; - -export const registerCcrRoutes = ({ router, __LEGACY }: RouteDependencies) => { - /** - * Returns Auto-follow stats - */ - router.get( - { - path: `${API_BASE_PATH}/stats/auto_follow`, - validate: false, - }, - licensePreRoutingFactory({ - __LEGACY, - requestHandler: async (ctx, request, response) => { - const callWithRequest = callWithRequestFactory(__LEGACY.server, request); - - try { - const { auto_follow_stats: autoFollowStats } = await callWithRequest('ccr.stats'); - - return response.ok({ - body: deserializeAutoFollowStats(autoFollowStats), - }); - } catch (err) { - return mapErrorToKibanaHttpResponse(err); - } - }, - }) - ); - - /** - * Returns whether the user has CCR permissions - */ - router.get( - { - path: `${API_BASE_PATH}/permissions`, - validate: false, - }, - licensePreRoutingFactory({ - __LEGACY, - requestHandler: async (ctx, request, response) => { - const xpackMainPlugin = __LEGACY.server.plugins.xpack_main; - const xpackInfo = xpackMainPlugin && xpackMainPlugin.info; - - if (!xpackInfo) { - // xpackInfo is updated via poll, so it may not be available until polling has begun. - // In this rare situation, tell the client the service is temporarily unavailable. - return response.customError({ - statusCode: 503, - body: 'Security info unavailable', - }); - } - - const securityInfo = xpackInfo && xpackInfo.isAvailable() && xpackInfo.feature('security'); - if (!securityInfo || !securityInfo.isAvailable() || !securityInfo.isEnabled()) { - // If security isn't enabled or available (in the case where security is enabled but license reverted to Basic) let the user use CCR. - return response.ok({ - body: { - hasPermission: true, - missingClusterPrivileges: [], - }, - }); - } - - const callWithRequest = callWithRequestFactory(__LEGACY.server, request); - - try { - const { has_all_requested: hasPermission, cluster } = await callWithRequest( - 'ccr.permissions', - { - body: { - cluster: ['manage', 'manage_ccr'], - }, - } - ); - - const missingClusterPrivileges = Object.keys(cluster).reduce( - (permissions: any, permissionName: any) => { - if (!cluster[permissionName]) { - permissions.push(permissionName); - return permissions; - } - }, - [] as any[] - ); - - return response.ok({ - body: { - hasPermission, - missingClusterPrivileges, - }, - }); - } catch (err) { - return mapErrorToKibanaHttpResponse(err); - } - }, - }) - ); -}; diff --git a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/routes/api/follower_index.ts b/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/routes/api/follower_index.ts deleted file mode 100644 index 1d7dacf4a8688..0000000000000 --- a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/routes/api/follower_index.ts +++ /dev/null @@ -1,357 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import { schema } from '@kbn/config-schema'; -import { - deserializeFollowerIndex, - deserializeListFollowerIndices, - serializeFollowerIndex, - serializeAdvancedSettings, - // @ts-ignore -} from '../../../../common/services/follower_index_serialization'; -import { API_BASE_PATH } from '../../../../common/constants'; -// @ts-ignore -import { removeEmptyFields } from '../../../../common/services/utils'; -// @ts-ignore -import { callWithRequestFactory } from '../../lib/call_with_request_factory'; -import { licensePreRoutingFactory } from '../../lib/license_pre_routing_factory'; - -import { RouteDependencies } from '../types'; -import { mapErrorToKibanaHttpResponse } from '../map_to_kibana_http_error'; - -export const registerFollowerIndexRoutes = ({ router, __LEGACY }: RouteDependencies) => { - /** - * Returns a list of all follower indices - */ - router.get( - { - path: `${API_BASE_PATH}/follower_indices`, - validate: false, - }, - licensePreRoutingFactory({ - __LEGACY, - requestHandler: async (ctx, request, response) => { - const callWithRequest = callWithRequestFactory(__LEGACY.server, request); - - try { - const { follower_indices: followerIndices } = await callWithRequest('ccr.info', { - id: '_all', - }); - - const { - follow_stats: { indices: followerIndicesStats }, - } = await callWithRequest('ccr.stats'); - - const followerIndicesStatsMap = followerIndicesStats.reduce((map: any, stats: any) => { - map[stats.index] = stats; - return map; - }, {}); - - const collatedFollowerIndices = followerIndices.map((followerIndex: any) => { - return { - ...followerIndex, - ...followerIndicesStatsMap[followerIndex.follower_index], - }; - }); - - return response.ok({ - body: { - indices: deserializeListFollowerIndices(collatedFollowerIndices), - }, - }); - } catch (err) { - return mapErrorToKibanaHttpResponse(err); - } - }, - }) - ); - - /** - * Returns a single follower index pattern - */ - router.get( - { - path: `${API_BASE_PATH}/follower_indices/{id}`, - validate: { - params: schema.object({ - id: schema.string(), - }), - }, - }, - licensePreRoutingFactory({ - __LEGACY, - requestHandler: async (ctx, request, response) => { - const callWithRequest = callWithRequestFactory(__LEGACY.server, request); - const { id } = request.params; - - try { - const { follower_indices: followerIndices } = await callWithRequest('ccr.info', { id }); - - const followerIndexInfo = followerIndices && followerIndices[0]; - - if (!followerIndexInfo) { - return response.notFound({ - body: `The follower index "${id}" does not exist.`, - }); - } - - // If this follower is paused, skip call to ES stats api since it will return 404 - if (followerIndexInfo.status === 'paused') { - return response.ok({ - body: deserializeFollowerIndex({ - ...followerIndexInfo, - }), - }); - } else { - const { - indices: followerIndicesStats, - } = await callWithRequest('ccr.followerIndexStats', { id }); - - return response.ok({ - body: deserializeFollowerIndex({ - ...followerIndexInfo, - ...(followerIndicesStats ? followerIndicesStats[0] : {}), - }), - }); - } - } catch (err) { - return mapErrorToKibanaHttpResponse(err); - } - }, - }) - ); - - /** - * Create a follower index - */ - router.post( - { - path: `${API_BASE_PATH}/follower_indices`, - validate: { - body: schema.object( - { - name: schema.string(), - }, - { unknowns: 'allow' } - ), - }, - }, - licensePreRoutingFactory({ - __LEGACY, - requestHandler: async (ctx, request, response) => { - const callWithRequest = callWithRequestFactory(__LEGACY.server, request); - const { name, ...rest } = request.body; - const body = removeEmptyFields(serializeFollowerIndex(rest)); - - try { - return response.ok({ - body: await callWithRequest('ccr.saveFollowerIndex', { name, body }), - }); - } catch (err) { - return mapErrorToKibanaHttpResponse(err); - } - }, - }) - ); - - /** - * Edit a follower index - */ - router.put( - { - path: `${API_BASE_PATH}/follower_indices/{id}`, - validate: { - params: schema.object({ id: schema.string() }), - body: schema.object({ - maxReadRequestOperationCount: schema.maybe(schema.number()), - maxOutstandingReadRequests: schema.maybe(schema.number()), - maxReadRequestSize: schema.maybe(schema.string()), // byte value - maxWriteRequestOperationCount: schema.maybe(schema.number()), - maxWriteRequestSize: schema.maybe(schema.string()), // byte value - maxOutstandingWriteRequests: schema.maybe(schema.number()), - maxWriteBufferCount: schema.maybe(schema.number()), - maxWriteBufferSize: schema.maybe(schema.string()), // byte value - maxRetryDelay: schema.maybe(schema.string()), // time value - readPollTimeout: schema.maybe(schema.string()), // time value - }), - }, - }, - licensePreRoutingFactory({ - __LEGACY, - requestHandler: async (ctx, request, response) => { - const callWithRequest = callWithRequestFactory(__LEGACY.server, request); - const { id } = request.params; - - // We need to first pause the follower and then resume it passing the advanced settings - try { - const { follower_indices: followerIndices } = await callWithRequest('ccr.info', { id }); - const followerIndexInfo = followerIndices && followerIndices[0]; - if (!followerIndexInfo) { - return response.notFound({ body: `The follower index "${id}" does not exist.` }); - } - - // Retrieve paused state instead of pulling it from the payload to ensure it's not stale. - const isPaused = followerIndexInfo.status === 'paused'; - // Pause follower if not already paused - if (!isPaused) { - await callWithRequest('ccr.pauseFollowerIndex', { id }); - } - - // Resume follower - const body = removeEmptyFields(serializeAdvancedSettings(request.body)); - return response.ok({ - body: await callWithRequest('ccr.resumeFollowerIndex', { id, body }), - }); - } catch (err) { - return mapErrorToKibanaHttpResponse(err); - } - }, - }) - ); - - /** - * Pauses a follower index - */ - router.put( - { - path: `${API_BASE_PATH}/follower_indices/{id}/pause`, - validate: { - params: schema.object({ id: schema.string() }), - }, - }, - licensePreRoutingFactory({ - __LEGACY, - requestHandler: async (ctx, request, response) => { - const callWithRequest = callWithRequestFactory(__LEGACY.server, request); - const { id } = request.params; - const ids = id.split(','); - - const itemsPaused: string[] = []; - const errors: Array<{ id: string; error: any }> = []; - - await Promise.all( - ids.map(_id => - callWithRequest('ccr.pauseFollowerIndex', { id: _id }) - .then(() => itemsPaused.push(_id)) - .catch((err: Error) => { - errors.push({ id: _id, error: mapErrorToKibanaHttpResponse(err) }); - }) - ) - ); - - return response.ok({ - body: { - itemsPaused, - errors, - }, - }); - }, - }) - ); - - /** - * Resumes a follower index - */ - router.put( - { - path: `${API_BASE_PATH}/follower_indices/{id}/resume`, - validate: { - params: schema.object({ id: schema.string() }), - }, - }, - licensePreRoutingFactory({ - __LEGACY, - requestHandler: async (ctx, request, response) => { - const callWithRequest = callWithRequestFactory(__LEGACY.server, request); - const { id } = request.params; - const ids = id.split(','); - - const itemsResumed: string[] = []; - const errors: Array<{ id: string; error: any }> = []; - - await Promise.all( - ids.map(_id => - callWithRequest('ccr.resumeFollowerIndex', { id: _id }) - .then(() => itemsResumed.push(_id)) - .catch((err: Error) => { - errors.push({ id: _id, error: mapErrorToKibanaHttpResponse(err) }); - }) - ) - ); - - return response.ok({ - body: { - itemsResumed, - errors, - }, - }); - }, - }) - ); - - /** - * Unfollow follower index's leader index - */ - router.put( - { - path: `${API_BASE_PATH}/follower_indices/{id}/unfollow`, - validate: { - params: schema.object({ id: schema.string() }), - }, - }, - licensePreRoutingFactory({ - __LEGACY, - requestHandler: async (ctx, request, response) => { - const callWithRequest = callWithRequestFactory(__LEGACY.server, request); - const { id } = request.params; - const ids = id.split(','); - - const itemsUnfollowed: string[] = []; - const itemsNotOpen: string[] = []; - const errors: Array<{ id: string; error: any }> = []; - - await Promise.all( - ids.map(async _id => { - try { - // Try to pause follower, let it fail silently since it may already be paused - try { - await callWithRequest('ccr.pauseFollowerIndex', { id: _id }); - } catch (e) { - // Swallow errors - } - - // Close index - await callWithRequest('indices.close', { index: _id }); - - // Unfollow leader - await callWithRequest('ccr.unfollowLeaderIndex', { id: _id }); - - // Try to re-open the index, store failures in a separate array to surface warnings in the UI - // This will allow users to query their index normally after unfollowing - try { - await callWithRequest('indices.open', { index: _id }); - } catch (e) { - itemsNotOpen.push(_id); - } - - // Push success - itemsUnfollowed.push(_id); - } catch (err) { - errors.push({ id: _id, error: mapErrorToKibanaHttpResponse(err) }); - } - }) - ); - - return response.ok({ - body: { - itemsUnfollowed, - itemsNotOpen, - errors, - }, - }); - }, - }) - ); -}; diff --git a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/routes/map_to_kibana_http_error.ts b/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/routes/map_to_kibana_http_error.ts deleted file mode 100644 index 6a81bd26dc47d..0000000000000 --- a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/routes/map_to_kibana_http_error.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { kibanaResponseFactory } from '../../../../../../../src/core/server'; -// @ts-ignore -import { wrapEsError } from '../lib/error_wrappers'; -import { isEsError } from '../lib/is_es_error'; - -export const mapErrorToKibanaHttpResponse = (err: any) => { - if (isEsError(err)) { - const { statusCode, message, body } = wrapEsError(err); - return kibanaResponseFactory.customError({ - statusCode, - body: { - message, - attributes: { - cause: body?.cause, - }, - }, - }); - } - return kibanaResponseFactory.internalError(err); -}; diff --git a/x-pack/legacy/plugins/monitoring/README.md b/x-pack/legacy/plugins/monitoring/README.md index 3659f4d2fa0f7..e9ececa8c6350 100644 --- a/x-pack/legacy/plugins/monitoring/README.md +++ b/x-pack/legacy/plugins/monitoring/README.md @@ -72,8 +72,8 @@ cluster. 1. Set the Kibana config: ``` % cat config/kibana.dev.yml - xpack.monitoring.elasticsearch: - url: "http://localhost:9210" + monitoring.ui.elasticsearch: + hosts: "http://localhost:9210" username: "kibana" password: "changeme" ``` diff --git a/x-pack/legacy/plugins/monitoring/public/components/apm/instances/instances.js b/x-pack/legacy/plugins/monitoring/public/components/apm/instances/instances.js index f3a888bf9e905..a48fbc51341f1 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/apm/instances/instances.js +++ b/x-pack/legacy/plugins/monitoring/public/components/apm/instances/instances.js @@ -18,6 +18,7 @@ import { } from '@elastic/eui'; import { Status } from './status'; import { formatMetric } from '../../../lib/format_number'; +import { getSafeForExternalLink } from '../../../lib/get_safe_for_external_link'; import { formatTimestampToDuration } from '../../../../common'; import { i18n } from '@kbn/i18n'; import { APM_SYSTEM_ID } from '../../../../common/constants'; @@ -56,7 +57,10 @@ function getColumns(setupMode) { return ( - + {name} {setupModeStatus} diff --git a/x-pack/legacy/plugins/monitoring/public/components/beats/listing/listing.js b/x-pack/legacy/plugins/monitoring/public/components/beats/listing/listing.js index dfc9117ef48bc..a20728eb9a58f 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/beats/listing/listing.js +++ b/x-pack/legacy/plugins/monitoring/public/components/beats/listing/listing.js @@ -19,13 +19,13 @@ import { formatMetric } from 'plugins/monitoring/lib/format_number'; import { EuiMonitoringTable } from 'plugins/monitoring/components/table'; import { i18n } from '@kbn/i18n'; import { BEATS_SYSTEM_ID } from '../../../../common/constants'; +import { getSafeForExternalLink } from '../../../lib/get_safe_for_external_link'; import { ListingCallOut } from '../../setup_mode/listing_callout'; import { SetupModeBadge } from '../../setup_mode/badge'; import { FormattedMessage } from '@kbn/i18n/react'; export class Listing extends PureComponent { getColumns() { - const { kbnUrl, scope } = this.props.angular; const setupMode = this.props.setupMode; return [ @@ -59,11 +59,7 @@ export class Listing extends PureComponent { return (
{ - scope.$evalAsync(() => { - kbnUrl.changePath(`/beats/beat/${beat.uuid}`); - }); - }} + href={getSafeForExternalLink(`#/beats/beat/${beat.uuid}`)} data-test-subj={`beatLink-${name}`} > {name} diff --git a/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/elasticsearch_panel.js b/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/elasticsearch_panel.js index 7b08c89f53881..48ff8edaafe60 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/elasticsearch_panel.js +++ b/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/elasticsearch_panel.js @@ -32,6 +32,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { Reason } from '../../logs/reason'; import { SetupModeTooltip } from '../../setup_mode/tooltip'; +import { getSafeForExternalLink } from '../../../lib/get_safe_for_external_link'; import { ELASTICSEARCH_SYSTEM_ID } from '../../../../common/constants'; const calculateShards = shards => { @@ -168,7 +169,7 @@ export function ElasticsearchPanel(props) { const showMlJobs = () => { // if license doesn't support ML, then `ml === null` if (props.ml) { - const gotoURL = '#/elasticsearch/ml_jobs'; + const gotoURL = getSafeForExternalLink('#/elasticsearch/ml_jobs'); return ( <> diff --git a/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/license_text.js b/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/license_text.js index c6fb386c755f3..012c81e63931e 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/license_text.js +++ b/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/license_text.js @@ -6,6 +6,7 @@ import React from 'react'; import moment from 'moment-timezone'; +import { getSafeForExternalLink } from '../../../lib/get_safe_for_external_link'; import { capitalize } from 'lodash'; import { EuiLink } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -18,7 +19,7 @@ export function LicenseText({ license, showLicenseExpiration }) { } return ( - + { return ( - + {shardId} ); diff --git a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/indices/indices.js b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/indices/indices.js index f8dd7b0af7a17..a73dfbf8cd321 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/indices/indices.js +++ b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/indices/indices.js @@ -8,6 +8,7 @@ import React from 'react'; import { capitalize } from 'lodash'; import { LARGE_FLOAT, LARGE_BYTES, LARGE_ABBREVIATED } from '../../../../common/formatting'; import { formatMetric } from '../../../lib/format_number'; +import { getSafeForExternalLink } from '../../../lib/get_safe_for_external_link'; import { ElasticsearchStatusIcon } from '../status_icon'; import { ClusterStatus } from '../cluster_status'; import { EuiMonitoringTable } from '../../table'; @@ -34,7 +35,10 @@ const columns = [ sortable: true, render: value => (
- + {value}
diff --git a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js index d9cf29f73ce0d..9e6252315f429 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js +++ b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js @@ -7,6 +7,7 @@ import React, { Fragment } from 'react'; import { NodeStatusIcon } from '../node'; import { extractIp } from '../../../lib/extract_ip'; // TODO this is only used for elasticsearch nodes summary / node detail, so it should be moved to components/elasticsearch/nodes/lib +import { getSafeForExternalLink } from '../../../lib/get_safe_for_external_link'; import { ClusterStatus } from '../cluster_status'; import { EuiMonitoringSSPTable } from '../../table'; import { MetricCell, OfflineCell } from './cells'; @@ -75,7 +76,7 @@ const getColumns = (showCgroupMetricsElasticsearch, setupMode, clusterUuid) => { render: (value, node) => { let nameLink = ( {value} diff --git a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/shard_activity/recovery_index.js b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/shard_activity/recovery_index.js index 7a9d096437d52..27598bee6d841 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/shard_activity/recovery_index.js +++ b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/shard_activity/recovery_index.js @@ -8,13 +8,14 @@ import React from 'react'; import { EuiLink } from '@elastic/eui'; import { Snapshot } from './snapshot'; import { FormattedMessage } from '@kbn/i18n/react'; +import { getSafeForExternalLink } from '../../../lib/get_safe_for_external_link'; export const RecoveryIndex = props => { const { name, shard, relocationType } = props; return (
- {name} + {name}
{ return (
{name} diff --git a/x-pack/legacy/plugins/monitoring/public/components/no_data/explanations/collection_enabled/__tests__/__snapshots__/collection_enabled.test.js.snap b/x-pack/legacy/plugins/monitoring/public/components/no_data/explanations/collection_enabled/__tests__/__snapshots__/collection_enabled.test.js.snap index 43ff42b6a80e8..fa266b30fcb94 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/no_data/explanations/collection_enabled/__tests__/__snapshots__/collection_enabled.test.js.snap +++ b/x-pack/legacy/plugins/monitoring/public/components/no_data/explanations/collection_enabled/__tests__/__snapshots__/collection_enabled.test.js.snap @@ -192,6 +192,7 @@ exports[`ExplainCollectionEnabled should explain about xpack.monitoring.collecti isCopyable={false} paddingSize="l" transparentBackground={false} + whiteSpace="pre-wrap" > { diff --git a/x-pack/legacy/plugins/cross_cluster_replication/common/constants/app.ts b/x-pack/legacy/plugins/monitoring/public/lib/get_safe_for_external_link.ts similarity index 66% rename from x-pack/legacy/plugins/cross_cluster_replication/common/constants/app.ts rename to x-pack/legacy/plugins/monitoring/public/lib/get_safe_for_external_link.ts index 4ce0a2f5644f3..6f3e398c1414f 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/common/constants/app.ts +++ b/x-pack/legacy/plugins/monitoring/public/lib/get_safe_for_external_link.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -export const APPS = { - CCR_APP: 'ccr', - REMOTE_CLUSTER_APP: 'remote_cluster', -}; +export function getSafeForExternalLink(url: string) { + return `${url.split('?')[0]}?${location.hash.split('?')[1]}`; +} diff --git a/x-pack/legacy/plugins/monitoring/public/np_imports/angular/modules.ts b/x-pack/legacy/plugins/monitoring/public/np_imports/angular/modules.ts index b506784bf15ee..a047c25c2b1d7 100644 --- a/x-pack/legacy/plugins/monitoring/public/np_imports/angular/modules.ts +++ b/x-pack/legacy/plugins/monitoring/public/np_imports/angular/modules.ts @@ -26,6 +26,7 @@ import { import { PromiseServiceCreator } from './providers/promises'; // @ts-ignore import { PrivateProvider } from './providers/private'; +import { getSafeForExternalLink } from '../../lib/get_safe_for_external_link'; type IPrivate = (provider: (...injectable: any[]) => T) => T; @@ -134,7 +135,8 @@ function createHrefModule(core: AppMountContext['core']) { pre: (_$scope, _$el, $attr) => { $attr.$observe(name, val => { if (val) { - $attr.$set('href', core.http.basePath.prepend(val as string)); + const url = getSafeForExternalLink(val as string); + $attr.$set('href', core.http.basePath.prepend(url)); } }); }, diff --git a/x-pack/legacy/plugins/siem/package.json b/x-pack/legacy/plugins/siem/package.json index 3a93beef963a0..c9b3f8a37a7ea 100644 --- a/x-pack/legacy/plugins/siem/package.json +++ b/x-pack/legacy/plugins/siem/package.json @@ -7,12 +7,10 @@ "scripts": {}, "devDependencies": { "@types/lodash": "^4.14.110", - "@types/js-yaml": "^3.12.1", - "@types/react-beautiful-dnd": "^12.1.1" + "@types/js-yaml": "^3.12.1" }, "dependencies": { "lodash": "^4.17.15", - "react-beautiful-dnd": "^12.2.0", "react-markdown": "^4.0.6" } } diff --git a/x-pack/legacy/plugins/siem/public/components/and_or_badge/index.tsx b/x-pack/legacy/plugins/siem/public/components/and_or_badge/index.tsx index 2fb270c284000..e2078bb2473f5 100644 --- a/x-pack/legacy/plugins/siem/public/components/and_or_badge/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/and_or_badge/index.tsx @@ -30,7 +30,7 @@ const RoundedBadge = styled(EuiBadge)` .euiBadge__text { text-overflow: clip; } -`; +` as any; // eslint-disable-line @typescript-eslint/no-explicit-any RoundedBadge.displayName = 'RoundedBadge'; diff --git a/x-pack/legacy/plugins/siem/public/components/draggables/index.tsx b/x-pack/legacy/plugins/siem/public/components/draggables/index.tsx index b3811d05eea04..cea900f7bccf9 100644 --- a/x-pack/legacy/plugins/siem/public/components/draggables/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/draggables/index.tsx @@ -118,7 +118,7 @@ DefaultDraggable.displayName = 'DefaultDraggable'; export const Badge = styled(EuiBadge)` vertical-align: top; -`; +` as any; // eslint-disable-line @typescript-eslint/no-explicit-any Badge.displayName = 'Badge'; diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/line_tool_tip_content.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/line_tool_tip_content.tsx index eff4769944765..ef2cd85667408 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/line_tool_tip_content.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/line_tool_tip_content.tsx @@ -20,7 +20,7 @@ import * as i18n from '../translations'; const FlowBadge = styled(EuiBadge)` height: 45px; min-width: 85px; -`; +` as any; // eslint-disable-line @typescript-eslint/no-explicit-any const EuiFlexGroupStyled = styled(EuiFlexGroup)` margin: 0 auto; diff --git a/x-pack/legacy/plugins/siem/public/components/flyout/index.tsx b/x-pack/legacy/plugins/siem/public/components/flyout/index.tsx index 44abe5b679c8e..b0f6494e2d663 100644 --- a/x-pack/legacy/plugins/siem/public/components/flyout/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/flyout/index.tsx @@ -25,7 +25,7 @@ export const Badge = styled(EuiBadge)` right: 0%; top: 0%; border-bottom-left-radius: 5px; -`; +` as any; // eslint-disable-line @typescript-eslint/no-explicit-any Badge.displayName = 'Badge'; diff --git a/x-pack/legacy/plugins/siem/public/components/header_page/index.tsx b/x-pack/legacy/plugins/siem/public/components/header_page/index.tsx index f88ffc3f3c6c4..acbb337a7c2ab 100644 --- a/x-pack/legacy/plugins/siem/public/components/header_page/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/header_page/index.tsx @@ -54,12 +54,13 @@ LinkBack.displayName = 'LinkBack'; const Badge = styled(EuiBadge)` letter-spacing: 0; -`; +` as any; // eslint-disable-line @typescript-eslint/no-explicit-any Badge.displayName = 'Badge'; interface BackOptions { href: LinkIconProps['href']; text: LinkIconProps['children']; + dataTestSubj?: string; } export interface HeaderPageProps extends HeaderProps { @@ -91,7 +92,11 @@ const HeaderPageComponent: React.FC = ({ {backOptions && ( - + {backOptions.text} diff --git a/x-pack/legacy/plugins/siem/public/components/header_page/title.tsx b/x-pack/legacy/plugins/siem/public/components/header_page/title.tsx index 59039ddd6a23b..47dd0dc55d703 100644 --- a/x-pack/legacy/plugins/siem/public/components/header_page/title.tsx +++ b/x-pack/legacy/plugins/siem/public/components/header_page/title.tsx @@ -19,7 +19,7 @@ StyledEuiBetaBadge.displayName = 'StyledEuiBetaBadge'; const Badge = styled(EuiBadge)` letter-spacing: 0; -`; +` as any; // eslint-disable-line @typescript-eslint/no-explicit-any Badge.displayName = 'Badge'; interface Props { diff --git a/x-pack/legacy/plugins/siem/public/components/link_icon/index.tsx b/x-pack/legacy/plugins/siem/public/components/link_icon/index.tsx index ba5874d42d515..36f57c46c1628 100644 --- a/x-pack/legacy/plugins/siem/public/components/link_icon/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/link_icon/index.tsx @@ -50,12 +50,14 @@ export interface LinkIconProps extends LinkProps { children: string; iconSize?: IconSize; iconType: IconType; + dataTestSubj?: string; } export const LinkIcon = React.memo( ({ children, color, + dataTestSubj, disabled, href, iconSide = 'left', @@ -67,6 +69,7 @@ export const LinkIcon = React.memo( { expect(store.getState().inputs.global.timerange.kind).toBe('relative'); }); - test('Make Sure it is last 15 minutes date', () => { - expect(store.getState().inputs.global.timerange.fromStr).toBe('now-15m'); + test('Make Sure it is last 24 hours date', () => { + expect(store.getState().inputs.global.timerange.fromStr).toBe('now-24h'); expect(store.getState().inputs.global.timerange.toStr).toBe('now'); }); @@ -180,7 +180,7 @@ describe('SIEM Super Date Picker', () => { ).toBe('Today'); }); - test('Today and Last 15 minutes are in Recently used date ranges', () => { + test('Today and Last 24 hours are in Recently used date ranges', () => { wrapper .find('[data-test-subj="superDatePickerToggleQuickMenuButton"]') .first() @@ -198,7 +198,7 @@ describe('SIEM Super Date Picker', () => { .find('div.euiQuickSelectPopover__section') .at(1) .text() - ).toBe('Last 15 minutesToday'); + ).toBe('Last 24 hoursToday'); }); test('Make sure that it does not add any duplicate if you click again on today', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_signature.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_signature.tsx index 24c52f3372d62..d05ab6bcc378f 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_signature.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_signature.tsx @@ -30,7 +30,7 @@ SignatureFlexItem.displayName = 'SignatureFlexItem'; const Badge = styled(EuiBadge)` vertical-align: top; -`; +` as any; // eslint-disable-line @typescript-eslint/no-explicit-any Badge.displayName = 'Badge'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_signature.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_signature.tsx index 39c21c4ffa33b..31525a4904bc2 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_signature.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_signature.tsx @@ -21,7 +21,7 @@ import * as i18n from './translations'; const Badge = styled(EuiBadge)` vertical-align: top; -`; +` as any; // eslint-disable-line @typescript-eslint/no-explicit-any Badge.displayName = 'Badge'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/empty.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/empty.tsx index 56639f90c1464..ddb07b4636b88 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/empty.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/empty.tsx @@ -26,7 +26,7 @@ const BadgeHighlighted = styled(EuiBadge)` margin: 0 5px 0 5px; maxwidth: 85px; minwidth: 85px; -`; +` as any; // eslint-disable-line @typescript-eslint/no-explicit-any BadgeHighlighted.displayName = 'BadgeHighlighted'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/provider_badge.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/provider_badge.tsx index 8e4665acc2c26..a5525df5ef3c3 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/provider_badge.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/provider_badge.tsx @@ -44,7 +44,7 @@ const ProviderBadgeStyled = styled(EuiBadge)` margin-right: 0; margin-left: 4px; } -`; +` as any; // eslint-disable-line @typescript-eslint/no-explicit-any ProviderBadgeStyled.displayName = 'ProviderBadgeStyled'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/provider_item_and_drag_drop.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/provider_item_and_drag_drop.tsx index 663b3dd501341..0ae952412a973 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/provider_item_and_drag_drop.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/provider_item_and_drag_drop.tsx @@ -56,7 +56,7 @@ DropAndTargetDataProviders.displayName = 'DropAndTargetDataProviders'; const NumberProviderAndBadge = styled(EuiBadge)` margin: 0px 5px; -`; +` as any; // eslint-disable-line @typescript-eslint/no-explicit-any NumberProviderAndBadge.displayName = 'NumberProviderAndBadge'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/insert_timeline_popover/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/insert_timeline_popover/index.test.tsx index e63bce388ae80..c5aea833a4b2f 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/insert_timeline_popover/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/insert_timeline_popover/index.test.tsx @@ -12,9 +12,13 @@ import routeData from 'react-router'; import { InsertTimelinePopoverComponent } from './'; const mockDispatch = jest.fn(); -jest.mock('react-redux', () => ({ - useDispatch: () => mockDispatch, -})); +jest.mock('react-redux', () => { + const reactRedux = jest.requireActual('react-redux'); + return { + ...reactRedux, + useDispatch: () => mockDispatch, + }; +}); const mockLocation = { pathname: '/apath', hash: '', diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/properties/helpers.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/properties/helpers.tsx index 6f7e1f782d3f6..bf953cfd006aa 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/properties/helpers.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/properties/helpers.tsx @@ -39,7 +39,7 @@ export const newTimelineToolTip = 'Create a new timeline'; const NotesCountBadge = styled(EuiBadge)` margin-left: 5px; -`; +` as any; // eslint-disable-line @typescript-eslint/no-explicit-any NotesCountBadge.displayName = 'NotesCountBadge'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/selectable_timeline/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/selectable_timeline/index.tsx index 49e3bc4017a10..639d30bbe7bb9 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/selectable_timeline/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/selectable_timeline/index.tsx @@ -147,7 +147,7 @@ const SelectableTimelineComponent: React.FC = ({ - + {isUntitled(option) ? i18nTimeline.UNTITLED_TIMELINE : option.title} diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/columns.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/columns.tsx index e48e5cb0c5959..9c2a7fc07f2d3 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/columns.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/columns.tsx @@ -126,10 +126,9 @@ export const getCasesColumns = ( render: (createdAt: Case['createdAt']) => { if (createdAt != null) { return ( - + + + ); } return getEmptyTagValue(); @@ -142,10 +141,9 @@ export const getCasesColumns = ( render: (closedAt: Case['closedAt']) => { if (closedAt != null) { return ( - + + + ); } return getEmptyTagValue(); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.test.tsx index 58d0c1b0faaf3..eb5bca6cc57ff 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.test.tsx @@ -125,8 +125,9 @@ describe('AllCases', () => { wrapper .find(`[data-test-subj="case-table-column-createdAt"]`) .first() + .childAt(0) .prop('value') - ).toEqual(useGetCasesMockState.data.cases[0].createdAt); + ).toBe(useGetCasesMockState.data.cases[0].createdAt); expect( wrapper .find(`[data-test-subj="case-table-case-count"]`) diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.tsx index a6eca717a82a3..9dd90074a2e7b 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/all_cases/index.tsx @@ -313,6 +313,7 @@ export const AllCases = React.memo(({ userCanCrud }) => { (({ userCanCrud }) => { (({ userCanCrud }) => { fill href={getCreateCaseUrl(urlSearch)} iconType="plusInCircle" + data-test-subj="createNewCaseBtn" > {i18n.CREATE_TITLE} diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx index 3cf0405f40637..01b9bc42f8e91 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/case_view/index.tsx @@ -216,6 +216,7 @@ export const CaseComponent = React.memo( backOptions={{ href: getCaseUrl(search), text: i18n.BACK_TO_ALL, + dataTestSubj: 'backToCases', }} data-test-subj="case-view-title" titleNode={ diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/open_closed_stats/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/open_closed_stats/index.tsx index 75f1d4d911518..b9dab13090aca 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/open_closed_stats/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/open_closed_stats/index.tsx @@ -12,19 +12,28 @@ export interface Props { caseCount: number | null; caseStatus: 'open' | 'closed'; isLoading: boolean; + dataTestSubj?: string; } -export const OpenClosedStats = React.memo(({ caseCount, caseStatus, isLoading }) => { - const openClosedStats = useMemo( - () => [ - { - title: caseStatus === 'open' ? i18n.OPEN_CASES : i18n.CLOSED_CASES, - description: isLoading ? : caseCount ?? 'N/A', - }, - ], - [caseCount, caseStatus, isLoading] - ); - return ; -}); +export const OpenClosedStats = React.memo( + ({ caseCount, caseStatus, isLoading, dataTestSubj }) => { + const openClosedStats = useMemo( + () => [ + { + title: caseStatus === 'open' ? i18n.OPEN_CASES : i18n.CLOSED_CASES, + description: isLoading ? : caseCount ?? 'N/A', + }, + ], + [caseCount, caseStatus, isLoading, dataTestSubj] + ); + return ( + + ); + } +); OpenClosedStats.displayName = 'OpenClosedStats'; diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/tag_list/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/tag_list/index.tsx index c96ae09706426..c61feab0bab98 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/tag_list/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/tag_list/index.tsx @@ -93,7 +93,7 @@ export const TagList = React.memo( )} - + {tags.length === 0 && !isEditTags &&

{i18n.NO_TAGS}

} {tags.length > 0 && !isEditTags && diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/helpers.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/helpers.tsx index 5b7a85e23834d..d3014354ab7b3 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/helpers.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/helpers.tsx @@ -41,7 +41,7 @@ const EuiBadgeWrap = styled(EuiBadge)` .euiBadge__text { white-space: pre-wrap !important; } -`; +` as any; // eslint-disable-line @typescript-eslint/no-explicit-any export const buildQueryBarDescription = ({ field, diff --git a/x-pack/package.json b/x-pack/package.json index 3c6146b491f60..7d2dcb789c2ad 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -91,6 +91,7 @@ "@types/proper-lockfile": "^3.0.1", "@types/puppeteer": "^1.20.1", "@types/react": "^16.9.19", + "@types/react-beautiful-dnd": "^12.1.1", "@types/react-dom": "^16.9.5", "@types/react-redux": "^7.1.7", "@types/react-router-dom": "^5.1.3", @@ -182,7 +183,7 @@ "@elastic/apm-rum-react": "^1.1.1", "@elastic/datemath": "5.0.3", "@elastic/ems-client": "7.8.0", - "@elastic/eui": "21.0.1", + "@elastic/eui": "22.3.0", "@elastic/filesaver": "1.1.2", "@elastic/maki": "6.2.0", "@elastic/node-crypto": "1.1.1", @@ -305,7 +306,7 @@ "re-resizable": "^6.1.1", "react": "^16.12.0", "react-apollo": "^2.1.4", - "react-beautiful-dnd": "^8.0.7", + "react-beautiful-dnd": "^12.2.0", "react-datetime": "^2.14.0", "react-dom": "^16.12.0", "react-dropzone": "^4.2.9", diff --git a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx index 41ef863c00e44..ef4a0f76de9ed 100644 --- a/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx +++ b/x-pack/plugins/advanced_ui_actions/public/components/action_wizard/action_wizard.tsx @@ -12,7 +12,7 @@ import { EuiIcon, EuiSpacer, EuiText, - EuiKeyPadMenuItemButton, + EuiKeyPadMenuItem, } from '@elastic/eui'; import { txtChangeButton } from './i18n'; import './action_wizard.scss'; @@ -180,7 +180,7 @@ const ActionFactorySelector: React.FC = ({ return ( {actionFactories.map(actionFactory => ( - = ({ onClick={() => onActionFactorySelected(actionFactory)} > {actionFactory.iconType && } - + ))} ); diff --git a/x-pack/plugins/canvas/server/routes/custom_elements/update.test.ts b/x-pack/plugins/canvas/server/routes/custom_elements/update.test.ts index f21a9c25b6e64..285a998030cf6 100644 --- a/x-pack/plugins/canvas/server/routes/custom_elements/update.test.ts +++ b/x-pack/plugins/canvas/server/routes/custom_elements/update.test.ts @@ -45,7 +45,6 @@ const customElement: CustomElementPayload = { name: 'MyCustomElement', displayName: 'My Wonderful Custom Element', content: 'This is content', - tags: ['filter', 'graphic'], '@created': '2019-02-08T18:35:23.029Z', '@timestamp': '2019-02-08T18:35:23.029Z', }; diff --git a/x-pack/plugins/cross_cluster_replication/common/constants/index.ts b/x-pack/plugins/cross_cluster_replication/common/constants/index.ts new file mode 100644 index 0000000000000..797141b0996af --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/common/constants/index.ts @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +import { LicenseType } from '../../../licensing/common/types'; + +const platinumLicense: LicenseType = 'platinum'; + +export const PLUGIN = { + ID: 'crossClusterReplication', + TITLE: i18n.translate('xpack.crossClusterReplication.appTitle', { + defaultMessage: 'Cross-Cluster Replication', + }), + minimumLicenseType: platinumLicense, +}; + +export const APPS = { + CCR_APP: 'ccr', + REMOTE_CLUSTER_APP: 'remote_cluster', +}; + +export const MANAGEMENT_ID = 'cross_cluster_replication'; +export const BASE_PATH = `/management/elasticsearch/${MANAGEMENT_ID}`; +export const BASE_PATH_REMOTE_CLUSTERS = '/management/elasticsearch/remote_clusters'; +export const API_BASE_PATH = '/api/cross_cluster_replication'; +export const API_REMOTE_CLUSTERS_BASE_PATH = '/api/remote_clusters'; +export const API_INDEX_MANAGEMENT_BASE_PATH = '/api/index_management'; + +export const FOLLOWER_INDEX_ADVANCED_SETTINGS = { + maxReadRequestOperationCount: 5120, + maxOutstandingReadRequests: 12, + maxReadRequestSize: '32mb', + maxWriteRequestOperationCount: 5120, + maxWriteRequestSize: '9223372036854775807b', + maxOutstandingWriteRequests: 9, + maxWriteBufferCount: 2147483647, + maxWriteBufferSize: '512mb', + maxRetryDelay: '500ms', + readPollTimeout: '1m', +}; diff --git a/x-pack/plugins/cross_cluster_replication/common/services/__snapshots__/follower_index_serialization.test.ts.snap b/x-pack/plugins/cross_cluster_replication/common/services/__snapshots__/follower_index_serialization.test.ts.snap new file mode 100644 index 0000000000000..c20556fe1434d --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/common/services/__snapshots__/follower_index_serialization.test.ts.snap @@ -0,0 +1,128 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`[CCR] follower index serialization deserializeFollowerIndex() deserializes Elasticsearch follower index object 1`] = ` +Object { + "leaderIndex": "leader 1", + "maxOutstandingReadRequests": 1, + "maxOutstandingWriteRequests": 1, + "maxReadRequestOperationCount": 1, + "maxReadRequestSize": "1b", + "maxRetryDelay": "1s", + "maxWriteBufferCount": 1, + "maxWriteBufferSize": "1b", + "maxWriteRequestOperationCount": 1, + "maxWriteRequestSize": "1b", + "name": "follower index 1", + "readPollTimeout": "1s", + "remoteCluster": "cluster 1", + "shards": Array [ + Object { + "bytesReadCount": 1, + "failedReadRequestsCount": 1, + "failedWriteRequestsCount": 1, + "followerGlobalCheckpoint": 1, + "followerMappingVersion": 1, + "followerMaxSequenceNum": 1, + "followerSettingsVersion": 1, + "id": 1, + "lastRequestedSequenceNum": 1, + "leaderGlobalCheckpoint": 1, + "leaderIndex": "leader 1", + "leaderMaxSequenceNum": 1, + "operationsReadCount": 1, + "operationsWrittenCount": 1, + "outstandingReadRequestsCount": 1, + "outstandingWriteRequestsCount": 1, + "readExceptions": Array [], + "remoteCluster": "cluster 1", + "successfulReadRequestCount": 1, + "successfulWriteRequestsCount": 1, + "timeSinceLastReadMs": 1, + "totalReadRemoteExecTimeMs": 1, + "totalReadTimeMs": 1, + "totalWriteTimeMs": 1, + "writeBufferOperationsCount": 1, + "writeBufferSizeBytes": 1, + }, + Object { + "bytesReadCount": undefined, + "failedReadRequestsCount": undefined, + "failedWriteRequestsCount": undefined, + "followerGlobalCheckpoint": undefined, + "followerMappingVersion": undefined, + "followerMaxSequenceNum": undefined, + "followerSettingsVersion": undefined, + "id": "shard 2", + "lastRequestedSequenceNum": undefined, + "leaderGlobalCheckpoint": undefined, + "leaderIndex": "leader_index 2", + "leaderMaxSequenceNum": undefined, + "operationsReadCount": undefined, + "operationsWrittenCount": undefined, + "outstandingReadRequestsCount": undefined, + "outstandingWriteRequestsCount": undefined, + "readExceptions": undefined, + "remoteCluster": "remote_cluster 2", + "successfulReadRequestCount": undefined, + "successfulWriteRequestsCount": undefined, + "timeSinceLastReadMs": undefined, + "totalReadRemoteExecTimeMs": undefined, + "totalReadTimeMs": undefined, + "totalWriteTimeMs": undefined, + "writeBufferOperationsCount": undefined, + "writeBufferSizeBytes": undefined, + }, + ], + "status": "active", +} +`; + +exports[`[CCR] follower index serialization deserializeShard() deserializes shard 1`] = ` +Object { + "bytesReadCount": 1, + "failedReadRequestsCount": 1, + "failedWriteRequestsCount": 1, + "followerGlobalCheckpoint": 1, + "followerMappingVersion": 1, + "followerMaxSequenceNum": 1, + "followerSettingsVersion": 1, + "id": 1, + "lastRequestedSequenceNum": 1, + "leaderGlobalCheckpoint": 1, + "leaderIndex": "leader index", + "leaderMaxSequenceNum": 1, + "operationsReadCount": 1, + "operationsWrittenCount": 1, + "outstandingReadRequestsCount": 1, + "outstandingWriteRequestsCount": 1, + "readExceptions": Array [ + "read exception", + ], + "remoteCluster": "remote cluster", + "successfulReadRequestCount": 1, + "successfulWriteRequestsCount": 1, + "timeSinceLastReadMs": 1, + "totalReadRemoteExecTimeMs": 1, + "totalReadTimeMs": 1, + "totalWriteTimeMs": 1, + "writeBufferOperationsCount": 1, + "writeBufferSizeBytes": 1, +} +`; + +exports[`[CCR] follower index serialization serializeFollowerIndex() serializes object to Elasticsearch follower index object 1`] = ` +Object { + "leader_index": "leader index", + "max_outstanding_read_requests": 1, + "max_outstanding_write_requests": 1, + "max_read_request_operation_count": 1, + "max_read_request_size": "1b", + "max_retry_delay": "1s", + "max_write_buffer_count": 1, + "max_write_buffer_size": "1b", + "max_write_request_operation_count": 1, + "max_write_request_size": "1b", + "read_poll_timeout": "1s", + "remote_cluster": "remote cluster", +} +`; diff --git a/x-pack/legacy/plugins/cross_cluster_replication/common/services/auto_follow_pattern_serialization.test.js b/x-pack/plugins/cross_cluster_replication/common/services/auto_follow_pattern_serialization.test.ts similarity index 85% rename from x-pack/legacy/plugins/cross_cluster_replication/common/services/auto_follow_pattern_serialization.test.js rename to x-pack/plugins/cross_cluster_replication/common/services/auto_follow_pattern_serialization.test.ts index eef87a6cc4c89..fe3e59f21ee23 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/common/services/auto_follow_pattern_serialization.test.js +++ b/x-pack/plugins/cross_cluster_replication/common/services/auto_follow_pattern_serialization.test.ts @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +import { AutoFollowPattern, AutoFollowPatternFromEs } from '../types'; + import { deserializeAutoFollowPattern, deserializeListAutoFollowPatterns, @@ -12,13 +14,10 @@ import { describe('[CCR] auto-follow_serialization', () => { describe('deserializeAutoFollowPattern()', () => { - it('should return empty object if name or esObject are not provided', () => { - expect(deserializeAutoFollowPattern()).toEqual({}); - }); - it('should deserialize Elasticsearch object', () => { const expected = { name: 'some-name', + active: true, remoteCluster: 'foo', leaderIndexPatterns: ['foo-*'], followIndexPattern: 'bar', @@ -27,13 +26,14 @@ describe('[CCR] auto-follow_serialization', () => { const esObject = { name: 'some-name', pattern: { + active: true, remote_cluster: expected.remoteCluster, leader_index_patterns: expected.leaderIndexPatterns, follow_index_pattern: expected.followIndexPattern, }, }; - expect(deserializeAutoFollowPattern(esObject)).toEqual(expected); + expect(deserializeAutoFollowPattern(esObject as AutoFollowPatternFromEs)).toEqual(expected); }); }); @@ -78,7 +78,9 @@ describe('[CCR] auto-follow_serialization', () => { ], }; - expect(deserializeListAutoFollowPatterns(esObjects.patterns)).toEqual(expected); + expect( + deserializeListAutoFollowPatterns(esObjects.patterns as AutoFollowPatternFromEs[]) + ).toEqual(expected); }); }); @@ -96,7 +98,7 @@ describe('[CCR] auto-follow_serialization', () => { followIndexPattern: expected.follow_index_pattern, }; - expect(serializeAutoFollowPattern(object)).toEqual(expected); + expect(serializeAutoFollowPattern(object as AutoFollowPattern)).toEqual(expected); }); }); }); diff --git a/x-pack/plugins/cross_cluster_replication/common/services/auto_follow_pattern_serialization.ts b/x-pack/plugins/cross_cluster_replication/common/services/auto_follow_pattern_serialization.ts new file mode 100644 index 0000000000000..265af0ede1462 --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/common/services/auto_follow_pattern_serialization.ts @@ -0,0 +1,38 @@ +/* + * 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 { AutoFollowPattern, AutoFollowPatternFromEs, AutoFollowPatternToEs } from '../types'; + +export const deserializeAutoFollowPattern = ( + autoFollowPattern: AutoFollowPatternFromEs +): AutoFollowPattern => { + const { + name, + pattern: { active, remote_cluster, leader_index_patterns, follow_index_pattern }, + } = autoFollowPattern; + + return { + name, + active, + remoteCluster: remote_cluster, + leaderIndexPatterns: leader_index_patterns, + followIndexPattern: follow_index_pattern, + }; +}; + +export const deserializeListAutoFollowPatterns = ( + autoFollowPatterns: AutoFollowPatternFromEs[] +): AutoFollowPattern[] => autoFollowPatterns.map(deserializeAutoFollowPattern); + +export const serializeAutoFollowPattern = ({ + remoteCluster, + leaderIndexPatterns, + followIndexPattern, +}: AutoFollowPattern): AutoFollowPatternToEs => ({ + remote_cluster: remoteCluster, + leader_index_patterns: leaderIndexPatterns, + follow_index_pattern: followIndexPattern, +}); diff --git a/x-pack/plugins/cross_cluster_replication/common/services/follower_index_serialization.test.ts b/x-pack/plugins/cross_cluster_replication/common/services/follower_index_serialization.test.ts new file mode 100644 index 0000000000000..bfe3e1b3443e6 --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/common/services/follower_index_serialization.test.ts @@ -0,0 +1,224 @@ +/* + * 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 { ShardFromEs, FollowerIndexFromEs, FollowerIndex } from '../types'; + +import { + deserializeShard, + deserializeFollowerIndex, + deserializeListFollowerIndices, + serializeFollowerIndex, +} from './follower_index_serialization'; + +describe('[CCR] follower index serialization', () => { + describe('deserializeShard()', () => { + it('deserializes shard', () => { + const serializedShard = { + remote_cluster: 'remote cluster', + leader_index: 'leader index', + shard_id: 1, + leader_global_checkpoint: 1, + leader_max_seq_no: 1, + follower_global_checkpoint: 1, + follower_max_seq_no: 1, + last_requested_seq_no: 1, + outstanding_read_requests: 1, + outstanding_write_requests: 1, + write_buffer_operation_count: 1, + write_buffer_size_in_bytes: 1, + follower_mapping_version: 1, + follower_settings_version: 1, + total_read_time_millis: 1, + total_read_remote_exec_time_millis: 1, + successful_read_requests: 1, + failed_read_requests: 1, + operations_read: 1, + bytes_read: 1, + total_write_time_millis: 1, + successful_write_requests: 1, + failed_write_requests: 1, + operations_written: 1, + read_exceptions: ['read exception'], + time_since_last_read_millis: 1, + }; + + expect(deserializeShard(serializedShard as ShardFromEs)).toMatchSnapshot(); + }); + }); + + describe('deserializeFollowerIndex()', () => { + it('deserializes Elasticsearch follower index object', () => { + const serializedFollowerIndex = { + follower_index: 'follower index 1', + remote_cluster: 'cluster 1', + leader_index: 'leader 1', + status: 'active', + parameters: { + max_read_request_operation_count: 1, + max_outstanding_read_requests: 1, + max_read_request_size: '1b', + max_write_request_operation_count: 1, + max_write_request_size: '1b', + max_outstanding_write_requests: 1, + max_write_buffer_count: 1, + max_write_buffer_size: '1b', + max_retry_delay: '1s', + read_poll_timeout: '1s', + }, + shards: [ + { + remote_cluster: 'cluster 1', + leader_index: 'leader 1', + shard_id: 1, + leader_global_checkpoint: 1, + leader_max_seq_no: 1, + follower_global_checkpoint: 1, + follower_max_seq_no: 1, + last_requested_seq_no: 1, + outstanding_read_requests: 1, + outstanding_write_requests: 1, + write_buffer_operation_count: 1, + write_buffer_size_in_bytes: 1, + follower_mapping_version: 1, + follower_settings_version: 1, + total_read_time_millis: 1, + total_read_remote_exec_time_millis: 1, + successful_read_requests: 1, + failed_read_requests: 1, + operations_read: 1, + bytes_read: 1, + total_write_time_millis: 1, + successful_write_requests: 1, + failed_write_requests: 1, + operations_written: 1, + // This is an array of exception objects + read_exceptions: [], + time_since_last_read_millis: 1, + }, + { + remote_cluster: 'remote_cluster 2', + leader_index: 'leader_index 2', + shard_id: 'shard 2', + }, + ], + }; + + expect( + deserializeFollowerIndex(serializedFollowerIndex as FollowerIndexFromEs) + ).toMatchSnapshot(); + }); + }); + + describe('deserializeListFollowerIndices()', () => { + it('deserializes list of Elasticsearch follower index objects', () => { + const serializedFollowerIndexList = [ + { + follower_index: 'follower index 1', + remote_cluster: 'cluster 1', + leader_index: 'leader 1', + status: 'active', + parameters: { + max_read_request_operation_count: 1, + max_outstanding_read_requests: 1, + max_read_request_size: '1b', + max_write_request_operation_count: 1, + max_write_request_size: '1b', + max_outstanding_write_requests: 1, + max_write_buffer_count: 1, + max_write_buffer_size: '1b', + max_retry_delay: '1s', + read_poll_timeout: '1s', + }, + shards: [], + }, + { + follower_index: 'follower index 2', + remote_cluster: 'cluster 2', + leader_index: 'leader 2', + status: 'paused', + parameters: { + max_read_request_operation_count: 2, + max_outstanding_read_requests: 2, + max_read_request_size: '2b', + max_write_request_operation_count: 2, + max_write_request_size: '2b', + max_outstanding_write_requests: 2, + max_write_buffer_count: 2, + max_write_buffer_size: '2b', + max_retry_delay: '2s', + read_poll_timeout: '2s', + }, + shards: [], + }, + ]; + + const deserializedFollowerIndexList = [ + { + name: 'follower index 1', + remoteCluster: 'cluster 1', + leaderIndex: 'leader 1', + status: 'active', + maxReadRequestOperationCount: 1, + maxOutstandingReadRequests: 1, + maxReadRequestSize: '1b', + maxWriteRequestOperationCount: 1, + maxWriteRequestSize: '1b', + maxOutstandingWriteRequests: 1, + maxWriteBufferCount: 1, + maxWriteBufferSize: '1b', + maxRetryDelay: '1s', + readPollTimeout: '1s', + shards: [], + }, + { + name: 'follower index 2', + remoteCluster: 'cluster 2', + leaderIndex: 'leader 2', + status: 'paused', + maxReadRequestOperationCount: 2, + maxOutstandingReadRequests: 2, + maxReadRequestSize: '2b', + maxWriteRequestOperationCount: 2, + maxWriteRequestSize: '2b', + maxOutstandingWriteRequests: 2, + maxWriteBufferCount: 2, + maxWriteBufferSize: '2b', + maxRetryDelay: '2s', + readPollTimeout: '2s', + shards: [], + }, + ]; + + expect(deserializeListFollowerIndices(serializedFollowerIndexList)).toEqual( + deserializedFollowerIndexList + ); + }); + }); + + describe('serializeFollowerIndex()', () => { + it('serializes object to Elasticsearch follower index object', () => { + const deserializedFollowerIndex = { + name: 'test', + status: 'active', + shards: [], + remoteCluster: 'remote cluster', + leaderIndex: 'leader index', + maxReadRequestOperationCount: 1, + maxOutstandingReadRequests: 1, + maxReadRequestSize: '1b', + maxWriteRequestOperationCount: 1, + maxWriteRequestSize: '1b', + maxOutstandingWriteRequests: 1, + maxWriteBufferCount: 1, + maxWriteBufferSize: '1b', + maxRetryDelay: '1s', + readPollTimeout: '1s', + }; + + expect(serializeFollowerIndex(deserializedFollowerIndex as FollowerIndex)).toMatchSnapshot(); + }); + }); +}); diff --git a/x-pack/legacy/plugins/cross_cluster_replication/common/services/follower_index_serialization.js b/x-pack/plugins/cross_cluster_replication/common/services/follower_index_serialization.ts similarity index 87% rename from x-pack/legacy/plugins/cross_cluster_replication/common/services/follower_index_serialization.js rename to x-pack/plugins/cross_cluster_replication/common/services/follower_index_serialization.ts index c41fde8f7818d..df476a0b2db89 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/common/services/follower_index_serialization.js +++ b/x-pack/plugins/cross_cluster_replication/common/services/follower_index_serialization.ts @@ -4,7 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -/* eslint-disable camelcase */ +import { + ShardFromEs, + Shard, + FollowerIndexFromEs, + FollowerIndex, + FollowerIndexToEs, + FollowerIndexAdvancedSettings, + FollowerIndexAdvancedSettingsToEs, +} from '../types'; + export const deserializeShard = ({ remote_cluster, leader_index, @@ -32,7 +41,7 @@ export const deserializeShard = ({ operations_written, read_exceptions, time_since_last_read_millis, -}) => ({ +}: ShardFromEs): Shard => ({ id: shard_id, remoteCluster: remote_cluster, leaderIndex: leader_index, @@ -61,9 +70,7 @@ export const deserializeShard = ({ readExceptions: read_exceptions, timeSinceLastReadMs: time_since_last_read_millis, }); -/* eslint-enable camelcase */ -/* eslint-disable camelcase */ export const deserializeFollowerIndex = ({ follower_index, remote_cluster, @@ -82,7 +89,7 @@ export const deserializeFollowerIndex = ({ read_poll_timeout, } = {}, shards, -}) => ({ +}: FollowerIndexFromEs): FollowerIndex => ({ name: follower_index, remoteCluster: remote_cluster, leaderIndex: leader_index, @@ -99,10 +106,10 @@ export const deserializeFollowerIndex = ({ readPollTimeout: read_poll_timeout, shards: shards && shards.map(deserializeShard), }); -/* eslint-enable camelcase */ -export const deserializeListFollowerIndices = followerIndices => - followerIndices.map(deserializeFollowerIndex); +export const deserializeListFollowerIndices = ( + followerIndices: FollowerIndexFromEs[] +): FollowerIndex[] => followerIndices.map(deserializeFollowerIndex); export const serializeAdvancedSettings = ({ maxReadRequestOperationCount, @@ -115,7 +122,7 @@ export const serializeAdvancedSettings = ({ maxWriteBufferSize, maxRetryDelay, readPollTimeout, -}) => ({ +}: FollowerIndexAdvancedSettings): FollowerIndexAdvancedSettingsToEs => ({ max_read_request_operation_count: maxReadRequestOperationCount, max_outstanding_read_requests: maxOutstandingReadRequests, max_read_request_size: maxReadRequestSize, @@ -128,7 +135,7 @@ export const serializeAdvancedSettings = ({ read_poll_timeout: readPollTimeout, }); -export const serializeFollowerIndex = followerIndex => ({ +export const serializeFollowerIndex = (followerIndex: FollowerIndex): FollowerIndexToEs => ({ remote_cluster: followerIndex.remoteCluster, leader_index: followerIndex.leaderIndex, ...serializeAdvancedSettings(followerIndex), diff --git a/x-pack/legacy/plugins/cross_cluster_replication/common/services/utils.test.js b/x-pack/plugins/cross_cluster_replication/common/services/utils.test.ts similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/common/services/utils.test.js rename to x-pack/plugins/cross_cluster_replication/common/services/utils.test.ts diff --git a/x-pack/legacy/plugins/cross_cluster_replication/common/services/utils.js b/x-pack/plugins/cross_cluster_replication/common/services/utils.ts similarity index 62% rename from x-pack/legacy/plugins/cross_cluster_replication/common/services/utils.js rename to x-pack/plugins/cross_cluster_replication/common/services/utils.ts index 3d8c97f45327c..dda6732254cc3 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/common/services/utils.js +++ b/x-pack/plugins/cross_cluster_replication/common/services/utils.ts @@ -3,14 +3,14 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -export const arrify = val => (Array.isArray(val) ? val : [val]); +export const arrify = (val: any): any[] => (Array.isArray(val) ? val : [val]); /** * Utilty to add some latency in a Promise chain * * @param {number} time Time in millisecond to wait */ -export const wait = (time = 1000) => data => { +export const wait = (time = 1000) => (data: any): Promise => { return new Promise(resolve => { setTimeout(() => resolve(data), time); }); @@ -19,8 +19,11 @@ export const wait = (time = 1000) => data => { /** * Utility to remove empty fields ("") from a request body */ -export const removeEmptyFields = body => - Object.entries(body).reduce((acc, [key, value]) => { +export const removeEmptyFields = (body: Record): Record => + Object.entries(body).reduce((acc: Record, [key, value]: [string, any]): Record< + string, + any + > => { if (value !== '') { acc[key] = value; } diff --git a/x-pack/plugins/cross_cluster_replication/common/types.ts b/x-pack/plugins/cross_cluster_replication/common/types.ts new file mode 100644 index 0000000000000..4932d6c570297 --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/common/types.ts @@ -0,0 +1,186 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export interface AutoFollowPattern { + name: string; + active: boolean; + remoteCluster: string; + leaderIndexPatterns: string[]; + followIndexPattern: string; +} + +export interface AutoFollowPatternFromEs { + name: string; + pattern: { + active: boolean; + remote_cluster: string; + leader_index_patterns: string[]; + follow_index_pattern: string; + }; +} + +export interface AutoFollowPatternToEs { + remote_cluster: string; + leader_index_patterns: string[]; + follow_index_pattern: string; +} + +export interface ShardFromEs { + remote_cluster: string; + leader_index: string; + shard_id: number; + leader_global_checkpoint: number; + leader_max_seq_no: number; + follower_global_checkpoint: number; + follower_max_seq_no: number; + last_requested_seq_no: number; + outstanding_read_requests: number; + outstanding_write_requests: number; + write_buffer_operation_count: number; + write_buffer_size_in_bytes: number; + follower_mapping_version: number; + follower_settings_version: number; + total_read_time_millis: number; + total_read_remote_exec_time_millis: number; + successful_read_requests: number; + failed_read_requests: number; + operations_read: number; + bytes_read: number; + total_write_time_millis: number; + successful_write_requests: number; + failed_write_requests: number; + operations_written: number; + // This is an array of exception objects + read_exceptions: any[]; + time_since_last_read_millis: number; +} + +export interface Shard { + remoteCluster: string; + leaderIndex: string; + id: number; + leaderGlobalCheckpoint: number; + leaderMaxSequenceNum: number; + followerGlobalCheckpoint: number; + followerMaxSequenceNum: number; + lastRequestedSequenceNum: number; + outstandingReadRequestsCount: number; + outstandingWriteRequestsCount: number; + writeBufferOperationsCount: number; + writeBufferSizeBytes: number; + followerMappingVersion: number; + followerSettingsVersion: number; + totalReadTimeMs: number; + totalReadRemoteExecTimeMs: number; + successfulReadRequestCount: number; + failedReadRequestsCount: number; + operationsReadCount: number; + bytesReadCount: number; + totalWriteTimeMs: number; + successfulWriteRequestsCount: number; + failedWriteRequestsCount: number; + operationsWrittenCount: number; + // This is an array of exception objects + readExceptions: any[]; + timeSinceLastReadMs: number; +} + +export interface FollowerIndexFromEs { + follower_index: string; + remote_cluster: string; + leader_index: string; + status: string; + // Once https://github.com/elastic/elasticsearch/issues/54996 is resolved so that paused follower + // indices contain this information, we can removed this optional typing as well as the optional + // typing in FollowerIndexAdvancedSettings and FollowerIndexAdvancedSettingsToEs. + parameters?: FollowerIndexAdvancedSettingsToEs; + shards: ShardFromEs[]; +} + +export interface FollowerIndex extends FollowerIndexAdvancedSettings { + name: string; + remoteCluster: string; + leaderIndex: string; + status: string; + shards: Shard[]; +} + +export interface FollowerIndexToEs extends FollowerIndexAdvancedSettingsToEs { + remote_cluster: string; + leader_index: string; +} + +export interface FollowerIndexAdvancedSettings { + maxReadRequestOperationCount?: number; + maxOutstandingReadRequests?: number; + maxReadRequestSize?: string; // byte value + maxWriteRequestOperationCount?: number; + maxWriteRequestSize?: string; // byte value + maxOutstandingWriteRequests?: number; + maxWriteBufferCount?: number; + maxWriteBufferSize?: string; // byte value + maxRetryDelay?: string; // time value + readPollTimeout?: string; // time value +} + +export interface FollowerIndexAdvancedSettingsToEs { + max_read_request_operation_count?: number; + max_outstanding_read_requests?: number; + max_read_request_size?: string; // byte value + max_write_request_operation_count?: number; + max_write_request_size?: string; // byte value + max_outstanding_write_requests?: number; + max_write_buffer_count?: number; + max_write_buffer_size?: string; // byte value + max_retry_delay?: string; // time value + read_poll_timeout?: string; // time value +} + +export interface RecentAutoFollowError { + timestamp: number; + leaderIndex: string; + autoFollowException: { + type: string; + reason: string; + }; +} + +export interface RecentAutoFollowErrorFromEs { + timestamp: number; + leader_index: string; + auto_follow_exception: { + type: string; + reason: string; + }; +} + +export interface AutoFollowedCluster { + clusterName: string; + timeSinceLastCheckMillis: number; + lastSeenMetadataVersion: number; +} + +export interface AutoFollowedClusterFromEs { + cluster_name: string; + time_since_last_check_millis: number; + last_seen_metadata_version: number; +} + +export interface AutoFollowStats { + numberOfFailedFollowIndices: number; + numberOfFailedRemoteClusterStateRequests: number; + numberOfSuccessfulFollowIndices: number; + recentAutoFollowErrors: RecentAutoFollowError[]; + autoFollowedClusters: AutoFollowedCluster[]; +} + +export interface AutoFollowStatsFromEs { + number_of_failed_follow_indices: number; + number_of_failed_remote_cluster_state_requests: number; + number_of_successful_follow_indices: number; + recent_auto_follow_errors: RecentAutoFollowErrorFromEs[]; + auto_followed_clusters: AutoFollowedClusterFromEs[]; +} diff --git a/x-pack/plugins/cross_cluster_replication/kibana.json b/x-pack/plugins/cross_cluster_replication/kibana.json new file mode 100644 index 0000000000000..ccf98f41def47 --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/kibana.json @@ -0,0 +1,17 @@ +{ + "id": "crossClusterReplication", + "version": "kibana", + "server": true, + "ui": true, + "requiredPlugins": [ + "home", + "licensing", + "management", + "remoteClusters", + "indexManagement" + ], + "optionalPlugins": [ + "usageCollection" + ], + "configPath": ["xpack", "ccr"] +} diff --git a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/auto_follow_pattern_add.test.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/auto_follow_pattern_add.test.js similarity index 98% rename from x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/auto_follow_pattern_add.test.js rename to x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/auto_follow_pattern_add.test.js index 2be00e70f6f84..db1430d157183 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/auto_follow_pattern_add.test.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/auto_follow_pattern_add.test.js @@ -3,11 +3,10 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import '../../public/np_ready/app/services/breadcrumbs.mock'; -import { setupEnvironment, pageHelpers, nextTick, getRandomString } from './helpers'; -import { indexPatterns } from '../../../../../../src/plugins/data/public'; -jest.mock('ui/new_platform'); +import { indexPatterns } from '../../../../../../src/plugins/data/public'; +import './mocks'; +import { setupEnvironment, pageHelpers, nextTick, getRandomString } from './helpers'; const { setup } = pageHelpers.autoFollowPatternAdd; diff --git a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/auto_follow_pattern_edit.test.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/auto_follow_pattern_edit.test.js similarity index 95% rename from x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/auto_follow_pattern_edit.test.js rename to x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/auto_follow_pattern_edit.test.js index abc3e5dc9def2..170bce7b82085 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/auto_follow_pattern_edit.test.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/auto_follow_pattern_edit.test.js @@ -4,13 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import '../../public/np_ready/app/services/breadcrumbs.mock'; -import { AutoFollowPatternForm } from '../../public/np_ready/app/components/auto_follow_pattern_form'; +import { AutoFollowPatternForm } from '../../app/components/auto_follow_pattern_form'; +import './mocks'; import { setupEnvironment, pageHelpers, nextTick } from './helpers'; import { AUTO_FOLLOW_PATTERN_EDIT } from './helpers/constants'; -jest.mock('ui/new_platform'); - const { setup } = pageHelpers.autoFollowPatternEdit; const { setup: setupAutoFollowPatternAdd } = pageHelpers.autoFollowPatternAdd; diff --git a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/auto_follow_pattern_list.test.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/auto_follow_pattern_list.test.js similarity index 97% rename from x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/auto_follow_pattern_list.test.js rename to x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/auto_follow_pattern_list.test.js index 20e982856dc19..190400e988634 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/auto_follow_pattern_list.test.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/auto_follow_pattern_list.test.js @@ -4,13 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import '../../public/np_ready/app/services/breadcrumbs.mock'; +import { getAutoFollowPatternMock } from './fixtures/auto_follow_pattern'; +import './mocks'; import { setupEnvironment, pageHelpers, nextTick, getRandomString } from './helpers'; -import { getAutoFollowPatternClientMock } from '../../fixtures/auto_follow_pattern'; - -jest.mock('ui/new_platform'); - const { setup } = pageHelpers.autoFollowPatternList; describe('', () => { @@ -79,11 +76,11 @@ describe('', () => { const testPrefix = 'prefix_'; const testSuffix = '_suffix'; - const autoFollowPattern1 = getAutoFollowPatternClientMock({ + const autoFollowPattern1 = getAutoFollowPatternMock({ name: `a${getRandomString()}`, followIndexPattern: `${testPrefix}{{leader_index}}${testSuffix}`, }); - const autoFollowPattern2 = getAutoFollowPatternClientMock({ + const autoFollowPattern2 = getAutoFollowPatternMock({ name: `b${getRandomString()}`, followIndexPattern: '{{leader_index}}', // no prefix nor suffix }); @@ -305,10 +302,12 @@ describe('', () => { const message = 'bar'; const recentAutoFollowErrors = [ { + timestamp: 1587081600021, leaderIndex: `${autoFollowPattern1.name}:my-leader-test`, autoFollowException: { type: 'exception', reason: message }, }, { + timestamp: 1587081600021, leaderIndex: `${autoFollowPattern2.name}:my-leader-test`, autoFollowException: { type: 'exception', reason: message }, }, @@ -327,7 +326,7 @@ describe('', () => { expect(exists('autoFollowPatternDetail.errors')).toBe(true); expect(exists('autoFollowPatternDetail.titleErrors')).toBe(true); expect(find('autoFollowPatternDetail.recentError').map(error => error.text())).toEqual([ - message, + 'April 16th, 2020 8:00:00 PM: bar', ]); }); }); diff --git a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/fixtures/auto_follow_pattern.ts b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/fixtures/auto_follow_pattern.ts new file mode 100644 index 0000000000000..e6444c37e8590 --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/fixtures/auto_follow_pattern.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { getRandomString } from '../../../../../../test_utils'; +import { AutoFollowPattern } from '../../../../common/types'; + +export const getAutoFollowPatternMock = ({ + name = getRandomString(), + active = false, + remoteCluster = getRandomString(), + leaderIndexPatterns = [`${getRandomString()}-*`], + followIndexPattern = getRandomString(), +}: { + name: string; + active: boolean; + remoteCluster: string; + leaderIndexPatterns: string[]; + followIndexPattern: string; +}): AutoFollowPattern => ({ + name, + active, + remoteCluster, + leaderIndexPatterns, + followIndexPattern, +}); diff --git a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/fixtures/follower_index.ts b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/fixtures/follower_index.ts new file mode 100644 index 0000000000000..ff051d470531b --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/fixtures/follower_index.ts @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { getRandomString } from '../../../../../../test_utils'; +import { FollowerIndex } from '../../../../common/types'; + +const Chance = require('chance'); // eslint-disable-line import/no-extraneous-dependencies, @typescript-eslint/no-var-requires +const chance = new Chance(); + +interface FollowerIndexMock { + name: string; + remoteCluster: string; + leaderIndex: string; + status: string; +} + +export const getFollowerIndexMock = ({ + name = getRandomString(), + remoteCluster = getRandomString(), + leaderIndex = getRandomString(), + status = 'Active', +}: FollowerIndexMock): FollowerIndex => ({ + name, + remoteCluster, + leaderIndex, + status, + maxReadRequestOperationCount: chance.integer(), + maxOutstandingReadRequests: chance.integer(), + maxReadRequestSize: getRandomString({ length: 5 }), + maxWriteRequestOperationCount: chance.integer(), + maxWriteRequestSize: '9223372036854775807b', + maxOutstandingWriteRequests: chance.integer(), + maxWriteBufferCount: chance.integer(), + maxWriteBufferSize: getRandomString({ length: 5 }), + maxRetryDelay: getRandomString({ length: 5 }), + readPollTimeout: getRandomString({ length: 5 }), + shards: [ + { + id: 0, + remoteCluster, + leaderIndex, + leaderGlobalCheckpoint: chance.integer(), + leaderMaxSequenceNum: chance.integer(), + followerGlobalCheckpoint: chance.integer(), + followerMaxSequenceNum: chance.integer(), + lastRequestedSequenceNum: chance.integer(), + outstandingReadRequestsCount: chance.integer(), + outstandingWriteRequestsCount: chance.integer(), + writeBufferOperationsCount: chance.integer(), + writeBufferSizeBytes: chance.integer(), + followerMappingVersion: chance.integer(), + followerSettingsVersion: chance.integer(), + totalReadTimeMs: chance.integer(), + totalReadRemoteExecTimeMs: chance.integer(), + successfulReadRequestCount: chance.integer(), + failedReadRequestsCount: chance.integer(), + operationsReadCount: chance.integer(), + bytesReadCount: chance.integer(), + totalWriteTimeMs: chance.integer(), + successfulWriteRequestsCount: chance.integer(), + failedWriteRequestsCount: chance.integer(), + operationsWrittenCount: chance.integer(), + readExceptions: [], + timeSinceLastReadMs: chance.integer(), + }, + ], +}); diff --git a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/follower_index_add.test.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/follower_index_add.test.js similarity index 98% rename from x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/follower_index_add.test.js rename to x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/follower_index_add.test.js index 7680be9d858a4..4c99339e16952 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/follower_index_add.test.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/follower_index_add.test.js @@ -4,13 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import '../../public/np_ready/app/services/breadcrumbs.mock'; -import { setupEnvironment, pageHelpers, nextTick } from './helpers'; -import { RemoteClustersFormField } from '../../public/np_ready/app/components'; - import { indexPatterns } from '../../../../../../src/plugins/data/public'; - -jest.mock('ui/new_platform'); +import './mocks'; +import { setupEnvironment, pageHelpers, nextTick } from './helpers'; +import { RemoteClustersFormField } from '../../app/components'; const { setup } = pageHelpers.followerIndexAdd; const { setup: setupAutoFollowPatternAdd } = pageHelpers.autoFollowPatternAdd; diff --git a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/follower_index_edit.test.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/follower_index_edit.test.js similarity index 95% rename from x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/follower_index_edit.test.js rename to x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/follower_index_edit.test.js index cfa37ff2e0358..f4bda2af653aa 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/follower_index_edit.test.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/follower_index_edit.test.js @@ -4,12 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import '../../public/np_ready/app/services/breadcrumbs.mock'; -import { setupEnvironment, pageHelpers, nextTick } from './helpers'; -import { FollowerIndexForm } from '../../public/np_ready/app/components/follower_index_form/follower_index_form'; +import { FollowerIndexForm } from '../../app/components/follower_index_form/follower_index_form'; +import './mocks'; import { FOLLOWER_INDEX_EDIT } from './helpers/constants'; - -jest.mock('ui/new_platform'); +import { setupEnvironment, pageHelpers, nextTick } from './helpers'; const { setup } = pageHelpers.followerIndexEdit; const { setup: setupFollowerIndexAdd } = pageHelpers.followerIndexAdd; diff --git a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/follower_indices_list.test.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/follower_indices_list.test.js similarity index 99% rename from x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/follower_indices_list.test.js rename to x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/follower_indices_list.test.js index dde31d1d166f9..f98a1dafbbcbf 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/follower_indices_list.test.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/follower_indices_list.test.js @@ -4,12 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ +import { getFollowerIndexMock } from './fixtures/follower_index'; +import './mocks'; import { setupEnvironment, pageHelpers, nextTick, getRandomString } from './helpers'; -import { getFollowerIndexMock } from '../../fixtures/follower_index'; - -jest.mock('ui/new_platform'); - const { setup } = pageHelpers.followerIndexList; describe('', () => { diff --git a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/helpers/auto_follow_pattern_add.helpers.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/auto_follow_pattern_add.helpers.js similarity index 76% rename from x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/helpers/auto_follow_pattern_add.helpers.js rename to x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/auto_follow_pattern_add.helpers.js index 1f64e589bc4c1..1cb4e7c7725df 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/helpers/auto_follow_pattern_add.helpers.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/auto_follow_pattern_add.helpers.js @@ -5,9 +5,9 @@ */ import { registerTestBed } from '../../../../../../test_utils'; -import { AutoFollowPatternAdd } from '../../../public/np_ready/app/sections/auto_follow_pattern_add'; -import { ccrStore } from '../../../public/np_ready/app/store'; -import routing from '../../../public/np_ready/app/services/routing'; +import { AutoFollowPatternAdd } from '../../../app/sections/auto_follow_pattern_add'; +import { ccrStore } from '../../../app/store'; +import { routing } from '../../../app/services/routing'; const testBedConfig = { store: ccrStore, diff --git a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/helpers/auto_follow_pattern_edit.helpers.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/auto_follow_pattern_edit.helpers.js similarity index 82% rename from x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/helpers/auto_follow_pattern_edit.helpers.js rename to x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/auto_follow_pattern_edit.helpers.js index 2b110c6552072..9cad61893c409 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/helpers/auto_follow_pattern_edit.helpers.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/auto_follow_pattern_edit.helpers.js @@ -5,9 +5,9 @@ */ import { registerTestBed } from '../../../../../../test_utils'; -import { AutoFollowPatternEdit } from '../../../public/np_ready/app/sections/auto_follow_pattern_edit'; -import { ccrStore } from '../../../public/np_ready/app/store'; -import routing from '../../../public/np_ready/app/services/routing'; +import { AutoFollowPatternEdit } from '../../../app/sections/auto_follow_pattern_edit'; +import { ccrStore } from '../../../app/store'; +import { routing } from '../../../app/services/routing'; import { AUTO_FOLLOW_PATTERN_EDIT_NAME } from './constants'; diff --git a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/helpers/auto_follow_pattern_list.helpers.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/auto_follow_pattern_list.helpers.js similarity index 92% rename from x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/helpers/auto_follow_pattern_list.helpers.js rename to x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/auto_follow_pattern_list.helpers.js index 1d3e8ad6dff83..450feed49f9f2 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/helpers/auto_follow_pattern_list.helpers.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/auto_follow_pattern_list.helpers.js @@ -5,9 +5,9 @@ */ import { registerTestBed, findTestSubject } from '../../../../../../test_utils'; -import { AutoFollowPatternList } from '../../../public/np_ready/app/sections/home/auto_follow_pattern_list'; -import { ccrStore } from '../../../public/np_ready/app/store'; -import routing from '../../../public/np_ready/app/services/routing'; +import { AutoFollowPatternList } from '../../../app/sections/home/auto_follow_pattern_list'; +import { ccrStore } from '../../../app/store'; +import { routing } from '../../../app/services/routing'; const testBedConfig = { store: ccrStore, diff --git a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/helpers/constants.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/constants.js similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/helpers/constants.js rename to x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/constants.js diff --git a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/helpers/follower_index_add.helpers.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_add.helpers.js similarity index 79% rename from x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/helpers/follower_index_add.helpers.js rename to x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_add.helpers.js index f74baa1b2ad0a..856b09f3f3cba 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/helpers/follower_index_add.helpers.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_add.helpers.js @@ -5,9 +5,9 @@ */ import { registerTestBed } from '../../../../../../test_utils'; -import { FollowerIndexAdd } from '../../../public/np_ready/app/sections/follower_index_add'; -import { ccrStore } from '../../../public/np_ready/app/store'; -import routing from '../../../public/np_ready/app/services/routing'; +import { FollowerIndexAdd } from '../../../app/sections/follower_index_add'; +import { ccrStore } from '../../../app/store'; +import { routing } from '../../../app/services/routing'; const testBedConfig = { store: ccrStore, diff --git a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/helpers/follower_index_edit.helpers.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_edit.helpers.js similarity index 84% rename from x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/helpers/follower_index_edit.helpers.js rename to x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_edit.helpers.js index 47f8539bb593b..893d01f151bc2 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/helpers/follower_index_edit.helpers.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_edit.helpers.js @@ -5,9 +5,9 @@ */ import { registerTestBed } from '../../../../../../test_utils'; -import { FollowerIndexEdit } from '../../../public/np_ready/app/sections/follower_index_edit'; -import { ccrStore } from '../../../public/np_ready/app/store'; -import routing from '../../../public/np_ready/app/services/routing'; +import { FollowerIndexEdit } from '../../../app/sections/follower_index_edit'; +import { ccrStore } from '../../../app/store'; +import { routing } from '../../../app/services/routing'; import { FOLLOWER_INDEX_EDIT_NAME } from './constants'; diff --git a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/helpers/follower_index_list.helpers.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_list.helpers.js similarity index 90% rename from x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/helpers/follower_index_list.helpers.js rename to x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_list.helpers.js index 2154e11e17b1f..52f4267594cc1 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/helpers/follower_index_list.helpers.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_list.helpers.js @@ -5,9 +5,9 @@ */ import { registerTestBed, findTestSubject } from '../../../../../../test_utils'; -import { FollowerIndicesList } from '../../../public/np_ready/app/sections/home/follower_indices_list'; -import { ccrStore } from '../../../public/np_ready/app/store'; -import routing from '../../../public/np_ready/app/services/routing'; +import { FollowerIndicesList } from '../../../app/sections/home/follower_indices_list'; +import { ccrStore } from '../../../app/store'; +import { routing } from '../../../app/services/routing'; const testBedConfig = { store: ccrStore, diff --git a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/helpers/home.helpers.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/home.helpers.js similarity index 68% rename from x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/helpers/home.helpers.js rename to x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/home.helpers.js index 664ad909ba8e7..56dfa765bfa4f 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/helpers/home.helpers.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/home.helpers.js @@ -5,10 +5,10 @@ */ import { registerTestBed } from '../../../../../../test_utils'; -import { CrossClusterReplicationHome } from '../../../public/np_ready/app/sections/home/home'; -import { ccrStore } from '../../../public/np_ready/app/store'; -import routing from '../../../public/np_ready/app/services/routing'; -import { BASE_PATH } from '../../../common/constants'; +import { BASE_PATH } from '../../../../common/constants'; +import { CrossClusterReplicationHome } from '../../../app/sections/home/home'; +import { ccrStore } from '../../../app/store'; +import { routing } from '../../../app/services/routing'; const testBedConfig = { store: ccrStore, diff --git a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/helpers/http_requests.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/http_requests.js similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/helpers/http_requests.js rename to x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/http_requests.js diff --git a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/helpers/index.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/index.js similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/helpers/index.js rename to x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/index.js diff --git a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/helpers/setup_environment.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/setup_environment.js similarity index 91% rename from x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/helpers/setup_environment.js rename to x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/setup_environment.js index 3562ad0df5b51..6dedbbfa79b19 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/helpers/setup_environment.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/setup_environment.js @@ -7,7 +7,7 @@ import axios from 'axios'; import axiosXhrAdapter from 'axios/lib/adapters/xhr'; -import { setHttpClient } from '../../../public/np_ready/app/services/api'; +import { setHttpClient } from '../../../app/services/api'; import { init as initHttpRequests } from './http_requests'; export const setupEnvironment = () => { diff --git a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/home.test.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/home.test.js similarity index 93% rename from x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/home.test.js rename to x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/home.test.js index 2c536d069ef53..18d8b4eb9dbe0 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/home.test.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/home.test.js @@ -4,11 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import '../../public/np_ready/app/services/breadcrumbs.mock'; +import './mocks'; import { setupEnvironment, pageHelpers, nextTick } from './helpers'; -jest.mock('ui/new_platform'); - const { setup } = pageHelpers.home; describe('', () => { @@ -36,7 +34,7 @@ describe('', () => { ({ exists, find, component } = setup()); }); - test('should set the correct an app title', () => { + test('should set the correct app title', () => { expect(exists('appTitle')).toBe(true); expect(find('appTitle').text()).toEqual('Cross-Cluster Replication'); }); diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/services/breadcrumbs.mock.ts b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/mocks/breadcrumbs.mock.ts similarity index 70% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/services/breadcrumbs.mock.ts rename to x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/mocks/breadcrumbs.mock.ts index b7c75108d4ef0..60a196254d408 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/services/breadcrumbs.mock.ts +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/mocks/breadcrumbs.mock.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -jest.mock('./breadcrumbs', () => ({ - ...jest.requireActual('./breadcrumbs'), +jest.mock('../../../app/services/breadcrumbs', () => ({ + ...jest.requireActual('../../../app/services/breadcrumbs'), setBreadcrumbs: jest.fn(), })); diff --git a/x-pack/legacy/plugins/cross_cluster_replication/common/constants/plugin.ts b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/mocks/index.ts similarity index 79% rename from x-pack/legacy/plugins/cross_cluster_replication/common/constants/plugin.ts rename to x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/mocks/index.ts index bd5bb50514c01..cff9c003f3e80 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/common/constants/plugin.ts +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/mocks/index.ts @@ -4,6 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ -export const PLUGIN = { - ID: 'cross_cluster_replication', -}; +import './breadcrumbs.mock'; +import './track_ui_metric.mock'; diff --git a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/mocks/track_ui_metric.mock.ts b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/mocks/track_ui_metric.mock.ts new file mode 100644 index 0000000000000..016e259343285 --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/mocks/track_ui_metric.mock.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +jest.mock('../../../app/services/track_ui_metric', () => ({ + ...jest.requireActual('../../../app/services/track_ui_metric'), + trackUiMetric: jest.fn(), + trackUserRequest: (request: Promise) => { + return request.then(response => response); + }, +})); diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/app.js b/x-pack/plugins/cross_cluster_replication/public/app/app.tsx similarity index 89% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/app.js rename to x-pack/plugins/cross_cluster_replication/public/app/app.tsx index 968646a4bd1b0..ec349ccd6f2c7 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/app.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/app.tsx @@ -5,8 +5,8 @@ */ import React, { Component, Fragment } from 'react'; -import PropTypes from 'prop-types'; -import { Route, Switch, Redirect, withRouter } from 'react-router-dom'; +import { Route, Switch, Redirect, withRouter, RouteComponentProps } from 'react-router-dom'; +import { History } from 'history'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -20,12 +20,14 @@ import { EuiTitle, } from '@elastic/eui'; -import { BASE_PATH } from '../../../common/constants'; +import { BASE_PATH } from '../../common/constants'; import { getFatalErrors } from './services/notifications'; import { SectionError } from './components'; -import routing from './services/routing'; +import { routing } from './services/routing'; +// @ts-ignore import { loadPermissions } from './services/api'; +// @ts-ignore import { CrossClusterReplicationHome, AutoFollowPatternAdd, @@ -34,16 +36,21 @@ import { FollowerIndexEdit, } from './sections'; -class AppComponent extends Component { - static propTypes = { - history: PropTypes.shape({ - push: PropTypes.func.isRequired, - createHref: PropTypes.func.isRequired, - }).isRequired, - }; +interface AppProps { + history: History; + location: any; +} + +interface AppState { + isFetchingPermissions: boolean; + fetchPermissionError: any; + hasPermission: boolean; + missingClusterPrivileges: any[]; +} - constructor(...args) { - super(...args); +class AppComponent extends Component { + constructor(props: any) { + super(props); this.registerRouter(); this.state = { @@ -54,18 +61,10 @@ class AppComponent extends Component { }; } - UNSAFE_componentWillMount() { - routing.userHasLeftApp = false; - } - componentDidMount() { this.checkPermissions(); } - componentWillUnmount() { - routing.userHasLeftApp = true; - } - async checkPermissions() { this.setState({ isFetchingPermissions: true, @@ -163,7 +162,6 @@ class AppComponent extends Component {
- + ( diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/components/remote_clusters_provider.js b/x-pack/plugins/cross_cluster_replication/public/app/components/remote_clusters_provider.js similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/components/remote_clusters_provider.js rename to x-pack/plugins/cross_cluster_replication/public/app/components/remote_clusters_provider.js diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/components/section_error.js b/x-pack/plugins/cross_cluster_replication/public/app/components/section_error.js similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/components/section_error.js rename to x-pack/plugins/cross_cluster_replication/public/app/components/section_error.js diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/components/section_loading.js b/x-pack/plugins/cross_cluster_replication/public/app/components/section_loading.js similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/components/section_loading.js rename to x-pack/plugins/cross_cluster_replication/public/app/components/section_loading.js diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/components/section_unauthorized.js b/x-pack/plugins/cross_cluster_replication/public/app/components/section_unauthorized.js similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/components/section_unauthorized.js rename to x-pack/plugins/cross_cluster_replication/public/app/components/section_unauthorized.js diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/constants/api.js b/x-pack/plugins/cross_cluster_replication/public/app/constants/api.ts similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/constants/api.js rename to x-pack/plugins/cross_cluster_replication/public/app/constants/api.ts diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/constants/index.js b/x-pack/plugins/cross_cluster_replication/public/app/constants/index.ts similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/constants/index.js rename to x-pack/plugins/cross_cluster_replication/public/app/constants/index.ts diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/constants/sections.js b/x-pack/plugins/cross_cluster_replication/public/app/constants/sections.ts similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/constants/sections.js rename to x-pack/plugins/cross_cluster_replication/public/app/constants/sections.ts diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/constants/ui_metric.js b/x-pack/plugins/cross_cluster_replication/public/app/constants/ui_metric.ts similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/constants/ui_metric.js rename to x-pack/plugins/cross_cluster_replication/public/app/constants/ui_metric.ts diff --git a/x-pack/plugins/cross_cluster_replication/public/app/index.tsx b/x-pack/plugins/cross_cluster_replication/public/app/index.tsx new file mode 100644 index 0000000000000..79569b587f97f --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/public/app/index.tsx @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import { render, unmountComponentAtNode } from 'react-dom'; +import { Provider } from 'react-redux'; +import { HashRouter } from 'react-router-dom'; +import { I18nStart } from 'kibana/public'; +import { UnmountCallback } from 'src/core/public'; + +import { init as initBreadcrumbs, SetBreadcrumbs } from './services/breadcrumbs'; +import { init as initDocumentation } from './services/documentation_links'; +import { App } from './app'; +import { ccrStore } from './store'; + +const renderApp = (element: Element, I18nContext: I18nStart['Context']): UnmountCallback => { + render( + + + + + + + , + element + ); + + return () => unmountComponentAtNode(element); +}; + +export async function mountApp({ + element, + setBreadcrumbs, + I18nContext, + ELASTIC_WEBSITE_URL, + DOC_LINK_VERSION, +}: { + element: Element; + setBreadcrumbs: SetBreadcrumbs; + I18nContext: I18nStart['Context']; + ELASTIC_WEBSITE_URL: string; + DOC_LINK_VERSION: string; +}): Promise { + // Import and initialize additional services here instead of in plugin.ts to reduce the size of the + // initial bundle as much as possible. + initBreadcrumbs(setBreadcrumbs); + initDocumentation(`${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/reference/${DOC_LINK_VERSION}/`); + + return renderApp(element, I18nContext); +} diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/auto_follow_pattern_add/auto_follow_pattern_add.container.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_add/auto_follow_pattern_add.container.js similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/auto_follow_pattern_add/auto_follow_pattern_add.container.js rename to x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_add/auto_follow_pattern_add.container.js diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/auto_follow_pattern_add/auto_follow_pattern_add.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_add/auto_follow_pattern_add.js similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/auto_follow_pattern_add/auto_follow_pattern_add.js rename to x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_add/auto_follow_pattern_add.js diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/auto_follow_pattern_add/index.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_add/index.js similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/auto_follow_pattern_add/index.js rename to x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_add/index.js diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/auto_follow_pattern_edit/auto_follow_pattern_edit.container.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_edit/auto_follow_pattern_edit.container.js similarity index 80% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/auto_follow_pattern_edit/auto_follow_pattern_edit.container.js rename to x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_edit/auto_follow_pattern_edit.container.js index 2c90456076f85..be470edc07537 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/auto_follow_pattern_edit/auto_follow_pattern_edit.container.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_edit/auto_follow_pattern_edit.container.js @@ -39,8 +39,23 @@ const mapStateToProps = state => ({ const mapDispatchToProps = dispatch => ({ getAutoFollowPattern: id => dispatch(getAutoFollowPattern(id)), selectAutoFollowPattern: id => dispatch(selectEditAutoFollowPattern(id)), - saveAutoFollowPattern: (id, autoFollowPattern) => - dispatch(saveAutoFollowPattern(id, autoFollowPattern, true)), + saveAutoFollowPattern: (id, autoFollowPattern) => { + // Strip out errors. + const { active, remoteCluster, leaderIndexPatterns, followIndexPattern } = autoFollowPattern; + + dispatch( + saveAutoFollowPattern( + id, + { + active, + remoteCluster, + leaderIndexPatterns, + followIndexPattern, + }, + true + ) + ); + }, clearApiError: () => { dispatch(clearApiError(`${scope}-get`)); dispatch(clearApiError(`${scope}-save`)); diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/auto_follow_pattern_edit/auto_follow_pattern_edit.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_edit/auto_follow_pattern_edit.js similarity index 99% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/auto_follow_pattern_edit/auto_follow_pattern_edit.js rename to x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_edit/auto_follow_pattern_edit.js index 4cd3617abd989..387d7817a0357 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/auto_follow_pattern_edit/auto_follow_pattern_edit.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_edit/auto_follow_pattern_edit.js @@ -12,7 +12,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiPageContent, EuiSpacer } from '@elastic/eui'; import { listBreadcrumb, editBreadcrumb, setBreadcrumbs } from '../../services/breadcrumbs'; -import routing from '../../services/routing'; +import { routing } from '../../services/routing'; import { AutoFollowPatternForm, AutoFollowPatternPageTitle, diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/auto_follow_pattern_edit/index.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_edit/index.js similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/auto_follow_pattern_edit/index.js rename to x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_edit/index.js diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/follower_index_add/follower_index_add.container.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/follower_index_add/follower_index_add.container.js similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/follower_index_add/follower_index_add.container.js rename to x-pack/plugins/cross_cluster_replication/public/app/sections/follower_index_add/follower_index_add.container.js diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/follower_index_add/follower_index_add.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/follower_index_add/follower_index_add.js similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/follower_index_add/follower_index_add.js rename to x-pack/plugins/cross_cluster_replication/public/app/sections/follower_index_add/follower_index_add.js diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/follower_index_add/index.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/follower_index_add/index.js similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/follower_index_add/index.js rename to x-pack/plugins/cross_cluster_replication/public/app/sections/follower_index_add/index.js diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/follower_index_edit/follower_index_edit.container.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/follower_index_edit/follower_index_edit.container.js similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/follower_index_edit/follower_index_edit.container.js rename to x-pack/plugins/cross_cluster_replication/public/app/sections/follower_index_edit/follower_index_edit.container.js diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/follower_index_edit/follower_index_edit.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/follower_index_edit/follower_index_edit.js similarity index 99% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/follower_index_edit/follower_index_edit.js rename to x-pack/plugins/cross_cluster_replication/public/app/sections/follower_index_edit/follower_index_edit.js index 21493602c12a7..22f9a7338384b 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/follower_index_edit/follower_index_edit.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/follower_index_edit/follower_index_edit.js @@ -20,7 +20,7 @@ import { } from '@elastic/eui'; import { setBreadcrumbs, listBreadcrumb, editBreadcrumb } from '../../services/breadcrumbs'; -import routing from '../../services/routing'; +import { routing } from '../../services/routing'; import { FollowerIndexForm, FollowerIndexPageTitle, diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/follower_index_edit/index.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/follower_index_edit/index.js similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/follower_index_edit/index.js rename to x-pack/plugins/cross_cluster_replication/public/app/sections/follower_index_edit/index.js diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/home/auto_follow_pattern_list/auto_follow_pattern_list.container.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/auto_follow_pattern_list.container.js similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/home/auto_follow_pattern_list/auto_follow_pattern_list.container.js rename to x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/auto_follow_pattern_list.container.js diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/home/auto_follow_pattern_list/auto_follow_pattern_list.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/auto_follow_pattern_list.js similarity index 99% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/home/auto_follow_pattern_list/auto_follow_pattern_list.js rename to x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/auto_follow_pattern_list.js index e9e14f57e814f..c8cf94842aa68 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/home/auto_follow_pattern_list/auto_follow_pattern_list.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/auto_follow_pattern_list.js @@ -17,7 +17,7 @@ import { EuiSpacer, } from '@elastic/eui'; -import routing from '../../../services/routing'; +import { routing } from '../../../services/routing'; import { extractQueryParams } from '../../../services/query_params'; import { trackUiMetric, METRIC_TYPE } from '../../../services/track_ui_metric'; import { API_STATUS, UIM_AUTO_FOLLOW_PATTERN_LIST_LOAD } from '../../../constants'; diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/home/auto_follow_pattern_list/components/auto_follow_pattern_table/auto_follow_pattern_table.container.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/auto_follow_pattern_table/auto_follow_pattern_table.container.js similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/home/auto_follow_pattern_list/components/auto_follow_pattern_table/auto_follow_pattern_table.container.js rename to x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/auto_follow_pattern_table/auto_follow_pattern_table.container.js diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/home/auto_follow_pattern_list/components/auto_follow_pattern_table/auto_follow_pattern_table.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/auto_follow_pattern_table/auto_follow_pattern_table.js similarity index 97% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/home/auto_follow_pattern_list/components/auto_follow_pattern_table/auto_follow_pattern_table.js rename to x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/auto_follow_pattern_table/auto_follow_pattern_table.js index 956a9f10d810b..eb90e59e99fee 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/home/auto_follow_pattern_list/components/auto_follow_pattern_table/auto_follow_pattern_table.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/auto_follow_pattern_table/auto_follow_pattern_table.js @@ -20,8 +20,8 @@ import { AutoFollowPatternDeleteProvider, AutoFollowPatternActionMenu, } from '../../../../../components'; -import routing from '../../../../../services/routing'; -import { trackUiMetric, METRIC_TYPE } from '../../../../../services/track_ui_metric'; +import { routing } from '../../../../../services/routing'; +import { trackUiMetric } from '../../../../../services/track_ui_metric'; export class AutoFollowPatternTable extends PureComponent { static propTypes = { @@ -86,7 +86,7 @@ export class AutoFollowPatternTable extends PureComponent { return ( { - trackUiMetric(METRIC_TYPE.CLICK, UIM_AUTO_FOLLOW_PATTERN_SHOW_DETAILS_CLICK); + trackUiMetric('click', UIM_AUTO_FOLLOW_PATTERN_SHOW_DETAILS_CLICK); selectAutoFollowPattern(name); }} data-test-subj="autoFollowPatternLink" diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/home/auto_follow_pattern_list/components/auto_follow_pattern_table/index.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/auto_follow_pattern_table/index.js similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/home/auto_follow_pattern_list/components/auto_follow_pattern_table/index.js rename to x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/auto_follow_pattern_table/index.js diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/home/auto_follow_pattern_list/components/detail_panel/detail_panel.container.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/detail_panel/detail_panel.container.js similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/home/auto_follow_pattern_list/components/detail_panel/detail_panel.container.js rename to x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/detail_panel/detail_panel.container.js diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/home/auto_follow_pattern_list/components/detail_panel/detail_panel.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/detail_panel/detail_panel.js similarity index 98% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/home/auto_follow_pattern_list/components/detail_panel/detail_panel.js rename to x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/detail_panel/detail_panel.js index 1a6d5e6efe35a..3f2ed82420ff1 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/home/auto_follow_pattern_list/components/detail_panel/detail_panel.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/detail_panel/detail_panel.js @@ -7,7 +7,9 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { FormattedMessage } from '@kbn/i18n/react'; -import { getIndexListUri } from '../../../../../../../../../../../plugins/index_management/public'; +import moment from 'moment'; + +import { getIndexListUri } from '../../../../../../../../../plugins/index_management/public'; import { EuiButtonEmpty, @@ -247,6 +249,7 @@ export class DetailPanel extends Component {
    {autoFollowPattern.errors.map((error, i) => (
  • + {moment(error.timestamp).format('MMMM Do, YYYY h:mm:ss A')}:{' '} {error.autoFollowException.reason}
  • ))} diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/home/auto_follow_pattern_list/components/detail_panel/index.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/detail_panel/index.js similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/home/auto_follow_pattern_list/components/detail_panel/index.js rename to x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/detail_panel/index.js diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/home/auto_follow_pattern_list/components/index.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/index.js similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/home/auto_follow_pattern_list/components/index.js rename to x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/index.js diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/home/auto_follow_pattern_list/index.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/index.js similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/home/auto_follow_pattern_list/index.js rename to x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/index.js diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/home/follower_indices_list/components/context_menu/context_menu.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/context_menu/context_menu.js similarity index 98% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/home/follower_indices_list/components/context_menu/context_menu.js rename to x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/context_menu/context_menu.js index 0f6ef75522ff7..4a66f7b717bac 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/home/follower_indices_list/components/context_menu/context_menu.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/context_menu/context_menu.js @@ -15,7 +15,7 @@ import { EuiPopoverTitle, } from '@elastic/eui'; -import routing from '../../../../../services/routing'; +import { routing } from '../../../../../services/routing'; import { FollowerIndexPauseProvider, FollowerIndexResumeProvider, diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/home/follower_indices_list/components/context_menu/index.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/context_menu/index.js similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/home/follower_indices_list/components/context_menu/index.js rename to x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/context_menu/index.js diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/home/follower_indices_list/components/detail_panel/detail_panel.container.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/detail_panel/detail_panel.container.js similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/home/follower_indices_list/components/detail_panel/detail_panel.container.js rename to x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/detail_panel/detail_panel.container.js diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/home/follower_indices_list/components/detail_panel/detail_panel.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/detail_panel/detail_panel.js similarity index 99% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/home/follower_indices_list/components/detail_panel/detail_panel.js rename to x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/detail_panel/detail_panel.js index 3e8cf6d3e2f78..4436d76643e6c 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/home/follower_indices_list/components/detail_panel/detail_panel.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/detail_panel/detail_panel.js @@ -31,7 +31,7 @@ import { } from '@elastic/eui'; import 'brace/theme/textmate'; -import { getIndexListUri } from '../../../../../../../../../../../plugins/index_management/public'; +import { getIndexListUri } from '../../../../../../../../../plugins/index_management/public'; import { API_STATUS } from '../../../../../constants'; import { ContextMenu } from '../context_menu'; @@ -489,7 +489,6 @@ export class DetailPanel extends Component { return ( { - trackUiMetric(METRIC_TYPE.CLICK, UIM_FOLLOWER_INDEX_SHOW_DETAILS_CLICK); + trackUiMetric('click', UIM_FOLLOWER_INDEX_SHOW_DETAILS_CLICK); selectFollowerIndex(name); }} data-test-subj="followerIndexLink" diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/home/follower_indices_list/components/follower_indices_table/index.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/follower_indices_table/index.js similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/home/follower_indices_list/components/follower_indices_table/index.js rename to x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/follower_indices_table/index.js diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/home/follower_indices_list/components/index.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/index.js similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/home/follower_indices_list/components/index.js rename to x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/index.js diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/home/follower_indices_list/follower_indices_list.container.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/follower_indices_list.container.js similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/home/follower_indices_list/follower_indices_list.container.js rename to x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/follower_indices_list.container.js diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/home/follower_indices_list/follower_indices_list.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/follower_indices_list.js similarity index 99% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/home/follower_indices_list/follower_indices_list.js rename to x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/follower_indices_list.js index b7e04721f4748..7b843d08cefd3 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/home/follower_indices_list/follower_indices_list.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/follower_indices_list.js @@ -17,7 +17,7 @@ import { EuiSpacer, } from '@elastic/eui'; -import routing from '../../../services/routing'; +import { routing } from '../../../services/routing'; import { extractQueryParams } from '../../../services/query_params'; import { trackUiMetric, METRIC_TYPE } from '../../../services/track_ui_metric'; import { API_STATUS, UIM_FOLLOWER_INDEX_LIST_LOAD } from '../../../constants'; diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/home/follower_indices_list/index.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/index.js similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/home/follower_indices_list/index.js rename to x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/index.js diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/home/home.container.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/home.container.js similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/home/home.container.js rename to x-pack/plugins/cross_cluster_replication/public/app/sections/home/home.container.js diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/home/home.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/home.js similarity index 96% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/home/home.js rename to x-pack/plugins/cross_cluster_replication/public/app/sections/home/home.js index 88db909612245..bcd9dad114862 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/home/home.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/home.js @@ -10,9 +10,9 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { EuiPageBody, EuiPageContent, EuiSpacer, EuiTab, EuiTabs, EuiTitle } from '@elastic/eui'; -import { BASE_PATH } from '../../../../../common/constants'; +import { BASE_PATH } from '../../../../common/constants'; import { setBreadcrumbs, listBreadcrumb } from '../../services/breadcrumbs'; -import routing from '../../services/routing'; +import { routing } from '../../services/routing'; import { AutoFollowPatternList } from './auto_follow_pattern_list'; import { FollowerIndicesList } from './follower_indices_list'; diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/home/index.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/index.js similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/home/index.js rename to x-pack/plugins/cross_cluster_replication/public/app/sections/home/index.js diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/tags/text.ts b/x-pack/plugins/cross_cluster_replication/public/app/sections/index.d.ts similarity index 50% rename from x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/tags/text.ts rename to x-pack/plugins/cross_cluster_replication/public/app/sections/index.d.ts index f9faf2ad2e3ca..b7c1f495604be 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/tags/text.ts +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/index.d.ts @@ -4,10 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { TagFactory } from '../../../public/lib/tag'; -import { TagStrings as strings } from '../../../i18n'; - -export const text: TagFactory = () => ({ - name: strings.text(), - color: '#D3DAE6', -}); +export declare const CrossClusterReplicationHome: any; +export declare const AutoFollowPatternAdd: any; +export declare const AutoFollowPatternEdit: any; +export declare const FollowerIndexAdd: any; +export declare const FollowerIndexEdit: any; diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/index.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/index.js similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/sections/index.js rename to x-pack/plugins/cross_cluster_replication/public/app/sections/index.js diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/services/__snapshots__/auto_follow_pattern_validators.test.js.snap b/x-pack/plugins/cross_cluster_replication/public/app/services/__snapshots__/auto_follow_pattern_validators.test.js.snap similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/services/__snapshots__/auto_follow_pattern_validators.test.js.snap rename to x-pack/plugins/cross_cluster_replication/public/app/services/__snapshots__/auto_follow_pattern_validators.test.js.snap diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/services/api.js b/x-pack/plugins/cross_cluster_replication/public/app/services/api.js similarity index 98% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/services/api.js rename to x-pack/plugins/cross_cluster_replication/public/app/services/api.js index 24bc7e17356e2..adff40ef29be6 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/services/api.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/services/api.js @@ -7,8 +7,8 @@ import { API_BASE_PATH, API_REMOTE_CLUSTERS_BASE_PATH, API_INDEX_MANAGEMENT_BASE_PATH, -} from '../../../../common/constants'; -import { arrify } from '../../../../common/services/utils'; +} from '../../../common/constants'; +import { arrify } from '../../../common/services/utils'; import { UIM_FOLLOWER_INDEX_CREATE, UIM_FOLLOWER_INDEX_UPDATE, diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/services/auto_follow_errors.js b/x-pack/plugins/cross_cluster_replication/public/app/services/auto_follow_errors.js similarity index 92% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/services/auto_follow_errors.js rename to x-pack/plugins/cross_cluster_replication/public/app/services/auto_follow_errors.js index 95aa3f0ebc3e4..70311d5ba1e4d 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/services/auto_follow_errors.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/services/auto_follow_errors.js @@ -9,11 +9,12 @@ export const parseAutoFollowError = error => { return null; } - const { leaderIndex, autoFollowException } = error; + const { leaderIndex, autoFollowException, timestamp } = error; const id = leaderIndex.substring(0, leaderIndex.lastIndexOf(':')); return { id, + timestamp, leaderIndex, autoFollowException, }; diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/services/auto_follow_errors.test.js b/x-pack/plugins/cross_cluster_replication/public/app/services/auto_follow_errors.test.js similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/services/auto_follow_errors.test.js rename to x-pack/plugins/cross_cluster_replication/public/app/services/auto_follow_errors.test.js diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/services/auto_follow_pattern.js b/x-pack/plugins/cross_cluster_replication/public/app/services/auto_follow_pattern.js similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/services/auto_follow_pattern.js rename to x-pack/plugins/cross_cluster_replication/public/app/services/auto_follow_pattern.js diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/services/auto_follow_pattern.test.js b/x-pack/plugins/cross_cluster_replication/public/app/services/auto_follow_pattern.test.js similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/services/auto_follow_pattern.test.js rename to x-pack/plugins/cross_cluster_replication/public/app/services/auto_follow_pattern.test.js diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/services/auto_follow_pattern_validators.js b/x-pack/plugins/cross_cluster_replication/public/app/services/auto_follow_pattern_validators.js similarity index 97% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/services/auto_follow_pattern_validators.js rename to x-pack/plugins/cross_cluster_replication/public/app/services/auto_follow_pattern_validators.js index 1b5a39658ee46..cf394d4b3c7d8 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/services/auto_follow_pattern_validators.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/services/auto_follow_pattern_validators.js @@ -8,8 +8,8 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { indices } from '../../../../../../../../src/plugins/es_ui_shared/public'; -import { indexPatterns } from '../../../../../../../../src/plugins/data/public'; +import { indices } from '../../../../../../src/plugins/es_ui_shared/public'; +import { indexPatterns } from '../../../../../../src/plugins/data/public'; const { indexNameBeginsWithPeriod, diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/services/auto_follow_pattern_validators.test.js b/x-pack/plugins/cross_cluster_replication/public/app/services/auto_follow_pattern_validators.test.js similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/services/auto_follow_pattern_validators.test.js rename to x-pack/plugins/cross_cluster_replication/public/app/services/auto_follow_pattern_validators.test.js diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/services/breadcrumbs.ts b/x-pack/plugins/cross_cluster_replication/public/app/services/breadcrumbs.ts similarity index 62% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/services/breadcrumbs.ts rename to x-pack/plugins/cross_cluster_replication/public/app/services/breadcrumbs.ts index dc64cdee07f7d..84ac9356462ad 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/services/breadcrumbs.ts +++ b/x-pack/plugins/cross_cluster_replication/public/app/services/breadcrumbs.ts @@ -3,26 +3,20 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ + import { i18n } from '@kbn/i18n'; import { ChromeBreadcrumb } from 'src/core/public'; -import { ManagementAppMountParams } from '../../../../../../../../src/plugins/management/public'; +import { ManagementAppMountParams } from '../../../../../../src/plugins/management/public'; + +import { BASE_PATH } from '../../../common/constants'; -import { BASE_PATH } from '../../../../common/constants'; +export type SetBreadcrumbs = ManagementAppMountParams['setBreadcrumbs']; -let setBreadcrumbs: ManagementAppMountParams['setBreadcrumbs']; +let setBreadcrumbs: (crumbs: ChromeBreadcrumb[]) => void; -export const setBreadcrumbSetter = ({ - __LEGACY, -}: { - __LEGACY: { - chrome: any; - MANAGEMENT_BREADCRUMB: ChromeBreadcrumb; - }; -}): void => { - setBreadcrumbs = (crumbs: ChromeBreadcrumb[]) => { - __LEGACY.chrome.breadcrumbs.set([__LEGACY.MANAGEMENT_BREADCRUMB, ...crumbs]); - }; +export const init = (_setBreadcrumbs: SetBreadcrumbs): void => { + setBreadcrumbs = _setBreadcrumbs; }; export const listBreadcrumb = { diff --git a/x-pack/plugins/cross_cluster_replication/public/app/services/documentation_links.ts b/x-pack/plugins/cross_cluster_replication/public/app/services/documentation_links.ts new file mode 100644 index 0000000000000..c8b00f6e246b5 --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/public/app/services/documentation_links.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +let _esBase: string; + +export const init = (esBase: string) => { + _esBase = esBase; +}; + +export const getAutoFollowPatternUrl = (): string => `${_esBase}/ccr-put-auto-follow-pattern.html`; +export const getFollowerIndexUrl = (): string => `${_esBase}/ccr-put-follow.html`; +export const getByteUnitsUrl = (): string => `${_esBase}/common-options.html#byte-units`; +export const getTimeUnitsUrl = (): string => `${_esBase}/common-options.html#time-units`; diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/services/follower_index_default_settings.js b/x-pack/plugins/cross_cluster_replication/public/app/services/follower_index_default_settings.js similarity index 89% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/services/follower_index_default_settings.js rename to x-pack/plugins/cross_cluster_replication/public/app/services/follower_index_default_settings.js index d20fa76ef5451..118a54887d404 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/services/follower_index_default_settings.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/services/follower_index_default_settings.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { FOLLOWER_INDEX_ADVANCED_SETTINGS } from '../../../../common/constants'; +import { FOLLOWER_INDEX_ADVANCED_SETTINGS } from '../../../common/constants'; export const getSettingDefault = name => { if (!FOLLOWER_INDEX_ADVANCED_SETTINGS[name]) { diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/services/get_remote_cluster_name.js b/x-pack/plugins/cross_cluster_replication/public/app/services/get_remote_cluster_name.js similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/services/get_remote_cluster_name.js rename to x-pack/plugins/cross_cluster_replication/public/app/services/get_remote_cluster_name.js diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/services/input_validation.js b/x-pack/plugins/cross_cluster_replication/public/app/services/input_validation.js similarity index 97% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/services/input_validation.js rename to x-pack/plugins/cross_cluster_replication/public/app/services/input_validation.js index 64c3e8412437e..7e2b45b625c1f 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/services/input_validation.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/services/input_validation.js @@ -6,7 +6,7 @@ import React from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -import { indices } from '../../../../../../../../src/plugins/es_ui_shared/public'; +import { indices } from '../../../../../../src/plugins/es_ui_shared/public'; const isEmpty = value => { return !value || !value.trim().length; diff --git a/x-pack/plugins/cross_cluster_replication/public/app/services/notifications.ts b/x-pack/plugins/cross_cluster_replication/public/app/services/notifications.ts new file mode 100644 index 0000000000000..66fc9de00995c --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/public/app/services/notifications.ts @@ -0,0 +1,18 @@ +/* + * 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 { IToasts, FatalErrorsSetup } from 'src/core/public'; + +let _toasts: IToasts; +let _fatalErrors: FatalErrorsSetup; + +export const init = (toasts: IToasts, fatalErrors: FatalErrorsSetup) => { + _toasts = toasts; + _fatalErrors = fatalErrors; +}; + +export const getToasts = () => _toasts; +export const getFatalErrors = () => _fatalErrors; diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/services/query_params.js b/x-pack/plugins/cross_cluster_replication/public/app/services/query_params.js similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/services/query_params.js rename to x-pack/plugins/cross_cluster_replication/public/app/services/query_params.js diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/index.js b/x-pack/plugins/cross_cluster_replication/public/app/services/routing.d.ts similarity index 87% rename from x-pack/legacy/plugins/cross_cluster_replication/public/index.js rename to x-pack/plugins/cross_cluster_replication/public/app/services/routing.d.ts index e92c44da34474..9e96ea12856f6 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/public/index.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/services/routing.d.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -import './register_routes'; +export declare const routing: any; diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/services/routing.js b/x-pack/plugins/cross_cluster_replication/public/app/services/routing.js similarity index 93% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/services/routing.js rename to x-pack/plugins/cross_cluster_replication/public/app/services/routing.js index 965aeaaad22ad..124c61e1ba19e 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/services/routing.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/services/routing.js @@ -10,7 +10,7 @@ import { createLocation } from 'history'; import { stringify } from 'query-string'; -import { APPS, BASE_PATH, BASE_PATH_REMOTE_CLUSTERS } from '../../../../common/constants'; +import { APPS, BASE_PATH, BASE_PATH_REMOTE_CLUSTERS } from '../../../common/constants'; const isModifiedEvent = event => !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey); @@ -32,7 +32,6 @@ const appToBasePathMap = { }; class Routing { - _userHasLeftApp = false; _reactRouter = null; /** @@ -97,14 +96,6 @@ class Routing { set reactRouter(router) { this._reactRouter = router; } - - get userHasLeftApp() { - return this._userHasLeftApp; - } - - set userHasLeftApp(hasLeft) { - this._userHasLeftApp = hasLeft; - } } -export default new Routing(); +export const routing = new Routing(); diff --git a/x-pack/plugins/cross_cluster_replication/public/app/services/track_ui_metric.ts b/x-pack/plugins/cross_cluster_replication/public/app/services/track_ui_metric.ts new file mode 100644 index 0000000000000..aecc4eb83893f --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/public/app/services/track_ui_metric.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { UsageCollectionSetup } from 'src/plugins/usage_collection/public'; +import { UiStatsMetricType, METRIC_TYPE } from '@kbn/analytics'; + +import { UIM_APP_NAME } from '../constants'; + +export { METRIC_TYPE }; + +// usageCollection is an optional dependency, so we default to a no-op. +export let trackUiMetric = (metricType: UiStatsMetricType, eventName: string) => {}; + +export function init(usageCollection: UsageCollectionSetup): void { + trackUiMetric = usageCollection.reportUiStats.bind(usageCollection, UIM_APP_NAME); +} + +/** + * Transparently return provided request Promise, while allowing us to track + * a successful completion of the request. + */ +export function trackUserRequest(request: Promise, actionType: string) { + // Only track successful actions. + return request.then(response => { + // It looks like we're using the wrong type here, added via + // https://github.com/elastic/kibana/pull/41113/files#diff-e65a0a6696a9d723969afd871cbd60cdR19 + // but we'll keep it for now to avoid discontinuity in our telemetry data. + trackUiMetric(METRIC_TYPE.LOADED, actionType); + + // We return the response immediately without waiting for the tracking request to resolve, + // to avoid adding additional latency. + return response; + }); +} diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/services/utils.js b/x-pack/plugins/cross_cluster_replication/public/app/services/utils.js similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/services/utils.js rename to x-pack/plugins/cross_cluster_replication/public/app/services/utils.js diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/services/utils.test.js b/x-pack/plugins/cross_cluster_replication/public/app/services/utils.test.js similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/services/utils.test.js rename to x-pack/plugins/cross_cluster_replication/public/app/services/utils.test.js diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/store/action_types.js b/x-pack/plugins/cross_cluster_replication/public/app/store/action_types.js similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/store/action_types.js rename to x-pack/plugins/cross_cluster_replication/public/app/store/action_types.js diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/store/actions/api.js b/x-pack/plugins/cross_cluster_replication/public/app/store/actions/api.js similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/store/actions/api.js rename to x-pack/plugins/cross_cluster_replication/public/app/store/actions/api.js diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/store/actions/auto_follow_pattern.js b/x-pack/plugins/cross_cluster_replication/public/app/store/actions/auto_follow_pattern.js similarity index 95% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/store/actions/auto_follow_pattern.js rename to x-pack/plugins/cross_cluster_replication/public/app/store/actions/auto_follow_pattern.js index b81cd30f3977a..52a22cb17d0a9 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/store/actions/auto_follow_pattern.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/store/actions/auto_follow_pattern.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { i18n } from '@kbn/i18n'; -import { getNotifications } from '../../services/notifications'; +import { getToasts } from '../../services/notifications'; import { SECTIONS, API_STATUS } from '../../constants'; import { loadAutoFollowPatterns as loadAutoFollowPatternsRequest, @@ -15,7 +15,7 @@ import { pauseAutoFollowPattern as pauseAutoFollowPatternRequest, resumeAutoFollowPattern as resumeAutoFollowPatternRequest, } from '../../services/api'; -import routing from '../../services/routing'; +import { routing } from '../../services/routing'; import * as t from '../action_types'; import { sendApiRequest } from './api'; import { getSelectedAutoFollowPatternId } from '../selectors'; @@ -75,7 +75,7 @@ export const saveAutoFollowPattern = (id, autoFollowPattern, isUpdating = false) } ); - getNotifications().addSuccess(successMessage); + getToasts().addSuccess(successMessage); routing.navigate(`/auto_follow_patterns`, undefined, { pattern: encodeURIComponent(id), }); @@ -111,7 +111,7 @@ export const deleteAutoFollowPattern = id => } ); - getNotifications().addDanger(errorMessage); + getToasts().addDanger(errorMessage); } if (response.itemsDeleted.length) { @@ -133,7 +133,7 @@ export const deleteAutoFollowPattern = id => } ); - getNotifications().addSuccess(successMessage); + getToasts().addSuccess(successMessage); // If we've just deleted a pattern we were looking at, we need to close the panel. const autoFollowPatternId = getSelectedAutoFollowPatternId('detail')(getState()); @@ -173,7 +173,7 @@ export const pauseAutoFollowPattern = id => } ); - getNotifications().addDanger(errorMessage); + getToasts().addDanger(errorMessage); } if (response.itemsPaused.length) { @@ -195,7 +195,7 @@ export const pauseAutoFollowPattern = id => } ); - getNotifications().addSuccess(successMessage); + getToasts().addSuccess(successMessage); } }, }); @@ -229,7 +229,7 @@ export const resumeAutoFollowPattern = id => } ); - getNotifications().addDanger(errorMessage); + getToasts().addDanger(errorMessage); } if (response.itemsResumed.length) { @@ -251,7 +251,7 @@ export const resumeAutoFollowPattern = id => } ); - getNotifications().addSuccess(successMessage); + getToasts().addSuccess(successMessage); } }, }); diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/store/actions/ccr.js b/x-pack/plugins/cross_cluster_replication/public/app/store/actions/ccr.js similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/store/actions/ccr.js rename to x-pack/plugins/cross_cluster_replication/public/app/store/actions/ccr.js diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/store/actions/follower_index.js b/x-pack/plugins/cross_cluster_replication/public/app/store/actions/follower_index.js similarity index 94% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/store/actions/follower_index.js rename to x-pack/plugins/cross_cluster_replication/public/app/store/actions/follower_index.js index ebdee067ced75..d081e0444eb58 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/store/actions/follower_index.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/store/actions/follower_index.js @@ -5,8 +5,8 @@ */ import { i18n } from '@kbn/i18n'; -import routing from '../../services/routing'; -import { getNotifications } from '../../services/notifications'; +import { routing } from '../../services/routing'; +import { getToasts } from '../../services/notifications'; import { SECTIONS, API_STATUS } from '../../constants'; import { loadFollowerIndices as loadFollowerIndicesRequest, @@ -76,7 +76,7 @@ export const saveFollowerIndex = (name, followerIndex, isUpdating = false) => } ); - getNotifications().addSuccess(successMessage); + getToasts().addSuccess(successMessage); routing.navigate(`/follower_indices`, undefined, { name: encodeURIComponent(name), }); @@ -112,7 +112,7 @@ export const pauseFollowerIndex = id => } ); - getNotifications().addDanger(errorMessage); + getToasts().addDanger(errorMessage); } if (response.itemsPaused.length) { @@ -134,7 +134,7 @@ export const pauseFollowerIndex = id => } ); - getNotifications().addSuccess(successMessage); + getToasts().addSuccess(successMessage); // Refresh list dispatch(loadFollowerIndices(true)); @@ -149,6 +149,7 @@ export const resumeFollowerIndex = id => scope, handler: async () => resumeFollowerIndexRequest(id), onSuccess(response, dispatch) { + console.log('response', response); /** * We can have 1 or more follower index resume operation * that can fail or succeed. We will show 1 toast notification for each. @@ -171,7 +172,7 @@ export const resumeFollowerIndex = id => } ); - getNotifications().addDanger(errorMessage); + getToasts().addDanger(errorMessage); } if (response.itemsResumed.length) { @@ -193,7 +194,7 @@ export const resumeFollowerIndex = id => } ); - getNotifications().addSuccess(successMessage); + getToasts().addSuccess(successMessage); } // Refresh list @@ -230,7 +231,7 @@ export const unfollowLeaderIndex = id => } ); - getNotifications().addDanger(errorMessage); + getToasts().addDanger(errorMessage); } if (response.itemsUnfollowed.length) { @@ -252,7 +253,7 @@ export const unfollowLeaderIndex = id => } ); - getNotifications().addSuccess(successMessage); + getToasts().addSuccess(successMessage); } if (response.itemsNotOpen.length) { @@ -274,7 +275,7 @@ export const unfollowLeaderIndex = id => } ); - getNotifications().addWarning(warningMessage); + getToasts().addWarning(warningMessage); } // If we've just unfollowed a follower index we were looking at, we need to close the panel. diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/store/actions/index.js b/x-pack/plugins/cross_cluster_replication/public/app/store/actions/index.js similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/store/actions/index.js rename to x-pack/plugins/cross_cluster_replication/public/app/store/actions/index.js diff --git a/x-pack/plugins/cross_cluster_replication/public/app/store/index.d.ts b/x-pack/plugins/cross_cluster_replication/public/app/store/index.d.ts new file mode 100644 index 0000000000000..6d35dfeddfd46 --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/public/app/store/index.d.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export declare const ccrStore: any; diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/store/index.js b/x-pack/plugins/cross_cluster_replication/public/app/store/index.js similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/store/index.js rename to x-pack/plugins/cross_cluster_replication/public/app/store/index.js diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/store/reducers/api.js b/x-pack/plugins/cross_cluster_replication/public/app/store/reducers/api.js similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/store/reducers/api.js rename to x-pack/plugins/cross_cluster_replication/public/app/store/reducers/api.js diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/store/reducers/api.test.js b/x-pack/plugins/cross_cluster_replication/public/app/store/reducers/api.test.js similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/store/reducers/api.test.js rename to x-pack/plugins/cross_cluster_replication/public/app/store/reducers/api.test.js diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/store/reducers/auto_follow_pattern.js b/x-pack/plugins/cross_cluster_replication/public/app/store/reducers/auto_follow_pattern.js similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/store/reducers/auto_follow_pattern.js rename to x-pack/plugins/cross_cluster_replication/public/app/store/reducers/auto_follow_pattern.js diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/store/reducers/follower_index.js b/x-pack/plugins/cross_cluster_replication/public/app/store/reducers/follower_index.js similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/store/reducers/follower_index.js rename to x-pack/plugins/cross_cluster_replication/public/app/store/reducers/follower_index.js diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/store/reducers/index.js b/x-pack/plugins/cross_cluster_replication/public/app/store/reducers/index.js similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/store/reducers/index.js rename to x-pack/plugins/cross_cluster_replication/public/app/store/reducers/index.js diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/store/reducers/stats.js b/x-pack/plugins/cross_cluster_replication/public/app/store/reducers/stats.js similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/store/reducers/stats.js rename to x-pack/plugins/cross_cluster_replication/public/app/store/reducers/stats.js diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/store/selectors/index.js b/x-pack/plugins/cross_cluster_replication/public/app/store/selectors/index.js similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/store/selectors/index.js rename to x-pack/plugins/cross_cluster_replication/public/app/store/selectors/index.js diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/store/store.js b/x-pack/plugins/cross_cluster_replication/public/app/store/store.js similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/app/store/store.js rename to x-pack/plugins/cross_cluster_replication/public/app/store/store.js diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/index.ts b/x-pack/plugins/cross_cluster_replication/public/index.ts similarity index 61% rename from x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/index.ts rename to x-pack/plugins/cross_cluster_replication/public/index.ts index 11aea6b7b5de4..e3e2d860e526d 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/public/np_ready/index.ts +++ b/x-pack/plugins/cross_cluster_replication/public/index.ts @@ -6,6 +6,7 @@ import { PluginInitializerContext } from 'src/core/public'; -import { CrossClusterReplicationUIPlugin } from './plugin'; +import { CrossClusterReplicationPlugin } from './plugin'; -export const plugin = (ctx: PluginInitializerContext) => new CrossClusterReplicationUIPlugin(ctx); +export const plugin = (initializerContext: PluginInitializerContext) => + new CrossClusterReplicationPlugin(initializerContext); diff --git a/x-pack/plugins/cross_cluster_replication/public/plugin.ts b/x-pack/plugins/cross_cluster_replication/public/plugin.ts new file mode 100644 index 0000000000000..bdaa04e9d53ee --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/public/plugin.ts @@ -0,0 +1,102 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { get } from 'lodash'; +import { first } from 'rxjs/operators'; +import { CoreSetup, Plugin, PluginInitializerContext } from 'src/core/public'; + +import { PLUGIN, MANAGEMENT_ID } from '../common/constants'; +import { init as initUiMetric } from './app/services/track_ui_metric'; +import { init as initNotification } from './app/services/notifications'; +import { PluginDependencies, ClientConfigType } from './types'; + +// @ts-ignore; +import { setHttpClient } from './app/services/api'; + +export class CrossClusterReplicationPlugin implements Plugin { + constructor(private readonly initializerContext: PluginInitializerContext) {} + + public setup(coreSetup: CoreSetup, plugins: PluginDependencies) { + const { licensing, remoteClusters, usageCollection, management, indexManagement } = plugins; + const esSection = management.sections.getSection('elasticsearch'); + + const { + http, + notifications: { toasts }, + fatalErrors, + getStartServices, + } = coreSetup; + + // Initialize services even if the app isn't mounted, because they're used by index management extensions. + setHttpClient(http); + initUiMetric(usageCollection); + initNotification(toasts, fatalErrors); + + const ccrApp = esSection!.registerApp({ + id: MANAGEMENT_ID, + title: PLUGIN.TITLE, + order: 4, + mount: async ({ element, setBreadcrumbs }) => { + const { mountApp } = await import('./app'); + + const [coreStart] = await getStartServices(); + const { + i18n: { Context: I18nContext }, + docLinks: { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION }, + } = coreStart; + + return mountApp({ + element, + setBreadcrumbs, + I18nContext, + ELASTIC_WEBSITE_URL, + DOC_LINK_VERSION, + }); + }, + }); + + ccrApp.disable(); + + licensing.license$ + .pipe(first()) + .toPromise() + .then(license => { + const licenseStatus = license.check(PLUGIN.ID, PLUGIN.minimumLicenseType); + const isLicenseOk = licenseStatus.state === 'valid'; + const config = this.initializerContext.config.get(); + + // remoteClusters.isUiEnabled is driven by the xpack.remote_clusters.ui.enabled setting. + // The CCR UI depends upon the Remote Clusters UI (e.g. by cross-linking to it), so if + // the Remote Clusters UI is disabled we can't show the CCR UI. + const isCcrUiEnabled = config.ui.enabled && remoteClusters.isUiEnabled; + + if (isLicenseOk && isCcrUiEnabled) { + ccrApp.enable(); + + if (indexManagement) { + const propertyPath = 'isFollowerIndex'; + + const followerBadgeExtension = { + matchIndex: (index: any) => { + return get(index, propertyPath); + }, + label: i18n.translate('xpack.crossClusterReplication.indexMgmtBadge.followerLabel', { + defaultMessage: 'Follower', + }), + color: 'default', + filterExpression: 'isFollowerIndex:true', + }; + + indexManagement.extensionsService.addBadge(followerBadgeExtension); + } + } + }); + } + + public start() {} + public stop() {} +} diff --git a/x-pack/plugins/cross_cluster_replication/public/types.ts b/x-pack/plugins/cross_cluster_replication/public/types.ts new file mode 100644 index 0000000000000..aac174b7524d3 --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/public/types.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/public'; +import { ManagementSetup } from '../../../../src/plugins/management/public'; +import { IndexManagementPluginSetup } from '../../index_management/public'; +import { RemoteClustersPluginSetup } from '../../remote_clusters/public'; +import { LicensingPluginSetup } from '../../licensing/public'; + +export interface PluginDependencies { + usageCollection: UsageCollectionSetup; + management: ManagementSetup; + indexManagement: IndexManagementPluginSetup; + remoteClusters: RemoteClustersPluginSetup; + licensing: LicensingPluginSetup; +} + +export interface ClientConfigType { + ui: { + enabled: boolean; + }; +} diff --git a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/client/elasticsearch_ccr.js b/x-pack/plugins/cross_cluster_replication/server/client/elasticsearch_ccr.ts similarity index 97% rename from x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/client/elasticsearch_ccr.js rename to x-pack/plugins/cross_cluster_replication/server/client/elasticsearch_ccr.ts index 91527b8eb7cc5..d4de54391286b 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/client/elasticsearch_ccr.js +++ b/x-pack/plugins/cross_cluster_replication/server/client/elasticsearch_ccr.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -export const elasticsearchJsPlugin = (Client, config, components) => { +export const elasticsearchJsPlugin = (Client: any, config: any, components: any) => { const ca = components.clientAction.factory; Client.prototype.ccr = components.clientAction.namespaceFactory(); diff --git a/x-pack/plugins/cross_cluster_replication/server/config.ts b/x-pack/plugins/cross_cluster_replication/server/config.ts new file mode 100644 index 0000000000000..17999d37c76b7 --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/server/config.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema, TypeOf } from '@kbn/config-schema'; + +export const configSchema = schema.object({ + enabled: schema.boolean({ defaultValue: true }), + ui: schema.object({ + enabled: schema.boolean({ defaultValue: true }), + }), +}); + +export type CrossClusterReplicationConfig = TypeOf; diff --git a/x-pack/plugins/cross_cluster_replication/server/index.ts b/x-pack/plugins/cross_cluster_replication/server/index.ts new file mode 100644 index 0000000000000..597c039ad202e --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/server/index.ts @@ -0,0 +1,19 @@ +/* + * 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 { PluginInitializerContext, PluginConfigDescriptor } from 'src/core/server'; +import { CrossClusterReplicationServerPlugin } from './plugin'; +import { configSchema, CrossClusterReplicationConfig } from './config'; + +export const plugin = (pluginInitializerContext: PluginInitializerContext) => + new CrossClusterReplicationServerPlugin(pluginInitializerContext); + +export const config: PluginConfigDescriptor = { + schema: configSchema, + exposeToBrowser: { + ui: true, + }, +}; diff --git a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/lib/__snapshots__/ccr_stats_serialization.test.js.snap b/x-pack/plugins/cross_cluster_replication/server/lib/__snapshots__/ccr_stats_serialization.test.ts.snap similarity index 93% rename from x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/lib/__snapshots__/ccr_stats_serialization.test.js.snap rename to x-pack/plugins/cross_cluster_replication/server/lib/__snapshots__/ccr_stats_serialization.test.ts.snap index 92ac6070904b5..3eced37112a35 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/lib/__snapshots__/ccr_stats_serialization.test.js.snap +++ b/x-pack/plugins/cross_cluster_replication/server/lib/__snapshots__/ccr_stats_serialization.test.ts.snap @@ -19,6 +19,7 @@ Object { "type": "exception", }, "leaderIndex": "pattern-1:kibana_sample_1", + "timestamp": 1587081600021, }, Object { "autoFollowException": Object { @@ -26,6 +27,7 @@ Object { "type": "exception", }, "leaderIndex": "pattern-2:kibana_sample_1", + "timestamp": 1587081600021, }, ], } diff --git a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/lib/ccr_stats_serialization.test.js b/x-pack/plugins/cross_cluster_replication/server/lib/ccr_stats_serialization.test.ts similarity index 95% rename from x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/lib/ccr_stats_serialization.test.js rename to x-pack/plugins/cross_cluster_replication/server/lib/ccr_stats_serialization.test.ts index 5120c56701e5b..5141aa56c1d7e 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/lib/ccr_stats_serialization.test.js +++ b/x-pack/plugins/cross_cluster_replication/server/lib/ccr_stats_serialization.test.ts @@ -15,6 +15,7 @@ describe('[CCR] auto-follow stats serialization', () => { recent_auto_follow_errors: [ { leader_index: 'pattern-1:kibana_sample_1', + timestamp: 1587081600021, auto_follow_exception: { type: 'exception', reason: @@ -23,6 +24,7 @@ describe('[CCR] auto-follow stats serialization', () => { }, { leader_index: 'pattern-2:kibana_sample_1', + timestamp: 1587081600021, auto_follow_exception: { type: 'exception', reason: diff --git a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/lib/ccr_stats_serialization.js b/x-pack/plugins/cross_cluster_replication/server/lib/ccr_stats_serialization.ts similarity index 77% rename from x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/lib/ccr_stats_serialization.js rename to x-pack/plugins/cross_cluster_replication/server/lib/ccr_stats_serialization.ts index e4d2f8d64d1bb..7e2b088919842 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/lib/ccr_stats_serialization.js +++ b/x-pack/plugins/cross_cluster_replication/server/lib/ccr_stats_serialization.ts @@ -4,11 +4,21 @@ * you may not use this file except in compliance with the Elastic License. */ -/* eslint-disable camelcase */ +import { + RecentAutoFollowError, + RecentAutoFollowErrorFromEs, + AutoFollowedCluster, + AutoFollowedClusterFromEs, + AutoFollowStats, + AutoFollowStatsFromEs, +} from '../../common/types'; + export const deserializeRecentAutoFollowErrors = ({ + timestamp, leader_index, auto_follow_exception: { type, reason }, -}) => ({ +}: RecentAutoFollowErrorFromEs): RecentAutoFollowError => ({ + timestamp, leaderIndex: leader_index, autoFollowException: { type, @@ -20,7 +30,7 @@ export const deserializeAutoFollowedClusters = ({ cluster_name, time_since_last_check_millis, last_seen_metadata_version, -}) => ({ +}: AutoFollowedClusterFromEs): AutoFollowedCluster => ({ clusterName: cluster_name, timeSinceLastCheckMillis: time_since_last_check_millis, lastSeenMetadataVersion: last_seen_metadata_version, @@ -32,11 +42,10 @@ export const deserializeAutoFollowStats = ({ number_of_successful_follow_indices, recent_auto_follow_errors, auto_followed_clusters, -}) => ({ +}: AutoFollowStatsFromEs): AutoFollowStats => ({ numberOfFailedFollowIndices: number_of_failed_follow_indices, numberOfFailedRemoteClusterStateRequests: number_of_failed_remote_cluster_state_requests, numberOfSuccessfulFollowIndices: number_of_successful_follow_indices, recentAutoFollowErrors: recent_auto_follow_errors.map(deserializeRecentAutoFollowErrors), autoFollowedClusters: auto_followed_clusters.map(deserializeAutoFollowedClusters), }); -/* eslint-enable camelcase */ diff --git a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/lib/error_wrappers/wrap_es_error.ts b/x-pack/plugins/cross_cluster_replication/server/lib/format_es_error.ts similarity index 90% rename from x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/lib/error_wrappers/wrap_es_error.ts rename to x-pack/plugins/cross_cluster_replication/server/lib/format_es_error.ts index 8afd5f1a018eb..9dde027cd6949 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/lib/error_wrappers/wrap_es_error.ts +++ b/x-pack/plugins/cross_cluster_replication/server/lib/format_es_error.ts @@ -63,3 +63,16 @@ export function wrapEsError( const message = statusCodeToMessageMap[statusCode]; return { message, statusCode }; } + +export function formatEsError(err: any): any { + const { statusCode, message, body } = wrapEsError(err); + return { + statusCode, + body: { + message, + attributes: { + cause: body?.cause, + }, + }, + }; +} diff --git a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/lib/is_es_error.ts b/x-pack/plugins/cross_cluster_replication/server/lib/is_es_error.ts similarity index 100% rename from x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/lib/is_es_error.ts rename to x-pack/plugins/cross_cluster_replication/server/lib/is_es_error.ts diff --git a/x-pack/plugins/cross_cluster_replication/server/plugin.ts b/x-pack/plugins/cross_cluster_replication/server/plugin.ts new file mode 100644 index 0000000000000..25c99803480f3 --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/server/plugin.ts @@ -0,0 +1,139 @@ +/* + * 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. + */ + +declare module 'src/core/server' { + interface RequestHandlerContext { + crossClusterReplication?: CrossClusterReplicationContext; + } +} + +import { Observable } from 'rxjs'; +import { first } from 'rxjs/operators'; +import { i18n } from '@kbn/i18n'; +import { + CoreSetup, + Plugin, + Logger, + PluginInitializerContext, + APICaller, + IScopedClusterClient, +} from 'src/core/server'; + +import { Index } from '../../index_management/server'; +import { PLUGIN } from '../common/constants'; +import { Dependencies } from './types'; +import { registerApiRoutes } from './routes'; +import { License } from './services'; +import { elasticsearchJsPlugin } from './client/elasticsearch_ccr'; +import { CrossClusterReplicationConfig } from './config'; +import { isEsError } from './lib/is_es_error'; +import { formatEsError } from './lib/format_es_error'; + +interface CrossClusterReplicationContext { + client: IScopedClusterClient; +} + +const ccrDataEnricher = async (indicesList: Index[], callWithRequest: APICaller) => { + if (!indicesList?.length) { + return indicesList; + } + const params = { + path: '/_all/_ccr/info', + method: 'GET', + }; + try { + const { follower_indices: followerIndices } = await callWithRequest( + 'transport.request', + params + ); + return indicesList.map(index => { + const isFollowerIndex = !!followerIndices.find( + (followerIndex: { follower_index: string }) => { + return followerIndex.follower_index === index.name; + } + ); + return { + ...index, + isFollowerIndex, + }; + }); + } catch (e) { + return indicesList; + } +}; + +export class CrossClusterReplicationServerPlugin implements Plugin { + private readonly config$: Observable; + private readonly license: License; + private readonly logger: Logger; + + constructor(initializerContext: PluginInitializerContext) { + this.logger = initializerContext.logger.get(); + this.config$ = initializerContext.config.create(); + this.license = new License(); + } + + setup( + { http, elasticsearch }: CoreSetup, + { licensing, indexManagement, remoteClusters }: Dependencies + ) { + this.config$ + .pipe(first()) + .toPromise() + .then(config => { + // remoteClusters.isUiEnabled is driven by the xpack.remote_clusters.ui.enabled setting. + // The CCR UI depends upon the Remote Clusters UI (e.g. by cross-linking to it), so if + // the Remote Clusters UI is disabled we can't show the CCR UI. + const isCcrUiEnabled = config.ui.enabled && remoteClusters.isUiEnabled; + + // If the UI isn't enabled, then we don't want to expose any CCR concepts in the UI, including + // "follower" badges for follower indices. + if (isCcrUiEnabled) { + if (indexManagement.indexDataEnricher) { + indexManagement.indexDataEnricher.add(ccrDataEnricher); + } + } + }); + + this.license.setup( + { + pluginId: PLUGIN.ID, + minimumLicenseType: PLUGIN.minimumLicenseType, + defaultErrorMessage: i18n.translate( + 'xpack.crossClusterReplication.licenseCheckErrorMessage', + { + defaultMessage: 'License check failed', + } + ), + }, + { + licensing, + logger: this.logger, + } + ); + + // Extend the elasticsearchJs client with additional endpoints. + const esClientConfig = { plugins: [elasticsearchJsPlugin] }; + const ccrEsClient = elasticsearch.createClient('crossClusterReplication', esClientConfig); + http.registerRouteHandlerContext('crossClusterReplication', (ctx, request) => { + return { + client: ccrEsClient.asScoped(request), + }; + }); + + registerApiRoutes({ + router: http.createRouter(), + license: this.license, + lib: { + isEsError, + formatEsError, + }, + }); + } + + start() {} + stop() {} +} diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/index.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/index.ts new file mode 100644 index 0000000000000..4cbdc7703a694 --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/index.ts @@ -0,0 +1,24 @@ +/* + * 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 { RouteDependencies } from '../../../types'; +import { registerCreateRoute } from './register_create_route'; +import { registerDeleteRoute } from './register_delete_route'; +import { registerFetchRoute } from './register_fetch_route'; +import { registerGetRoute } from './register_get_route'; +import { registerPauseRoute } from './register_pause_route'; +import { registerResumeRoute } from './register_resume_route'; +import { registerUpdateRoute } from './register_update_route'; + +export function registerAutoFollowPatternRoutes(dependencies: RouteDependencies) { + registerCreateRoute(dependencies); + registerDeleteRoute(dependencies); + registerFetchRoute(dependencies); + registerGetRoute(dependencies); + registerPauseRoute(dependencies); + registerResumeRoute(dependencies); + registerUpdateRoute(dependencies); +} diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_create_route.test.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_create_route.test.ts new file mode 100644 index 0000000000000..b41b52e1764c8 --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_create_route.test.ts @@ -0,0 +1,74 @@ +/* + * 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 { httpServiceMock, httpServerMock } from 'src/core/server/mocks'; +import { IRouter, kibanaResponseFactory, RequestHandler } from 'src/core/server'; + +import { isEsError } from '../../../lib/is_es_error'; +import { formatEsError } from '../../../lib/format_es_error'; +import { License } from '../../../services'; +import { mockRouteContext } from '../test_lib'; +import { registerCreateRoute } from './register_create_route'; + +const httpService = httpServiceMock.createSetupContract(); + +describe('[CCR API] Create auto-follow pattern', () => { + let routeHandler: RequestHandler; + + beforeEach(() => { + const router = httpService.createRouter('') as jest.Mocked; + + registerCreateRoute({ + router, + license: { + guardApiRoute: (route: any) => route, + } as License, + lib: { + isEsError, + formatEsError, + }, + }); + + routeHandler = router.post.mock.calls[0][1]; + }); + + it('should throw a 409 conflict error if id already exists', async () => { + const routeContextMock = mockRouteContext({ + // Fail the uniqueness check. + callAsCurrentUser: jest.fn().mockResolvedValueOnce(true), + }); + + const request = httpServerMock.createKibanaRequest({ + body: { + id: 'some-id', + foo: 'bar', + }, + }); + + const response = await routeHandler(routeContextMock, request, kibanaResponseFactory); + expect(response.status).toEqual(409); + }); + + it('should return 200 status when the id does not exist', async () => { + const routeContextMock = mockRouteContext({ + callAsCurrentUser: jest + .fn() + // Pass the uniqueness check. + .mockRejectedValueOnce({ statusCode: 404 }) + .mockResolvedValueOnce(true), + }); + + const request = httpServerMock.createKibanaRequest({ + body: { + id: 'some-id', + foo: 'bar', + }, + }); + + const response = await routeHandler(routeContextMock, request, kibanaResponseFactory); + expect(response.status).toEqual(200); + }); +}); diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_create_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_create_route.ts new file mode 100644 index 0000000000000..12503e3532a47 --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_create_route.ts @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema } from '@kbn/config-schema'; +import { serializeAutoFollowPattern } from '../../../../common/services/auto_follow_pattern_serialization'; +import { AutoFollowPattern } from '../../../../common/types'; +import { addBasePath } from '../../../services'; +import { RouteDependencies } from '../../../types'; + +/** + * Create an auto-follow pattern + */ +export const registerCreateRoute = ({ + router, + license, + lib: { isEsError, formatEsError }, +}: RouteDependencies) => { + const bodySchema = schema.object({ + id: schema.string(), + remoteCluster: schema.string(), + leaderIndexPatterns: schema.arrayOf(schema.string()), + followIndexPattern: schema.string(), + }); + + router.post( + { + path: addBasePath('/auto_follow_patterns'), + validate: { + body: bodySchema, + }, + }, + license.guardApiRoute(async (context, request, response) => { + const { id, ...rest } = request.body; + const body = serializeAutoFollowPattern(rest as AutoFollowPattern); + + /** + * First let's make sure that an auto-follow pattern with + * the same id does not exist. + */ + try { + await context.crossClusterReplication!.client.callAsCurrentUser('ccr.autoFollowPattern', { + id, + }); + // If we get here it means that an auto-follow pattern with the same id exists + return response.conflict({ + body: `An auto-follow pattern with the name "${id}" already exists.`, + }); + } catch (err) { + if (err.statusCode !== 404) { + if (isEsError(err)) { + return response.customError(formatEsError(err)); + } + // Case: default + return response.internalError({ body: err }); + } + } + + try { + return response.ok({ + body: await context.crossClusterReplication!.client.callAsCurrentUser( + 'ccr.saveAutoFollowPattern', + { id, body } + ), + }); + } catch (err) { + if (isEsError(err)) { + return response.customError(formatEsError(err)); + } + // Case: default + return response.internalError({ body: err }); + } + }) + ); +}; diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_delete_route.test.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_delete_route.test.ts new file mode 100644 index 0000000000000..e610d09b44275 --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_delete_route.test.ts @@ -0,0 +1,87 @@ +/* + * 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 { httpServiceMock, httpServerMock } from 'src/core/server/mocks'; +import { IRouter, kibanaResponseFactory, RequestHandler } from 'src/core/server'; + +import { isEsError } from '../../../lib/is_es_error'; +import { formatEsError } from '../../../lib/format_es_error'; +import { License } from '../../../services'; +import { mockRouteContext } from '../test_lib'; +import { registerDeleteRoute } from './register_delete_route'; + +const httpService = httpServiceMock.createSetupContract(); + +describe('[CCR API] Delete auto-follow pattern(s)', () => { + let routeHandler: RequestHandler; + + beforeEach(() => { + const router = httpService.createRouter('') as jest.Mocked; + + registerDeleteRoute({ + router, + license: { + guardApiRoute: (route: any) => route, + } as License, + lib: { + isEsError, + formatEsError, + }, + }); + + routeHandler = router.delete.mock.calls[0][1]; + }); + + it('deletes a single item', async () => { + const routeContextMock = mockRouteContext({ + callAsCurrentUser: jest.fn().mockResolvedValueOnce({ acknowledge: true }), + }); + + const request = httpServerMock.createKibanaRequest({ + params: { id: 'a' }, + }); + + const response = await routeHandler(routeContextMock, request, kibanaResponseFactory); + expect(response.payload.itemsDeleted).toEqual(['a']); + expect(response.payload.errors).toEqual([]); + }); + + it('deletes multiple items', async () => { + const routeContextMock = mockRouteContext({ + callAsCurrentUser: jest + .fn() + .mockResolvedValueOnce({ acknowledge: true }) + .mockResolvedValueOnce({ acknowledge: true }) + .mockResolvedValueOnce({ acknowledge: true }), + }); + + const request = httpServerMock.createKibanaRequest({ + params: { id: 'a,b,c' }, + }); + + const response = await routeHandler(routeContextMock, request, kibanaResponseFactory); + expect(response.payload.itemsDeleted).toEqual(['a', 'b', 'c']); + expect(response.payload.errors).toEqual([]); + }); + + it('returns partial errors', async () => { + const routeContextMock = mockRouteContext({ + callAsCurrentUser: jest + .fn() + .mockResolvedValueOnce({ acknowledge: true }) + .mockRejectedValueOnce({ response: { error: {} } }), + }); + + const request = httpServerMock.createKibanaRequest({ + params: { id: 'a,b' }, + }); + + const response = await routeHandler(routeContextMock, request, kibanaResponseFactory); + + expect(response.payload.itemsDeleted).toEqual(['a']); + expect(response.payload.errors[0].id).toEqual('b'); + }); +}); diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_delete_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_delete_route.ts new file mode 100644 index 0000000000000..ed2633a4a469e --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_delete_route.ts @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema } from '@kbn/config-schema'; + +import { addBasePath } from '../../../services'; +import { RouteDependencies } from '../../../types'; + +/** + * Delete an auto-follow pattern + */ +export const registerDeleteRoute = ({ + router, + license, + lib: { isEsError, formatEsError }, +}: RouteDependencies) => { + const paramsSchema = schema.object({ + id: schema.string(), + }); + + router.delete( + { + path: addBasePath('/auto_follow_patterns/{id}'), + validate: { + params: paramsSchema, + }, + }, + license.guardApiRoute(async (context, request, response) => { + const { id } = request.params; + const ids = id.split(','); + + const itemsDeleted: string[] = []; + const errors: Array<{ id: string; error: any }> = []; + + const formatError = (err: any) => { + if (isEsError(err)) { + return response.customError(formatEsError(err)); + } + // Case: default + return response.internalError({ body: err }); + }; + + await Promise.all( + ids.map(_id => + context + .crossClusterReplication!.client.callAsCurrentUser('ccr.deleteAutoFollowPattern', { + id: _id, + }) + .then(() => itemsDeleted.push(_id)) + .catch((err: any) => { + errors.push({ id: _id, error: formatError(err) }); + }) + ) + ); + + return response.ok({ + body: { + itemsDeleted, + errors, + }, + }); + }) + ); +}; diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_fetch_route.test.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_fetch_route.test.ts new file mode 100644 index 0000000000000..dd102c45665cb --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_fetch_route.test.ts @@ -0,0 +1,69 @@ +/* + * 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 { httpServiceMock, httpServerMock } from 'src/core/server/mocks'; +import { IRouter, kibanaResponseFactory, RequestHandler } from 'src/core/server'; + +import { isEsError } from '../../../lib/is_es_error'; +import { formatEsError } from '../../../lib/format_es_error'; +import { License } from '../../../services'; +import { mockRouteContext } from '../test_lib'; +import { registerFetchRoute } from './register_fetch_route'; + +const httpService = httpServiceMock.createSetupContract(); + +describe('[CCR API] Fetch all auto-follow patterns', () => { + let routeHandler: RequestHandler; + + beforeEach(() => { + const router = httpService.createRouter('') as jest.Mocked; + + registerFetchRoute({ + router, + license: { + guardApiRoute: (route: any) => route, + } as License, + lib: { + isEsError, + formatEsError, + }, + }); + + routeHandler = router.get.mock.calls[0][1]; + }); + + it('deserializes the response from Elasticsearch', async () => { + const ccrAutoFollowPatternResponseMock = { + patterns: [ + { + name: 'autoFollowPattern', + pattern: { + active: true, + remote_cluster: 'remoteCluster', + leader_index_patterns: ['leader*'], + follow_index_pattern: 'follow', + }, + }, + ], + }; + + const routeContextMock = mockRouteContext({ + callAsCurrentUser: jest.fn().mockResolvedValueOnce(ccrAutoFollowPatternResponseMock), + }); + + const request = httpServerMock.createKibanaRequest(); + const response = await routeHandler(routeContextMock, request, kibanaResponseFactory); + expect(response.payload.patterns).toEqual([ + { + active: true, + followIndexPattern: 'follow', + leaderIndexPatterns: ['leader*'], + name: 'autoFollowPattern', + remoteCluster: 'remoteCluster', + }, + ]); + }); +}); diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_fetch_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_fetch_route.ts new file mode 100644 index 0000000000000..70d8ae4d51e3b --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_fetch_route.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { deserializeListAutoFollowPatterns } from '../../../../common/services/auto_follow_pattern_serialization'; +import { addBasePath } from '../../../services'; +import { RouteDependencies } from '../../../types'; + +/** + * Get a list of all auto-follow patterns + */ +export const registerFetchRoute = ({ + router, + license, + lib: { isEsError, formatEsError }, +}: RouteDependencies) => { + router.get( + { + path: addBasePath('/auto_follow_patterns'), + validate: false, + }, + license.guardApiRoute(async (context, request, response) => { + try { + const result = await context.crossClusterReplication!.client.callAsCurrentUser( + 'ccr.autoFollowPatterns' + ); + return response.ok({ + body: { + patterns: deserializeListAutoFollowPatterns(result.patterns), + }, + }); + } catch (err) { + if (isEsError(err)) { + return response.customError(formatEsError(err)); + } + // Case: default + return response.internalError({ body: err }); + } + }) + ); +}; diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_get_route.test.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_get_route.test.ts new file mode 100644 index 0000000000000..d5889074651f5 --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_get_route.test.ts @@ -0,0 +1,67 @@ +/* + * 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 { httpServiceMock, httpServerMock } from 'src/core/server/mocks'; +import { IRouter, kibanaResponseFactory, RequestHandler } from 'src/core/server'; + +import { isEsError } from '../../../lib/is_es_error'; +import { formatEsError } from '../../../lib/format_es_error'; +import { License } from '../../../services'; +import { mockRouteContext } from '../test_lib'; +import { registerGetRoute } from './register_get_route'; + +const httpService = httpServiceMock.createSetupContract(); + +describe('[CCR API] Get one auto-follow pattern', () => { + let routeHandler: RequestHandler; + + beforeEach(() => { + const router = httpService.createRouter('') as jest.Mocked; + + registerGetRoute({ + router, + license: { + guardApiRoute: (route: any) => route, + } as License, + lib: { + isEsError, + formatEsError, + }, + }); + + routeHandler = router.get.mock.calls[0][1]; + }); + + it('should return a single resource even though ES returns an array with 1 item', async () => { + const ccrAutoFollowPatternResponseMock = { + patterns: [ + { + name: 'autoFollowPattern', + pattern: { + active: true, + remote_cluster: 'remoteCluster', + leader_index_patterns: ['leader*'], + follow_index_pattern: 'follow', + }, + }, + ], + }; + + const routeContextMock = mockRouteContext({ + callAsCurrentUser: jest.fn().mockResolvedValueOnce(ccrAutoFollowPatternResponseMock), + }); + + const request = httpServerMock.createKibanaRequest(); + const response = await routeHandler(routeContextMock, request, kibanaResponseFactory); + expect(response.payload).toEqual({ + active: true, + followIndexPattern: 'follow', + leaderIndexPatterns: ['leader*'], + name: 'autoFollowPattern', + remoteCluster: 'remoteCluster', + }); + }); +}); diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_get_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_get_route.ts new file mode 100644 index 0000000000000..1edbf7e8806c7 --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_get_route.ts @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema } from '@kbn/config-schema'; + +import { deserializeAutoFollowPattern } from '../../../../common/services/auto_follow_pattern_serialization'; +import { addBasePath } from '../../../services'; +import { RouteDependencies } from '../../../types'; + +/** + * Get a single auto-follow pattern + */ +export const registerGetRoute = ({ + router, + license, + lib: { isEsError, formatEsError }, +}: RouteDependencies) => { + const paramsSchema = schema.object({ + id: schema.string(), + }); + + router.get( + { + path: addBasePath('/auto_follow_patterns/{id}'), + validate: { + params: paramsSchema, + }, + }, + license.guardApiRoute(async (context, request, response) => { + const { id } = request.params; + + try { + const result = await context.crossClusterReplication!.client.callAsCurrentUser( + 'ccr.autoFollowPattern', + { id } + ); + const autoFollowPattern = result.patterns[0]; + + return response.ok({ + body: deserializeAutoFollowPattern(autoFollowPattern), + }); + } catch (err) { + if (isEsError(err)) { + return response.customError(formatEsError(err)); + } + // Case: default + return response.internalError({ body: err }); + } + }) + ); +}; diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_pause_route.test.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_pause_route.test.ts new file mode 100644 index 0000000000000..1eaac02918b88 --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_pause_route.test.ts @@ -0,0 +1,86 @@ +/* + * 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 { httpServiceMock, httpServerMock } from 'src/core/server/mocks'; +import { IRouter, kibanaResponseFactory, RequestHandler } from 'src/core/server'; + +import { isEsError } from '../../../lib/is_es_error'; +import { formatEsError } from '../../../lib/format_es_error'; +import { License } from '../../../services'; +import { mockRouteContext } from '../test_lib'; +import { registerPauseRoute } from './register_pause_route'; + +const httpService = httpServiceMock.createSetupContract(); + +describe('[CCR API] Pause auto-follow pattern(s)', () => { + let routeHandler: RequestHandler; + + beforeEach(() => { + const router = httpService.createRouter('') as jest.Mocked; + + registerPauseRoute({ + router, + license: { + guardApiRoute: (route: any) => route, + } as License, + lib: { + isEsError, + formatEsError, + }, + }); + + routeHandler = router.post.mock.calls[0][1]; + }); + + it('pauses a single item', async () => { + const routeContextMock = mockRouteContext({ + callAsCurrentUser: jest.fn().mockResolvedValueOnce({ acknowledge: true }), + }); + + const request = httpServerMock.createKibanaRequest({ + params: { id: 'a' }, + }); + + const response = await routeHandler(routeContextMock, request, kibanaResponseFactory); + expect(response.payload.itemsPaused).toEqual(['a']); + expect(response.payload.errors).toEqual([]); + }); + + it('pauses multiple items', async () => { + const routeContextMock = mockRouteContext({ + callAsCurrentUser: jest + .fn() + .mockResolvedValueOnce({ acknowledge: true }) + .mockResolvedValueOnce({ acknowledge: true }) + .mockResolvedValueOnce({ acknowledge: true }), + }); + + const request = httpServerMock.createKibanaRequest({ + params: { id: 'a,b,c' }, + }); + + const response = await routeHandler(routeContextMock, request, kibanaResponseFactory); + expect(response.payload.itemsPaused).toEqual(['a', 'b', 'c']); + expect(response.payload.errors).toEqual([]); + }); + + it('returns partial errors', async () => { + const routeContextMock = mockRouteContext({ + callAsCurrentUser: jest + .fn() + .mockResolvedValueOnce({ acknowledge: true }) + .mockRejectedValueOnce({ response: { error: {} } }), + }); + + const request = httpServerMock.createKibanaRequest({ + params: { id: 'a,b' }, + }); + + const response = await routeHandler(routeContextMock, request, kibanaResponseFactory); + expect(response.payload.itemsPaused).toEqual(['a']); + expect(response.payload.errors[0].id).toEqual('b'); + }); +}); diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_pause_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_pause_route.ts new file mode 100644 index 0000000000000..325939709e751 --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_pause_route.ts @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema } from '@kbn/config-schema'; +import { addBasePath } from '../../../services'; +import { RouteDependencies } from '../../../types'; + +/** + * Pause auto-follow pattern(s) + */ +export const registerPauseRoute = ({ + router, + license, + lib: { isEsError, formatEsError }, +}: RouteDependencies) => { + const paramsSchema = schema.object({ + id: schema.string(), + }); + + router.post( + { + path: addBasePath('/auto_follow_patterns/{id}/pause'), + validate: { + params: paramsSchema, + }, + }, + license.guardApiRoute(async (context, request, response) => { + const { id } = request.params; + const ids = id.split(','); + + const itemsPaused: string[] = []; + const errors: Array<{ id: string; error: any }> = []; + + const formatError = (err: any) => { + if (isEsError(err)) { + return response.customError(formatEsError(err)); + } + // Case: default + return response.internalError({ body: err }); + }; + + await Promise.all( + ids.map(_id => + context + .crossClusterReplication!.client.callAsCurrentUser('ccr.pauseAutoFollowPattern', { + id: _id, + }) + .then(() => itemsPaused.push(_id)) + .catch(err => { + errors.push({ id: _id, error: formatError(err) }); + }) + ) + ); + + return response.ok({ + body: { + itemsPaused, + errors, + }, + }); + }) + ); +}; diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_resume_route.test.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_resume_route.test.ts new file mode 100644 index 0000000000000..9839761e701fc --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_resume_route.test.ts @@ -0,0 +1,86 @@ +/* + * 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 { httpServiceMock, httpServerMock } from 'src/core/server/mocks'; +import { IRouter, kibanaResponseFactory, RequestHandler } from 'src/core/server'; + +import { isEsError } from '../../../lib/is_es_error'; +import { formatEsError } from '../../../lib/format_es_error'; +import { License } from '../../../services'; +import { mockRouteContext } from '../test_lib'; +import { registerResumeRoute } from './register_resume_route'; + +const httpService = httpServiceMock.createSetupContract(); + +describe('[CCR API] Resume auto-follow pattern(s)', () => { + let routeHandler: RequestHandler; + + beforeEach(() => { + const router = httpService.createRouter('') as jest.Mocked; + + registerResumeRoute({ + router, + license: { + guardApiRoute: (route: any) => route, + } as License, + lib: { + isEsError, + formatEsError, + }, + }); + + routeHandler = router.post.mock.calls[0][1]; + }); + + it('resumes a single item', async () => { + const routeContextMock = mockRouteContext({ + callAsCurrentUser: jest.fn().mockResolvedValueOnce({ acknowledge: true }), + }); + + const request = httpServerMock.createKibanaRequest({ + params: { id: 'a' }, + }); + + const response = await routeHandler(routeContextMock, request, kibanaResponseFactory); + expect(response.payload.itemsResumed).toEqual(['a']); + expect(response.payload.errors).toEqual([]); + }); + + it('resumes multiple items', async () => { + const routeContextMock = mockRouteContext({ + callAsCurrentUser: jest + .fn() + .mockResolvedValueOnce({ acknowledge: true }) + .mockResolvedValueOnce({ acknowledge: true }) + .mockResolvedValueOnce({ acknowledge: true }), + }); + + const request = httpServerMock.createKibanaRequest({ + params: { id: 'a,b,c' }, + }); + + const response = await routeHandler(routeContextMock, request, kibanaResponseFactory); + expect(response.payload.itemsResumed).toEqual(['a', 'b', 'c']); + expect(response.payload.errors).toEqual([]); + }); + + it('returns partial errors', async () => { + const routeContextMock = mockRouteContext({ + callAsCurrentUser: jest + .fn() + .mockResolvedValueOnce({ acknowledge: true }) + .mockRejectedValueOnce({ response: { error: {} } }), + }); + + const request = httpServerMock.createKibanaRequest({ + params: { id: 'a,b' }, + }); + + const response = await routeHandler(routeContextMock, request, kibanaResponseFactory); + expect(response.payload.itemsResumed).toEqual(['a']); + expect(response.payload.errors[0].id).toEqual('b'); + }); +}); diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_resume_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_resume_route.ts new file mode 100644 index 0000000000000..f5e917773704c --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_resume_route.ts @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema } from '@kbn/config-schema'; +import { addBasePath } from '../../../services'; +import { RouteDependencies } from '../../../types'; + +/** + * Resume auto-follow pattern(s) + */ +export const registerResumeRoute = ({ + router, + license, + lib: { isEsError, formatEsError }, +}: RouteDependencies) => { + const paramsSchema = schema.object({ + id: schema.string(), + }); + + router.post( + { + path: addBasePath('/auto_follow_patterns/{id}/resume'), + validate: { + params: paramsSchema, + }, + }, + license.guardApiRoute(async (context, request, response) => { + const { id } = request.params; + const ids = id.split(','); + + const itemsResumed: string[] = []; + const errors: Array<{ id: string; error: any }> = []; + + const formatError = (err: any) => { + if (isEsError(err)) { + return response.customError(formatEsError(err)); + } + // Case: default + return response.internalError({ body: err }); + }; + + await Promise.all( + ids.map((_id: string) => + context + .crossClusterReplication!.client.callAsCurrentUser('ccr.resumeAutoFollowPattern', { + id: _id, + }) + .then(() => itemsResumed.push(_id)) + .catch(err => { + errors.push({ id: _id, error: formatError(err) }); + }) + ) + ); + + return response.ok({ + body: { + itemsResumed, + errors, + }, + }); + }) + ); +}; diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_update_route.test.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_update_route.test.ts new file mode 100644 index 0000000000000..85f2270ec3aee --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_update_route.test.ts @@ -0,0 +1,64 @@ +/* + * 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 { httpServiceMock, httpServerMock } from 'src/core/server/mocks'; +import { IRouter, kibanaResponseFactory, RequestHandler } from 'src/core/server'; + +import { isEsError } from '../../../lib/is_es_error'; +import { formatEsError } from '../../../lib/format_es_error'; +import { License } from '../../../services'; +import { mockRouteContext } from '../test_lib'; +import { registerUpdateRoute } from './register_update_route'; + +const httpService = httpServiceMock.createSetupContract(); + +describe('[CCR API] Update auto-follow pattern', () => { + let routeHandler: RequestHandler; + + beforeEach(() => { + const router = httpService.createRouter('') as jest.Mocked; + + registerUpdateRoute({ + router, + license: { + guardApiRoute: (route: any) => route, + } as License, + lib: { + isEsError, + formatEsError, + }, + }); + + routeHandler = router.put.mock.calls[0][1]; + }); + + it('should serialize the payload before sending it to Elasticsearch', async () => { + const routeContextMock = mockRouteContext({ + // Just echo back what we send so we can inspect it. + callAsCurrentUser: jest.fn().mockImplementation((endpoint, payload) => payload), + }); + + const request = httpServerMock.createKibanaRequest({ + params: { id: 'foo' }, + body: { + remoteCluster: 'bar1', + leaderIndexPatterns: ['bar2'], + followIndexPattern: 'bar3', + }, + }); + + const response = await routeHandler(routeContextMock, request, kibanaResponseFactory); + + expect(response.payload).toEqual({ + id: 'foo', + body: { + remote_cluster: 'bar1', + leader_index_patterns: ['bar2'], + follow_index_pattern: 'bar3', + }, + }); + }); +}); diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_update_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_update_route.ts new file mode 100644 index 0000000000000..836e5f55c5a48 --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_update_route.ts @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema } from '@kbn/config-schema'; +import { serializeAutoFollowPattern } from '../../../../common/services/auto_follow_pattern_serialization'; +import { AutoFollowPattern } from '../../../../common/types'; +import { addBasePath } from '../../../services'; +import { RouteDependencies } from '../../../types'; + +/** + * Update an auto-follow pattern + */ +export const registerUpdateRoute = ({ + router, + license, + lib: { isEsError, formatEsError }, +}: RouteDependencies) => { + const paramsSchema = schema.object({ + id: schema.string(), + }); + + const bodySchema = schema.object({ + active: schema.boolean(), + remoteCluster: schema.string(), + leaderIndexPatterns: schema.arrayOf(schema.string()), + followIndexPattern: schema.string(), + }); + + router.put( + { + path: addBasePath('/auto_follow_patterns/{id}'), + validate: { + params: paramsSchema, + body: bodySchema, + }, + }, + license.guardApiRoute(async (context, request, response) => { + const { id } = request.params; + const body = serializeAutoFollowPattern(request.body as AutoFollowPattern); + + try { + return response.ok({ + body: await context.crossClusterReplication!.client.callAsCurrentUser( + 'ccr.saveAutoFollowPattern', + { id, body } + ), + }); + } catch (err) { + if (isEsError(err)) { + return response.customError(formatEsError(err)); + } + // Case: default + return response.internalError({ body: err }); + } + }) + ); +}; diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/cross_cluster_replication/index.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/cross_cluster_replication/index.ts new file mode 100644 index 0000000000000..45c5729535e58 --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/cross_cluster_replication/index.ts @@ -0,0 +1,14 @@ +/* + * 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 { RouteDependencies } from '../../../types'; +import { registerPermissionsRoute } from './register_permissions_route'; +import { registerStatsRoute } from './register_stats_route'; + +export function registerCrossClusterReplicationRoutes(dependencies: RouteDependencies) { + registerPermissionsRoute(dependencies); + registerStatsRoute(dependencies); +} diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/cross_cluster_replication/register_permissions_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/cross_cluster_replication/register_permissions_route.ts new file mode 100644 index 0000000000000..b8eb5ae14750e --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/cross_cluster_replication/register_permissions_route.ts @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { addBasePath } from '../../../services'; +import { RouteDependencies } from '../../../types'; + +/** + * Returns whether the user has CCR permissions + */ +export const registerPermissionsRoute = ({ + router, + license, + lib: { isEsError, formatEsError }, +}: RouteDependencies) => { + router.get( + { + path: addBasePath('/permissions'), + validate: false, + }, + license.guardApiRoute(async (context, request, response) => { + if (!license.isEsSecurityEnabled) { + // If security has been disabled in elasticsearch.yml. we'll just let the user use CCR + // because permissions are irrelevant. + return response.ok({ + body: { + hasPermission: true, + missingClusterPrivileges: [], + }, + }); + } + + try { + const { + has_all_requested: hasPermission, + cluster, + } = await context.crossClusterReplication!.client.callAsCurrentUser('ccr.permissions', { + body: { + cluster: ['manage', 'manage_ccr'], + }, + }); + + const missingClusterPrivileges = Object.keys(cluster).reduce( + (permissions: any, permissionName: any) => { + if (!cluster[permissionName]) { + permissions.push(permissionName); + return permissions; + } + }, + [] as any[] + ); + + return response.ok({ + body: { + hasPermission, + missingClusterPrivileges, + }, + }); + } catch (err) { + if (isEsError(err)) { + return response.customError(formatEsError(err)); + } + // Case: default + return response.internalError({ body: err }); + } + }) + ); +}; diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/cross_cluster_replication/register_stats_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/cross_cluster_replication/register_stats_route.ts new file mode 100644 index 0000000000000..d4288cf7303e2 --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/cross_cluster_replication/register_stats_route.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { addBasePath } from '../../../services'; +import { deserializeAutoFollowStats } from '../../../lib/ccr_stats_serialization'; +import { RouteDependencies } from '../../../types'; + +/** + * Returns Auto-follow stats + */ +export const registerStatsRoute = ({ + router, + license, + lib: { isEsError, formatEsError }, +}: RouteDependencies) => { + router.get( + { + path: addBasePath('/stats/auto_follow'), + validate: false, + }, + license.guardApiRoute(async (context, request, response) => { + try { + const { + auto_follow_stats: autoFollowStats, + } = await context.crossClusterReplication!.client.callAsCurrentUser('ccr.stats'); + + return response.ok({ + body: deserializeAutoFollowStats(autoFollowStats), + }); + } catch (err) { + if (isEsError(err)) { + return response.customError(formatEsError(err)); + } + // Case: default + return response.internalError({ body: err }); + } + }) + ); +}; diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/index.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/index.ts new file mode 100644 index 0000000000000..f5d8c7a4f5bda --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/index.ts @@ -0,0 +1,24 @@ +/* + * 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 { RouteDependencies } from '../../../types'; +import { registerCreateRoute } from './register_create_route'; +import { registerFetchRoute } from './register_fetch_route'; +import { registerGetRoute } from './register_get_route'; +import { registerPauseRoute } from './register_pause_route'; +import { registerResumeRoute } from './register_resume_route'; +import { registerUnfollowRoute } from './register_unfollow_route'; +import { registerUpdateRoute } from './register_update_route'; + +export function registerFollowerIndexRoutes(dependencies: RouteDependencies) { + registerCreateRoute(dependencies); + registerFetchRoute(dependencies); + registerGetRoute(dependencies); + registerPauseRoute(dependencies); + registerResumeRoute(dependencies); + registerUnfollowRoute(dependencies); + registerUpdateRoute(dependencies); +} diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_create_route.test.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_create_route.test.ts new file mode 100644 index 0000000000000..bba82b04ce9a0 --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_create_route.test.ts @@ -0,0 +1,54 @@ +/* + * 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 { httpServiceMock, httpServerMock } from 'src/core/server/mocks'; +import { IRouter, kibanaResponseFactory, RequestHandler } from 'src/core/server'; + +import { isEsError } from '../../../lib/is_es_error'; +import { formatEsError } from '../../../lib/format_es_error'; +import { License } from '../../../services'; +import { mockRouteContext } from '../test_lib'; +import { registerCreateRoute } from './register_create_route'; + +const httpService = httpServiceMock.createSetupContract(); + +describe('[CCR API] Create follower index', () => { + let routeHandler: RequestHandler; + + beforeEach(() => { + const router = httpService.createRouter('') as jest.Mocked; + + registerCreateRoute({ + router, + license: { + guardApiRoute: (route: any) => route, + } as License, + lib: { + isEsError, + formatEsError, + }, + }); + + routeHandler = router.post.mock.calls[0][1]; + }); + + it('should return 200 status when follower index is created', async () => { + const routeContextMock = mockRouteContext({ + callAsCurrentUser: jest.fn().mockResolvedValueOnce({ acknowledge: true }), + }); + + const request = httpServerMock.createKibanaRequest({ + body: { + name: 'follower_index', + remoteCluster: 'remote_cluster', + leaderIndex: 'leader_index', + }, + }); + + const response = await routeHandler(routeContextMock, request, kibanaResponseFactory); + expect(response.status).toEqual(200); + }); +}); diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_create_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_create_route.ts new file mode 100644 index 0000000000000..acaeedacfdb2a --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_create_route.ts @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema } from '@kbn/config-schema'; +import { serializeFollowerIndex } from '../../../../common/services/follower_index_serialization'; +import { FollowerIndex } from '../../../../common/types'; +import { addBasePath } from '../../../services'; +import { removeEmptyFields } from '../../../../common/services/utils'; +import { RouteDependencies } from '../../../types'; + +/** + * Create a follower index + */ +export const registerCreateRoute = ({ + router, + license, + lib: { isEsError, formatEsError }, +}: RouteDependencies) => { + const bodySchema = schema.object({ + name: schema.string(), + remoteCluster: schema.string(), + leaderIndex: schema.string(), + maxReadRequestOperationCount: schema.maybe(schema.number()), + maxOutstandingReadRequests: schema.maybe(schema.number()), + maxReadRequestSize: schema.maybe(schema.string()), // byte value + maxWriteRequestOperationCount: schema.maybe(schema.number()), + maxWriteRequestSize: schema.maybe(schema.string()), // byte value + maxOutstandingWriteRequests: schema.maybe(schema.number()), + maxWriteBufferCount: schema.maybe(schema.number()), + maxWriteBufferSize: schema.maybe(schema.string()), // byte value + maxRetryDelay: schema.maybe(schema.string()), // time value + readPollTimeout: schema.maybe(schema.string()), // time value + }); + + router.post( + { + path: addBasePath('/follower_indices'), + validate: { + body: bodySchema, + }, + }, + license.guardApiRoute(async (context, request, response) => { + const { name, ...rest } = request.body; + const body = removeEmptyFields(serializeFollowerIndex(rest as FollowerIndex)); + + try { + return response.ok({ + body: await context.crossClusterReplication!.client.callAsCurrentUser( + 'ccr.saveFollowerIndex', + { name, body } + ), + }); + } catch (err) { + if (isEsError(err)) { + return response.customError(formatEsError(err)); + } + // Case: default + return response.internalError({ body: err }); + } + }) + ); +}; diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_fetch_route.test.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_fetch_route.test.ts new file mode 100644 index 0000000000000..151ab84fabf4c --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_fetch_route.test.ts @@ -0,0 +1,160 @@ +/* + * 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 { httpServiceMock, httpServerMock } from 'src/core/server/mocks'; +import { IRouter, kibanaResponseFactory, RequestHandler } from 'src/core/server'; + +import { isEsError } from '../../../lib/is_es_error'; +import { formatEsError } from '../../../lib/format_es_error'; +import { License } from '../../../services'; +import { mockRouteContext } from '../test_lib'; +import { registerFetchRoute } from './register_fetch_route'; + +const httpService = httpServiceMock.createSetupContract(); + +describe('[CCR API] Fetch all follower indices', () => { + let routeHandler: RequestHandler; + + beforeEach(() => { + const router = httpService.createRouter('') as jest.Mocked; + + registerFetchRoute({ + router, + license: { + guardApiRoute: (route: any) => route, + } as License, + lib: { + isEsError, + formatEsError, + }, + }); + + routeHandler = router.get.mock.calls[0][1]; + }); + + it('deserializes the response from Elasticsearch', async () => { + const ccrInfoMockResponse = { + follower_indices: [ + { + follower_index: 'followerIndexName', + remote_cluster: 'remoteCluster', + leader_index: 'leaderIndex', + status: 'active', + parameters: { + max_read_request_operation_count: 1, + max_outstanding_read_requests: 1, + max_read_request_size: '1b', + max_write_request_operation_count: 1, + max_write_request_size: '1b', + max_outstanding_write_requests: 1, + max_write_buffer_count: 1, + max_write_buffer_size: '1b', + max_retry_delay: '1s', + read_poll_timeout: '1s', + }, + }, + ], + }; + + // These stats correlate to the above follower indices. + const ccrStatsMockResponse = { + follow_stats: { + indices: [ + { + index: 'followerIndexName', + shards: [ + { + shard_id: 1, + leader_index: 'leaderIndex', + leader_global_checkpoint: 1, + leader_max_seq_no: 1, + follower_global_checkpoint: 1, + follower_max_seq_no: 1, + last_requested_seq_no: 1, + outstanding_read_requests: 1, + outstanding_write_requests: 1, + write_buffer_operation_count: 1, + write_buffer_size_in_bytes: 1, + follower_mapping_version: 1, + follower_settings_version: 1, + total_read_time_millis: 1, + total_read_remote_exec_time_millis: 1, + successful_read_requests: 1, + failed_read_requests: 1, + operations_read: 1, + bytes_read: 1, + total_write_time_millis: 1, + successful_write_requests: 1, + failed_write_requests: 1, + operations_written: 1, + read_exceptions: 1, + time_since_last_read_millis: 1, + }, + ], + }, + ], + }, + }; + + const routeContextMock = mockRouteContext({ + callAsCurrentUser: jest + .fn() + .mockResolvedValueOnce(ccrInfoMockResponse) + .mockResolvedValueOnce(ccrStatsMockResponse), + }); + + const request = httpServerMock.createKibanaRequest(); + const response = await routeHandler(routeContextMock, request, kibanaResponseFactory); + + expect(response.payload.indices).toEqual([ + { + name: 'followerIndexName', + remoteCluster: 'remoteCluster', + leaderIndex: 'leaderIndex', + status: 'active', + maxReadRequestOperationCount: 1, + maxOutstandingReadRequests: 1, + maxReadRequestSize: '1b', + maxWriteRequestOperationCount: 1, + maxWriteRequestSize: '1b', + maxOutstandingWriteRequests: 1, + maxWriteBufferCount: 1, + maxWriteBufferSize: '1b', + maxRetryDelay: '1s', + readPollTimeout: '1s', + shards: [ + { + id: 1, + leaderIndex: 'leaderIndex', + leaderGlobalCheckpoint: 1, + leaderMaxSequenceNum: 1, + followerGlobalCheckpoint: 1, + followerMaxSequenceNum: 1, + lastRequestedSequenceNum: 1, + outstandingReadRequestsCount: 1, + outstandingWriteRequestsCount: 1, + writeBufferOperationsCount: 1, + writeBufferSizeBytes: 1, + followerMappingVersion: 1, + followerSettingsVersion: 1, + totalReadTimeMs: 1, + totalReadRemoteExecTimeMs: 1, + successfulReadRequestCount: 1, + failedReadRequestsCount: 1, + operationsReadCount: 1, + bytesReadCount: 1, + totalWriteTimeMs: 1, + successfulWriteRequestsCount: 1, + failedWriteRequestsCount: 1, + operationsWrittenCount: 1, + readExceptions: 1, + timeSinceLastReadMs: 1, + }, + ], + }, + ]); + }); +}); diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_fetch_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_fetch_route.ts new file mode 100644 index 0000000000000..a78901ce174e4 --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_fetch_route.ts @@ -0,0 +1,62 @@ +/* + * 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 { deserializeListFollowerIndices } from '../../../../common/services/follower_index_serialization'; +import { addBasePath } from '../../../services'; +import { RouteDependencies } from '../../../types'; + +/** + * Returns a list of all follower indices + */ +export const registerFetchRoute = ({ + router, + license, + lib: { isEsError, formatEsError }, +}: RouteDependencies) => { + router.get( + { + path: addBasePath('/follower_indices'), + validate: false, + }, + license.guardApiRoute(async (context, request, response) => { + try { + const { + follower_indices: followerIndices, + } = await context.crossClusterReplication!.client.callAsCurrentUser('ccr.info', { + id: '_all', + }); + + const { + follow_stats: { indices: followerIndicesStats }, + } = await context.crossClusterReplication!.client.callAsCurrentUser('ccr.stats'); + + const followerIndicesStatsMap = followerIndicesStats.reduce((map: any, stats: any) => { + map[stats.index] = stats; + return map; + }, {}); + + const collatedFollowerIndices = followerIndices.map((followerIndex: any) => { + return { + ...followerIndex, + ...followerIndicesStatsMap[followerIndex.follower_index], + }; + }); + + return response.ok({ + body: { + indices: deserializeListFollowerIndices(collatedFollowerIndices), + }, + }); + } catch (err) { + if (isEsError(err)) { + return response.customError(formatEsError(err)); + } + // Case: default + return response.internalError({ body: err }); + } + }) + ); +}; diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_get_route.test.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_get_route.test.ts new file mode 100644 index 0000000000000..42d04ca65b1cb --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_get_route.test.ts @@ -0,0 +1,159 @@ +/* + * 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 { httpServiceMock, httpServerMock } from 'src/core/server/mocks'; +import { IRouter, kibanaResponseFactory, RequestHandler } from 'src/core/server'; + +import { isEsError } from '../../../lib/is_es_error'; +import { formatEsError } from '../../../lib/format_es_error'; +import { License } from '../../../services'; +import { mockRouteContext } from '../test_lib'; +import { registerGetRoute } from './register_get_route'; + +const httpService = httpServiceMock.createSetupContract(); + +describe('[CCR API] Get one follower index', () => { + let routeHandler: RequestHandler; + + beforeEach(() => { + const router = httpService.createRouter('') as jest.Mocked; + + registerGetRoute({ + router, + license: { + guardApiRoute: (route: any) => route, + } as License, + lib: { + isEsError, + formatEsError, + }, + }); + + routeHandler = router.get.mock.calls[0][1]; + }); + + it('should return a single resource even though ES returns an array with 1 item', async () => { + const ccrInfoMockResponse = { + follower_indices: [ + { + follower_index: 'followerIndexName', + remote_cluster: 'remoteCluster', + leader_index: 'leaderIndex', + status: 'active', + parameters: { + max_read_request_operation_count: 1, + max_outstanding_read_requests: 1, + max_read_request_size: '1b', + max_write_request_operation_count: 1, + max_write_request_size: '1b', + max_outstanding_write_requests: 1, + max_write_buffer_count: 1, + max_write_buffer_size: '1b', + max_retry_delay: '1s', + read_poll_timeout: '1s', + }, + }, + ], + }; + + // These stats correlate to the above follower indices. + const ccrFollowerIndexStatsMockResponse = { + indices: [ + { + index: 'followerIndexName', + shards: [ + { + shard_id: 1, + leader_index: 'leaderIndex', + leader_global_checkpoint: 1, + leader_max_seq_no: 1, + follower_global_checkpoint: 1, + follower_max_seq_no: 1, + last_requested_seq_no: 1, + outstanding_read_requests: 1, + outstanding_write_requests: 1, + write_buffer_operation_count: 1, + write_buffer_size_in_bytes: 1, + follower_mapping_version: 1, + follower_settings_version: 1, + total_read_time_millis: 1, + total_read_remote_exec_time_millis: 1, + successful_read_requests: 1, + failed_read_requests: 1, + operations_read: 1, + bytes_read: 1, + total_write_time_millis: 1, + successful_write_requests: 1, + failed_write_requests: 1, + operations_written: 1, + read_exceptions: 1, + time_since_last_read_millis: 1, + }, + ], + }, + ], + }; + + const routeContextMock = mockRouteContext({ + callAsCurrentUser: jest + .fn() + .mockResolvedValueOnce(ccrInfoMockResponse) + .mockResolvedValueOnce(ccrFollowerIndexStatsMockResponse), + }); + + const request = httpServerMock.createKibanaRequest({ + params: { id: 'doesnt_matter' }, + }); + + const response = await routeHandler(routeContextMock, request, kibanaResponseFactory); + + expect(response.payload).toEqual({ + name: 'followerIndexName', + remoteCluster: 'remoteCluster', + leaderIndex: 'leaderIndex', + status: 'active', + maxReadRequestOperationCount: 1, + maxOutstandingReadRequests: 1, + maxReadRequestSize: '1b', + maxWriteRequestOperationCount: 1, + maxWriteRequestSize: '1b', + maxOutstandingWriteRequests: 1, + maxWriteBufferCount: 1, + maxWriteBufferSize: '1b', + maxRetryDelay: '1s', + readPollTimeout: '1s', + shards: [ + { + id: 1, + leaderIndex: 'leaderIndex', + leaderGlobalCheckpoint: 1, + leaderMaxSequenceNum: 1, + followerGlobalCheckpoint: 1, + followerMaxSequenceNum: 1, + lastRequestedSequenceNum: 1, + outstandingReadRequestsCount: 1, + outstandingWriteRequestsCount: 1, + writeBufferOperationsCount: 1, + writeBufferSizeBytes: 1, + followerMappingVersion: 1, + followerSettingsVersion: 1, + totalReadTimeMs: 1, + totalReadRemoteExecTimeMs: 1, + successfulReadRequestCount: 1, + failedReadRequestsCount: 1, + operationsReadCount: 1, + bytesReadCount: 1, + totalWriteTimeMs: 1, + successfulWriteRequestsCount: 1, + failedWriteRequestsCount: 1, + operationsWrittenCount: 1, + readExceptions: 1, + timeSinceLastReadMs: 1, + }, + ], + }); + }); +}); diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_get_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_get_route.ts new file mode 100644 index 0000000000000..98a182fc15681 --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_get_route.ts @@ -0,0 +1,78 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema } from '@kbn/config-schema'; +import { deserializeFollowerIndex } from '../../../../common/services/follower_index_serialization'; +import { addBasePath } from '../../../services'; +import { RouteDependencies } from '../../../types'; + +/** + * Returns a single follower index pattern + */ +export const registerGetRoute = ({ + router, + license, + lib: { isEsError, formatEsError }, +}: RouteDependencies) => { + const paramsSchema = schema.object({ + id: schema.string(), + }); + + router.get( + { + path: addBasePath('/follower_indices/{id}'), + validate: { + params: paramsSchema, + }, + }, + license.guardApiRoute(async (context, request, response) => { + const { id } = request.params; + + try { + const { + follower_indices: followerIndices, + } = await context.crossClusterReplication!.client.callAsCurrentUser('ccr.info', { id }); + + const followerIndexInfo = followerIndices && followerIndices[0]; + + if (!followerIndexInfo) { + return response.notFound({ + body: `The follower index "${id}" does not exist.`, + }); + } + + // If this follower is paused, skip call to ES stats api since it will return 404 + if (followerIndexInfo.status === 'paused') { + return response.ok({ + body: deserializeFollowerIndex({ + ...followerIndexInfo, + }), + }); + } else { + const { + indices: followerIndicesStats, + } = await context.crossClusterReplication!.client.callAsCurrentUser( + 'ccr.followerIndexStats', + { id } + ); + + return response.ok({ + body: deserializeFollowerIndex({ + ...followerIndexInfo, + ...(followerIndicesStats ? followerIndicesStats[0] : {}), + }), + }); + } + } catch (err) { + if (isEsError(err)) { + return response.customError(formatEsError(err)); + } + // Case: default + return response.internalError({ body: err }); + } + }) + ); +}; diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_pause_route.test.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_pause_route.test.ts new file mode 100644 index 0000000000000..82cb88cbacea7 --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_pause_route.test.ts @@ -0,0 +1,86 @@ +/* + * 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 { httpServiceMock, httpServerMock } from 'src/core/server/mocks'; +import { IRouter, kibanaResponseFactory, RequestHandler } from 'src/core/server'; + +import { isEsError } from '../../../lib/is_es_error'; +import { formatEsError } from '../../../lib/format_es_error'; +import { License } from '../../../services'; +import { mockRouteContext } from '../test_lib'; +import { registerPauseRoute } from './register_pause_route'; + +const httpService = httpServiceMock.createSetupContract(); + +describe('[CCR API] Pause follower index/indices', () => { + let routeHandler: RequestHandler; + + beforeEach(() => { + const router = httpService.createRouter('') as jest.Mocked; + + registerPauseRoute({ + router, + license: { + guardApiRoute: (route: any) => route, + } as License, + lib: { + isEsError, + formatEsError, + }, + }); + + routeHandler = router.put.mock.calls[0][1]; + }); + + it('pauses a single item', async () => { + const routeContextMock = mockRouteContext({ + callAsCurrentUser: jest.fn().mockResolvedValueOnce({ acknowledge: true }), + }); + + const request = httpServerMock.createKibanaRequest({ + params: { id: 'a' }, + }); + + const response = await routeHandler(routeContextMock, request, kibanaResponseFactory); + expect(response.payload.itemsPaused).toEqual(['a']); + expect(response.payload.errors).toEqual([]); + }); + + it('pauses multiple items', async () => { + const routeContextMock = mockRouteContext({ + callAsCurrentUser: jest + .fn() + .mockResolvedValueOnce({ acknowledge: true }) + .mockResolvedValueOnce({ acknowledge: true }) + .mockResolvedValueOnce({ acknowledge: true }), + }); + + const request = httpServerMock.createKibanaRequest({ + params: { id: 'a,b,c' }, + }); + + const response = await routeHandler(routeContextMock, request, kibanaResponseFactory); + expect(response.payload.itemsPaused).toEqual(['a', 'b', 'c']); + expect(response.payload.errors).toEqual([]); + }); + + it('returns partial errors', async () => { + const routeContextMock = mockRouteContext({ + callAsCurrentUser: jest + .fn() + .mockResolvedValueOnce({ acknowledge: true }) + .mockRejectedValueOnce({ response: { error: {} } }), + }); + + const request = httpServerMock.createKibanaRequest({ + params: { id: 'a,b' }, + }); + + const response = await routeHandler(routeContextMock, request, kibanaResponseFactory); + expect(response.payload.itemsPaused).toEqual(['a']); + expect(response.payload.errors[0].id).toEqual('b'); + }); +}); diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_pause_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_pause_route.ts new file mode 100644 index 0000000000000..7432ea7ca5c82 --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_pause_route.ts @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema } from '@kbn/config-schema'; +import { addBasePath } from '../../../services'; +import { RouteDependencies } from '../../../types'; + +/** + * Pauses a follower index + */ +export const registerPauseRoute = ({ + router, + license, + lib: { isEsError, formatEsError }, +}: RouteDependencies) => { + const paramsSchema = schema.object({ id: schema.string() }); + + router.put( + { + path: addBasePath('/follower_indices/{id}/pause'), + validate: { + params: paramsSchema, + }, + }, + license.guardApiRoute(async (context, request, response) => { + const { id } = request.params; + const ids = id.split(','); + + const itemsPaused: string[] = []; + const errors: Array<{ id: string; error: any }> = []; + + const formatError = (err: any) => { + if (isEsError(err)) { + return response.customError(formatEsError(err)); + } + // Case: default + return response.internalError({ body: err }); + }; + + await Promise.all( + ids.map((_id: string) => + context + .crossClusterReplication!.client.callAsCurrentUser('ccr.pauseFollowerIndex', { + id: _id, + }) + .then(() => itemsPaused.push(_id)) + .catch(err => { + errors.push({ id: _id, error: formatError(err) }); + }) + ) + ); + + return response.ok({ + body: { + itemsPaused, + errors, + }, + }); + }) + ); +}; diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_resume_route.test.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_resume_route.test.ts new file mode 100644 index 0000000000000..04167c5db3162 --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_resume_route.test.ts @@ -0,0 +1,86 @@ +/* + * 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 { httpServiceMock, httpServerMock } from 'src/core/server/mocks'; +import { IRouter, kibanaResponseFactory, RequestHandler } from 'src/core/server'; + +import { isEsError } from '../../../lib/is_es_error'; +import { formatEsError } from '../../../lib/format_es_error'; +import { License } from '../../../services'; +import { mockRouteContext } from '../test_lib'; +import { registerResumeRoute } from './register_resume_route'; + +const httpService = httpServiceMock.createSetupContract(); + +describe('[CCR API] Resume follower index/indices', () => { + let routeHandler: RequestHandler; + + beforeEach(() => { + const router = httpService.createRouter('') as jest.Mocked; + + registerResumeRoute({ + router, + license: { + guardApiRoute: (route: any) => route, + } as License, + lib: { + isEsError, + formatEsError, + }, + }); + + routeHandler = router.put.mock.calls[0][1]; + }); + + it('resumes a single item', async () => { + const routeContextMock = mockRouteContext({ + callAsCurrentUser: jest.fn().mockResolvedValueOnce({ acknowledge: true }), + }); + + const request = httpServerMock.createKibanaRequest({ + params: { id: 'a' }, + }); + + const response = await routeHandler(routeContextMock, request, kibanaResponseFactory); + expect(response.payload.itemsResumed).toEqual(['a']); + expect(response.payload.errors).toEqual([]); + }); + + it('resumes multiple items', async () => { + const routeContextMock = mockRouteContext({ + callAsCurrentUser: jest + .fn() + .mockResolvedValueOnce({ acknowledge: true }) + .mockResolvedValueOnce({ acknowledge: true }) + .mockResolvedValueOnce({ acknowledge: true }), + }); + + const request = httpServerMock.createKibanaRequest({ + params: { id: 'a,b,c' }, + }); + + const response = await routeHandler(routeContextMock, request, kibanaResponseFactory); + expect(response.payload.itemsResumed).toEqual(['a', 'b', 'c']); + expect(response.payload.errors).toEqual([]); + }); + + it('returns partial errors', async () => { + const routeContextMock = mockRouteContext({ + callAsCurrentUser: jest + .fn() + .mockResolvedValueOnce({ acknowledge: true }) + .mockRejectedValueOnce({ response: { error: {} } }), + }); + + const request = httpServerMock.createKibanaRequest({ + params: { id: 'a,b' }, + }); + + const response = await routeHandler(routeContextMock, request, kibanaResponseFactory); + expect(response.payload.itemsResumed).toEqual(['a']); + expect(response.payload.errors[0].id).toEqual('b'); + }); +}); diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_resume_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_resume_route.ts new file mode 100644 index 0000000000000..ca8f3a9f5fe9d --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_resume_route.ts @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema } from '@kbn/config-schema'; +import { addBasePath } from '../../../services'; +import { RouteDependencies } from '../../../types'; + +/** + * Resumes a follower index + */ +export const registerResumeRoute = ({ + router, + license, + lib: { isEsError, formatEsError }, +}: RouteDependencies) => { + const paramsSchema = schema.object({ id: schema.string() }); + + router.put( + { + path: addBasePath('/follower_indices/{id}/resume'), + validate: { + params: paramsSchema, + }, + }, + license.guardApiRoute(async (context, request, response) => { + const { id } = request.params; + const ids = id.split(','); + + const itemsResumed: string[] = []; + const errors: Array<{ id: string; error: any }> = []; + + const formatError = (err: any) => { + if (isEsError(err)) { + return response.customError(formatEsError(err)); + } + // Case: default + return response.internalError({ body: err }); + }; + + await Promise.all( + ids.map((_id: string) => + context + .crossClusterReplication!.client.callAsCurrentUser('ccr.resumeFollowerIndex', { + id: _id, + }) + .then(() => itemsResumed.push(_id)) + .catch(err => { + errors.push({ id: _id, error: formatError(err) }); + }) + ) + ); + + return response.ok({ + body: { + itemsResumed, + errors, + }, + }); + }) + ); +}; diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_unfollow_route.test.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_unfollow_route.test.ts new file mode 100644 index 0000000000000..6302d5868b0db --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_unfollow_route.test.ts @@ -0,0 +1,109 @@ +/* + * 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 { httpServiceMock, httpServerMock } from 'src/core/server/mocks'; +import { IRouter, kibanaResponseFactory, RequestHandler } from 'src/core/server'; + +import { isEsError } from '../../../lib/is_es_error'; +import { formatEsError } from '../../../lib/format_es_error'; +import { License } from '../../../services'; +import { mockRouteContext } from '../test_lib'; +import { registerUnfollowRoute } from './register_unfollow_route'; + +const httpService = httpServiceMock.createSetupContract(); + +describe('[CCR API] Unfollow follower index/indices', () => { + let routeHandler: RequestHandler; + + beforeEach(() => { + const router = httpService.createRouter('') as jest.Mocked; + + registerUnfollowRoute({ + router, + license: { + guardApiRoute: (route: any) => route, + } as License, + lib: { + isEsError, + formatEsError, + }, + }); + + routeHandler = router.put.mock.calls[0][1]; + }); + + it('unfollows a single item', async () => { + const routeContextMock = mockRouteContext({ + callAsCurrentUser: jest + .fn() + .mockResolvedValueOnce({ acknowledge: true }) + .mockResolvedValueOnce({ acknowledge: true }) + .mockResolvedValueOnce({ acknowledge: true }) + .mockResolvedValueOnce({ acknowledge: true }), + }); + + const request = httpServerMock.createKibanaRequest({ + params: { id: 'a' }, + }); + + const response = await routeHandler(routeContextMock, request, kibanaResponseFactory); + expect(response.payload.itemsUnfollowed).toEqual(['a']); + expect(response.payload.errors).toEqual([]); + }); + + it('unfollows multiple items', async () => { + const routeContextMock = mockRouteContext({ + callAsCurrentUser: jest + .fn() + // a + .mockResolvedValueOnce({ acknowledge: true }) + .mockResolvedValueOnce({ acknowledge: true }) + .mockResolvedValueOnce({ acknowledge: true }) + .mockResolvedValueOnce({ acknowledge: true }) + // b + .mockResolvedValueOnce({ acknowledge: true }) + .mockResolvedValueOnce({ acknowledge: true }) + .mockResolvedValueOnce({ acknowledge: true }) + .mockResolvedValueOnce({ acknowledge: true }) + // c + .mockResolvedValueOnce({ acknowledge: true }) + .mockResolvedValueOnce({ acknowledge: true }) + .mockResolvedValueOnce({ acknowledge: true }) + .mockResolvedValueOnce({ acknowledge: true }), + }); + + const request = httpServerMock.createKibanaRequest({ + params: { id: 'a,b,c' }, + }); + + const response = await routeHandler(routeContextMock, request, kibanaResponseFactory); + expect(response.payload.itemsUnfollowed).toEqual(['a', 'b', 'c']); + expect(response.payload.errors).toEqual([]); + }); + + it('returns partial errors', async () => { + const routeContextMock = mockRouteContext({ + callAsCurrentUser: jest + .fn() + // a + .mockResolvedValueOnce({ acknowledge: true }) + .mockResolvedValueOnce({ acknowledge: true }) + .mockResolvedValueOnce({ acknowledge: true }) + .mockResolvedValueOnce({ acknowledge: true }) + // b + .mockResolvedValueOnce({ acknowledge: true }) + .mockRejectedValueOnce({ response: { error: {} } }), + }); + + const request = httpServerMock.createKibanaRequest({ + params: { id: 'a,b' }, + }); + + const response = await routeHandler(routeContextMock, request, kibanaResponseFactory); + expect(response.payload.itemsUnfollowed).toEqual(['a']); + expect(response.payload.errors[0].id).toEqual('b'); + }); +}); diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_unfollow_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_unfollow_route.ts new file mode 100644 index 0000000000000..282fead02bbe0 --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_unfollow_route.ts @@ -0,0 +1,95 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema } from '@kbn/config-schema'; +import { addBasePath } from '../../../services'; +import { RouteDependencies } from '../../../types'; + +/** + * Unfollow follower index's leader index + */ +export const registerUnfollowRoute = ({ + router, + license, + lib: { isEsError, formatEsError }, +}: RouteDependencies) => { + const paramsSchema = schema.object({ id: schema.string() }); + + router.put( + { + path: addBasePath('/follower_indices/{id}/unfollow'), + validate: { + params: paramsSchema, + }, + }, + license.guardApiRoute(async (context, request, response) => { + const { id } = request.params; + const ids = id.split(','); + + const itemsUnfollowed: string[] = []; + const itemsNotOpen: string[] = []; + const errors: Array<{ id: string; error: any }> = []; + + const formatError = (err: any) => { + if (isEsError(err)) { + return response.customError(formatEsError(err)); + } + // Case: default + return response.internalError({ body: err }); + }; + + await Promise.all( + ids.map(async (_id: string) => { + try { + // Try to pause follower, let it fail silently since it may already be paused + try { + await context.crossClusterReplication!.client.callAsCurrentUser( + 'ccr.pauseFollowerIndex', + { id: _id } + ); + } catch (e) { + // Swallow errors + } + + // Close index + await context.crossClusterReplication!.client.callAsCurrentUser('indices.close', { + index: _id, + }); + + // Unfollow leader + await context.crossClusterReplication!.client.callAsCurrentUser( + 'ccr.unfollowLeaderIndex', + { id: _id } + ); + + // Try to re-open the index, store failures in a separate array to surface warnings in the UI + // This will allow users to query their index normally after unfollowing + try { + await context.crossClusterReplication!.client.callAsCurrentUser('indices.open', { + index: _id, + }); + } catch (e) { + itemsNotOpen.push(_id); + } + + // Push success + itemsUnfollowed.push(_id); + } catch (err) { + errors.push({ id: _id, error: formatError(err) }); + } + }) + ); + + return response.ok({ + body: { + itemsUnfollowed, + itemsNotOpen, + errors, + }, + }); + }) + ); +}; diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_update_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_update_route.ts new file mode 100644 index 0000000000000..521de77180974 --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_update_route.ts @@ -0,0 +1,93 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema } from '@kbn/config-schema'; +import { serializeAdvancedSettings } from '../../../../common/services/follower_index_serialization'; +import { FollowerIndexAdvancedSettings } from '../../../../common/types'; +import { removeEmptyFields } from '../../../../common/services/utils'; +import { addBasePath } from '../../../services'; +import { RouteDependencies } from '../../../types'; + +/** + * Update a follower index + */ +export const registerUpdateRoute = ({ + router, + license, + lib: { isEsError, formatEsError }, +}: RouteDependencies) => { + const paramsSchema = schema.object({ id: schema.string() }); + + const bodySchema = schema.object({ + maxReadRequestOperationCount: schema.maybe(schema.number()), + maxOutstandingReadRequests: schema.maybe(schema.number()), + maxReadRequestSize: schema.maybe(schema.string()), // byte value + maxWriteRequestOperationCount: schema.maybe(schema.number()), + maxWriteRequestSize: schema.maybe(schema.string()), // byte value + maxOutstandingWriteRequests: schema.maybe(schema.number()), + maxWriteBufferCount: schema.maybe(schema.number()), + maxWriteBufferSize: schema.maybe(schema.string()), // byte value + maxRetryDelay: schema.maybe(schema.string()), // time value + readPollTimeout: schema.maybe(schema.string()), // time value + }); + + router.put( + { + path: addBasePath('/follower_indices/{id}'), + validate: { + params: paramsSchema, + body: bodySchema, + }, + }, + license.guardApiRoute(async (context, request, response) => { + const { id } = request.params; + + // We need to first pause the follower and then resume it by passing the advanced settings + try { + const { + follower_indices: followerIndices, + } = await context.crossClusterReplication!.client.callAsCurrentUser('ccr.info', { id }); + + const followerIndexInfo = followerIndices && followerIndices[0]; + + if (!followerIndexInfo) { + return response.notFound({ body: `The follower index "${id}" does not exist.` }); + } + + // Retrieve paused state instead of pulling it from the payload to ensure it's not stale. + const isPaused = followerIndexInfo.status === 'paused'; + + // Pause follower if not already paused + if (!isPaused) { + await context.crossClusterReplication!.client.callAsCurrentUser( + 'ccr.pauseFollowerIndex', + { + id, + } + ); + } + + // Resume follower + const body = removeEmptyFields( + serializeAdvancedSettings(request.body as FollowerIndexAdvancedSettings) + ); + + return response.ok({ + body: await context.crossClusterReplication!.client.callAsCurrentUser( + 'ccr.resumeFollowerIndex', + { id, body } + ), + }); + } catch (err) { + if (isEsError(err)) { + return response.customError(formatEsError(err)); + } + // Case: default + return response.internalError({ body: err }); + } + }) + ); +}; diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/test_lib.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/test_lib.ts new file mode 100644 index 0000000000000..9b4fb134ed230 --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/test_lib.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { RequestHandlerContext } from 'src/core/server'; + +export function mockRouteContext({ + callAsCurrentUser, +}: { + callAsCurrentUser: any; +}): RequestHandlerContext { + const routeContextMock = ({ + crossClusterReplication: { + client: { + callAsCurrentUser, + }, + }, + } as unknown) as RequestHandlerContext; + + return routeContextMock; +} diff --git a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/routes/register_routes.ts b/x-pack/plugins/cross_cluster_replication/server/routes/index.ts similarity index 52% rename from x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/routes/register_routes.ts rename to x-pack/plugins/cross_cluster_replication/server/routes/index.ts index 7e59417550691..84abfb369e002 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/routes/register_routes.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/index.ts @@ -4,13 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ +import { RouteDependencies } from '../types'; + import { registerAutoFollowPatternRoutes } from './api/auto_follow_pattern'; import { registerFollowerIndexRoutes } from './api/follower_index'; -import { registerCcrRoutes } from './api/ccr'; -import { RouteDependencies } from './types'; +import { registerCrossClusterReplicationRoutes } from './api/cross_cluster_replication'; -export function registerRoutes(deps: RouteDependencies) { - registerAutoFollowPatternRoutes(deps); - registerFollowerIndexRoutes(deps); - registerCcrRoutes(deps); +export function registerApiRoutes(dependencies: RouteDependencies) { + registerAutoFollowPatternRoutes(dependencies); + registerFollowerIndexRoutes(dependencies); + registerCrossClusterReplicationRoutes(dependencies); } diff --git a/x-pack/legacy/plugins/canvas/public/lib/kibana_advanced_settings.ts b/x-pack/plugins/cross_cluster_replication/server/services/add_base_path.ts similarity index 64% rename from x-pack/legacy/plugins/canvas/public/lib/kibana_advanced_settings.ts rename to x-pack/plugins/cross_cluster_replication/server/services/add_base_path.ts index f57f3188a8184..3f3dd131df7c7 100644 --- a/x-pack/legacy/plugins/canvas/public/lib/kibana_advanced_settings.ts +++ b/x-pack/plugins/cross_cluster_replication/server/services/add_base_path.ts @@ -4,6 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getCoreStart } from '../legacy'; +import { API_BASE_PATH } from '../../common/constants'; -export const getAdvancedSettings = () => getCoreStart().uiSettings; +export const addBasePath = (uri: string): string => `${API_BASE_PATH}${uri}`; diff --git a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/lib/license_pre_routing_factory/index.ts b/x-pack/plugins/cross_cluster_replication/server/services/index.ts similarity index 74% rename from x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/lib/license_pre_routing_factory/index.ts rename to x-pack/plugins/cross_cluster_replication/server/services/index.ts index 0743e443955f4..d7b544b290c39 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/lib/license_pre_routing_factory/index.ts +++ b/x-pack/plugins/cross_cluster_replication/server/services/index.ts @@ -4,4 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ -export { licensePreRoutingFactory } from './license_pre_routing_factory'; +export { License } from './license'; +export { addBasePath } from './add_base_path'; diff --git a/x-pack/plugins/cross_cluster_replication/server/services/license.ts b/x-pack/plugins/cross_cluster_replication/server/services/license.ts new file mode 100644 index 0000000000000..bfd357867c3e2 --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/server/services/license.ts @@ -0,0 +1,93 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { Logger } from 'src/core/server'; +import { + KibanaRequest, + KibanaResponseFactory, + RequestHandler, + RequestHandlerContext, +} from 'src/core/server'; + +import { LicensingPluginSetup } from '../../../licensing/server'; +import { LicenseType } from '../../../licensing/common/types'; + +export interface LicenseStatus { + isValid: boolean; + message?: string; +} + +interface SetupSettings { + pluginId: string; + minimumLicenseType: LicenseType; + defaultErrorMessage: string; +} + +export class License { + private licenseStatus: LicenseStatus = { + isValid: false, + message: 'Invalid License', + }; + + private _isEsSecurityEnabled: boolean = false; + + setup( + { pluginId, minimumLicenseType, defaultErrorMessage }: SetupSettings, + { licensing, logger }: { licensing: LicensingPluginSetup; logger: Logger } + ) { + licensing.license$.subscribe(license => { + const { state, message } = license.check(pluginId, minimumLicenseType); + const hasRequiredLicense = state === 'valid'; + + // Retrieving security checks the results of GET /_xpack as well as license state, + // so we're also checking whether the security is disabled in elasticsearch.yml. + this._isEsSecurityEnabled = license.getFeature('security').isEnabled; + + if (hasRequiredLicense) { + this.licenseStatus = { isValid: true }; + } else { + this.licenseStatus = { + isValid: false, + message: message || defaultErrorMessage, + }; + if (message) { + logger.info(message); + } + } + }); + } + + guardApiRoute(handler: RequestHandler) { + const license = this; + + return function licenseCheck( + ctx: RequestHandlerContext, + request: KibanaRequest, + response: KibanaResponseFactory + ) { + const licenseStatus = license.getStatus(); + + if (!licenseStatus.isValid) { + return response.customError({ + body: { + message: licenseStatus.message || '', + }, + statusCode: 403, + }); + } + + return handler(ctx, request, response); + }; + } + + getStatus() { + return this.licenseStatus; + } + + // eslint-disable-next-line @typescript-eslint/explicit-member-accessibility + get isEsSecurityEnabled() { + return this._isEsSecurityEnabled; + } +} diff --git a/x-pack/plugins/cross_cluster_replication/server/types.ts b/x-pack/plugins/cross_cluster_replication/server/types.ts new file mode 100644 index 0000000000000..049d440e3d85d --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/server/types.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IRouter } from 'src/core/server'; +import { LicensingPluginSetup } from '../../licensing/server'; +import { IndexManagementPluginSetup } from '../../index_management/server'; +import { RemoteClustersPluginSetup } from '../../remote_clusters/server'; +import { License } from './services'; +import { isEsError } from './lib/is_es_error'; +import { formatEsError } from './lib/format_es_error'; + +export interface Dependencies { + licensing: LicensingPluginSetup; + indexManagement: IndexManagementPluginSetup; + remoteClusters: RemoteClustersPluginSetup; +} + +export interface RouteDependencies { + router: IRouter; + license: License; + lib: { + isEsError: typeof isEsError; + formatEsError: typeof formatEsError; + }; +} diff --git a/x-pack/plugins/data_enhanced/public/search/async_search_strategy.test.ts b/x-pack/plugins/data_enhanced/public/search/async_search_strategy.test.ts index 95f2c9e477064..a7d6aa894d91d 100644 --- a/x-pack/plugins/data_enhanced/public/search/async_search_strategy.test.ts +++ b/x-pack/plugins/data_enhanced/public/search/async_search_strategy.test.ts @@ -45,9 +45,11 @@ describe('Async search strategy', () => { it('stops polling when the response is complete', async () => { mockSearch - .mockReturnValueOnce(of({ id: 1, total: 2, loaded: 1 })) - .mockReturnValueOnce(of({ id: 1, total: 2, loaded: 2 })) - .mockReturnValueOnce(of({ id: 1, total: 2, loaded: 2 })); + .mockReturnValueOnce(of({ id: 1, total: 2, loaded: 1, is_running: true, is_partial: true })) + .mockReturnValueOnce(of({ id: 1, total: 2, loaded: 2, is_running: false, is_partial: false })) + .mockReturnValueOnce( + of({ id: 1, total: 2, loaded: 2, is_running: false, is_partial: false }) + ); const asyncSearch = asyncSearchStrategyProvider({ core: mockCoreStart, @@ -67,10 +69,39 @@ describe('Async search strategy', () => { expect(mockSearch).toBeCalledTimes(2); }); + it('stops polling when the response is an error', async () => { + mockSearch + .mockReturnValueOnce(of({ id: 1, total: 2, loaded: 1, is_running: true, is_partial: true })) + .mockReturnValueOnce(of({ id: 1, total: 2, loaded: 2, is_running: false, is_partial: true })) + .mockReturnValueOnce(of({ id: 1, total: 2, loaded: 2, is_running: false, is_partial: true })); + + const asyncSearch = asyncSearchStrategyProvider({ + core: mockCoreStart, + getSearchStrategy: jest.fn().mockImplementation(() => { + return () => { + return { + search: mockSearch, + }; + }; + }), + }); + + expect(mockSearch).toBeCalledTimes(0); + + await asyncSearch + .search(mockRequest, mockOptions) + .toPromise() + .catch(() => { + expect(mockSearch).toBeCalledTimes(2); + }); + }); + it('only sends the ID and server strategy after the first request', async () => { mockSearch - .mockReturnValueOnce(of({ id: 1, total: 2, loaded: 1 })) - .mockReturnValueOnce(of({ id: 1, total: 2, loaded: 2 })); + .mockReturnValueOnce(of({ id: 1, total: 2, loaded: 1, is_running: true, is_partial: true })) + .mockReturnValueOnce( + of({ id: 1, total: 2, loaded: 2, is_running: false, is_partial: false }) + ); const asyncSearch = asyncSearchStrategyProvider({ core: mockCoreStart, diff --git a/x-pack/plugins/data_enhanced/public/search/async_search_strategy.ts b/x-pack/plugins/data_enhanced/public/search/async_search_strategy.ts index 6271d7fcbeaac..18b5b976b3c1b 100644 --- a/x-pack/plugins/data_enhanced/public/search/async_search_strategy.ts +++ b/x-pack/plugins/data_enhanced/public/search/async_search_strategy.ts @@ -14,7 +14,7 @@ import { SYNC_SEARCH_STRATEGY, TSearchStrategyProvider, } from '../../../../../src/plugins/data/public'; -import { IAsyncSearchRequest, IAsyncSearchOptions } from './types'; +import { IAsyncSearchRequest, IAsyncSearchOptions, IAsyncSearchResponse } from './types'; export const ASYNC_SEARCH_STRATEGY = 'ASYNC_SEARCH_STRATEGY'; @@ -52,9 +52,14 @@ export const asyncSearchStrategyProvider: TSearchStrategyProvider { + expand((response: IAsyncSearchResponse) => { + // If the response indicates of an error, stop polling and complete the observable + if (!response || (response.is_partial && !response.is_running)) { + return throwError(new AbortError()); + } + // If the response indicates it is complete, stop polling and complete the observable - if ((response.loaded ?? 0) >= (response.total ?? 0)) return EMPTY; + if (!response.is_running) return EMPTY; id = response.id; diff --git a/x-pack/plugins/data_enhanced/public/search/types.ts b/x-pack/plugins/data_enhanced/public/search/types.ts index edaaf1b22654d..8ffc8eddda052 100644 --- a/x-pack/plugins/data_enhanced/public/search/types.ts +++ b/x-pack/plugins/data_enhanced/public/search/types.ts @@ -4,7 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ISearchOptions, ISyncSearchRequest } from '../../../../../src/plugins/data/public'; +import { + IKibanaSearchResponse, + ISearchOptions, + ISyncSearchRequest, +} from '../../../../../src/plugins/data/public'; export interface IAsyncSearchRequest extends ISyncSearchRequest { /** @@ -19,3 +23,14 @@ export interface IAsyncSearchOptions extends ISearchOptions { */ pollInterval?: number; } + +export interface IAsyncSearchResponse extends IKibanaSearchResponse { + /** + * Indicates whether async search is still in flight + */ + is_running?: boolean; + /** + * Indicates whether the results returned are complete or partial + */ + is_partial?: boolean; +} diff --git a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts index 6b329bccab4a7..bf502889ffa4f 100644 --- a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts +++ b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts @@ -23,6 +23,8 @@ import { shimHitsTotal } from './shim_hits_total'; export interface AsyncSearchResponse { id: string; + is_partial: boolean; + is_running: boolean; response: SearchResponse; } @@ -71,13 +73,19 @@ async function asyncSearch( // Wait up to 1s for the response to return const query = toSnakeCase({ waitForCompletionTimeout: '1s', ...queryParams }); - const { response, id } = (await caller( + const { id, response, is_partial, is_running } = (await caller( 'transport.request', { method, path, body, query }, options )) as AsyncSearchResponse; - return { id, rawResponse: shimHitsTotal(response), ...getTotalLoaded(response._shards) }; + return { + id, + is_partial, + is_running, + rawResponse: shimHitsTotal(response), + ...getTotalLoaded(response._shards), + }; } async function rollupSearch( diff --git a/x-pack/plugins/endpoint/server/endpoint_app_context_services.test.ts b/x-pack/plugins/endpoint/server/endpoint_app_context_services.test.ts new file mode 100644 index 0000000000000..943a9c22c6aae --- /dev/null +++ b/x-pack/plugins/endpoint/server/endpoint_app_context_services.test.ts @@ -0,0 +1,14 @@ +/* + * 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 { EndpointAppContextService } from './endpoint_app_context_services'; + +describe('test endpoint app context services', () => { + it('should throw error if start is not called', async () => { + const endpointAppContextService = new EndpointAppContextService(); + expect(() => endpointAppContextService.getIndexPatternRetriever()).toThrow(Error); + expect(() => endpointAppContextService.getAgentService()).toThrow(Error); + }); +}); diff --git a/x-pack/plugins/endpoint/server/endpoint_app_context_services.ts b/x-pack/plugins/endpoint/server/endpoint_app_context_services.ts new file mode 100644 index 0000000000000..4d77c5c68c69f --- /dev/null +++ b/x-pack/plugins/endpoint/server/endpoint_app_context_services.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 { IndexPatternRetriever } from './index_pattern'; +import { AgentService } from '../../ingest_manager/common/types'; + +/** + * A singleton that holds shared services that are initialized during the start up phase + * of the plugin lifecycle. And stop during the stop phase, if needed. + */ +export class EndpointAppContextService { + private indexPatternRetriever: IndexPatternRetriever | undefined; + private agentService: AgentService | undefined; + + public start(dependencies: { + indexPatternRetriever: IndexPatternRetriever; + agentService: AgentService; + }) { + this.indexPatternRetriever = dependencies.indexPatternRetriever; + this.agentService = dependencies.agentService; + } + + public stop() {} + + public getAgentService(): AgentService { + if (!this.agentService) { + throw new Error(`must call start on ${EndpointAppContextService.name} to call getter`); + } + return this.agentService; + } + + public getIndexPatternRetriever(): IndexPatternRetriever { + if (!this.indexPatternRetriever) { + throw new Error(`must call start on ${EndpointAppContextService.name} to call getter`); + } + return this.indexPatternRetriever; + } +} diff --git a/x-pack/plugins/endpoint/server/index_pattern.ts b/x-pack/plugins/endpoint/server/index_pattern.ts index ea612bfd75441..05cfd91f2777a 100644 --- a/x-pack/plugins/endpoint/server/index_pattern.ts +++ b/x-pack/plugins/endpoint/server/index_pattern.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ import { Logger, LoggerFactory, RequestHandlerContext } from 'kibana/server'; -import { ESIndexPatternService } from '../../ingest_manager/server'; import { EndpointAppConstants } from '../common/types'; +import { ESIndexPatternService } from '../../ingest_manager/common/types'; export interface IndexPatternRetriever { getIndexPattern(ctx: RequestHandlerContext, datasetPath: string): Promise; diff --git a/x-pack/plugins/endpoint/server/mocks.ts b/x-pack/plugins/endpoint/server/mocks.ts index 3881840efe9df..8d677cf37f066 100644 --- a/x-pack/plugins/endpoint/server/mocks.ts +++ b/x-pack/plugins/endpoint/server/mocks.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IngestManagerSetupContract } from '../../ingest_manager/server'; -import { AgentService } from '../../ingest_manager/common/types'; +import { AgentService, IngestManagerStartupContract } from '../../ingest_manager/common/types'; /** * Creates a mock IndexPatternRetriever for use in tests. @@ -47,9 +46,9 @@ export const createMockAgentService = (): jest.Mocked => { * @param indexPattern a string index pattern to return when called by a test * @returns the same value as `indexPattern` parameter */ -export const createMockIngestManagerSetupContract = ( +export const createMockIngestManagerStartupContract = ( indexPattern: string -): IngestManagerSetupContract => { +): IngestManagerStartupContract => { return { esIndexPatternService: { getESIndexPattern: jest.fn().mockResolvedValue(indexPattern), diff --git a/x-pack/plugins/endpoint/server/plugin.test.ts b/x-pack/plugins/endpoint/server/plugin.test.ts index c380bc5c3e3d0..44b9c3eabbd6d 100644 --- a/x-pack/plugins/endpoint/server/plugin.test.ts +++ b/x-pack/plugins/endpoint/server/plugin.test.ts @@ -4,15 +4,21 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EndpointPlugin, EndpointPluginSetupDependencies } from './plugin'; +import { + EndpointPlugin, + EndpointPluginSetupDependencies, + EndpointPluginStartDependencies, +} from './plugin'; import { coreMock } from '../../../../src/core/server/mocks'; import { PluginSetupContract } from '../../features/server'; -import { createMockIngestManagerSetupContract } from './mocks'; +import { createMockIngestManagerStartupContract } from './mocks'; describe('test endpoint plugin', () => { let plugin: EndpointPlugin; let mockCoreSetup: ReturnType; + let mockCoreStart: ReturnType; let mockedEndpointPluginSetupDependencies: jest.Mocked; + let mockedEndpointPluginStartDependencies: jest.Mocked; let mockedPluginSetupContract: jest.Mocked; beforeEach(() => { plugin = new EndpointPlugin( @@ -23,21 +29,32 @@ describe('test endpoint plugin', () => { ); mockCoreSetup = coreMock.createSetup(); + mockCoreStart = coreMock.createStart(); mockedPluginSetupContract = { registerFeature: jest.fn(), getFeatures: jest.fn(), getFeaturesUICapabilities: jest.fn(), registerLegacyAPI: jest.fn(), }; - mockedEndpointPluginSetupDependencies = { - features: mockedPluginSetupContract, - ingestManager: createMockIngestManagerSetupContract(''), - }; }); it('test properly setup plugin', async () => { + mockedEndpointPluginSetupDependencies = { + features: mockedPluginSetupContract, + }; await plugin.setup(mockCoreSetup, mockedEndpointPluginSetupDependencies); expect(mockedPluginSetupContract.registerFeature).toBeCalledTimes(1); expect(mockCoreSetup.http.createRouter).toBeCalledTimes(1); + expect(() => plugin.getEndpointAppContextService().getIndexPatternRetriever()).toThrow(Error); + expect(() => plugin.getEndpointAppContextService().getAgentService()).toThrow(Error); + }); + + it('test properly start plugin', async () => { + mockedEndpointPluginStartDependencies = { + ingestManager: createMockIngestManagerStartupContract(''), + }; + await plugin.start(mockCoreStart, mockedEndpointPluginStartDependencies); + expect(plugin.getEndpointAppContextService().getAgentService()).toBeTruthy(); + expect(plugin.getEndpointAppContextService().getIndexPatternRetriever()).toBeTruthy(); }); }); diff --git a/x-pack/plugins/endpoint/server/plugin.ts b/x-pack/plugins/endpoint/server/plugin.ts index ce6be5aeaf6db..f7ff32bdbfcbf 100644 --- a/x-pack/plugins/endpoint/server/plugin.ts +++ b/x-pack/plugins/endpoint/server/plugin.ts @@ -3,10 +3,9 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { Plugin, CoreSetup, PluginInitializerContext, Logger } from 'kibana/server'; +import { Plugin, CoreSetup, PluginInitializerContext, Logger, CoreStart } from 'kibana/server'; import { first } from 'rxjs/operators'; import { PluginSetupContract as FeaturesPluginSetupContract } from '../../features/server'; -import { IngestManagerSetupContract } from '../../ingest_manager/server'; import { createConfig$, EndpointConfigType } from './config'; import { EndpointAppContext } from './types'; @@ -15,14 +14,17 @@ import { registerResolverRoutes } from './routes/resolver'; import { registerIndexPatternRoute } from './routes/index_pattern'; import { registerEndpointRoutes } from './routes/metadata'; import { IngestIndexPatternRetriever } from './index_pattern'; +import { IngestManagerStartupContract } from '../../ingest_manager/common/types'; +import { EndpointAppContextService } from './endpoint_app_context_services'; export type EndpointPluginStart = void; export type EndpointPluginSetup = void; -export interface EndpointPluginStartDependencies {} // eslint-disable-line @typescript-eslint/no-empty-interface +export interface EndpointPluginStartDependencies { + ingestManager: IngestManagerStartupContract; +} export interface EndpointPluginSetupDependencies { features: FeaturesPluginSetupContract; - ingestManager: IngestManagerSetupContract; } export class EndpointPlugin @@ -34,9 +36,15 @@ export class EndpointPlugin EndpointPluginStartDependencies > { private readonly logger: Logger; + private readonly endpointAppContextService: EndpointAppContextService = new EndpointAppContextService(); constructor(private readonly initializerContext: PluginInitializerContext) { this.logger = this.initializerContext.logger.get('endpoint'); } + + public getEndpointAppContextService(): EndpointAppContextService { + return this.endpointAppContextService; + } + public setup(core: CoreSetup, plugins: EndpointPluginSetupDependencies) { plugins.features.registerFeature({ id: 'endpoint', @@ -66,12 +74,8 @@ export class EndpointPlugin }, }); const endpointContext = { - indexPatternRetriever: new IngestIndexPatternRetriever( - plugins.ingestManager.esIndexPatternService, - this.initializerContext.logger - ), - agentService: plugins.ingestManager.agentService, logFactory: this.initializerContext.logger, + service: this.endpointAppContextService, config: (): Promise => { return createConfig$(this.initializerContext) .pipe(first()) @@ -85,10 +89,18 @@ export class EndpointPlugin registerIndexPatternRoute(router, endpointContext); } - public start() { + public start(core: CoreStart, plugins: EndpointPluginStartDependencies) { this.logger.debug('Starting plugin'); + this.endpointAppContextService.start({ + indexPatternRetriever: new IngestIndexPatternRetriever( + plugins.ingestManager.esIndexPatternService, + this.initializerContext.logger + ), + agentService: plugins.ingestManager.agentService, + }); } public stop() { this.logger.debug('Stopping plugin'); + this.endpointAppContextService.stop(); } } diff --git a/x-pack/plugins/endpoint/server/routes/alerts/alerts.test.ts b/x-pack/plugins/endpoint/server/routes/alerts/alerts.test.ts index 39fc2ba4c74bb..1124c977ff924 100644 --- a/x-pack/plugins/endpoint/server/routes/alerts/alerts.test.ts +++ b/x-pack/plugins/endpoint/server/routes/alerts/alerts.test.ts @@ -13,25 +13,35 @@ import { registerAlertRoutes } from './index'; import { EndpointConfigSchema } from '../../config'; import { alertingIndexGetQuerySchema } from '../../../common/schema/alert_index'; import { createMockAgentService, createMockIndexPatternRetriever } from '../../mocks'; +import { EndpointAppContextService } from '../../endpoint_app_context_services'; describe('test alerts route', () => { let routerMock: jest.Mocked; let mockClusterClient: jest.Mocked; let mockScopedClient: jest.Mocked; + let endpointAppContextService: EndpointAppContextService; beforeEach(() => { mockClusterClient = elasticsearchServiceMock.createClusterClient(); mockScopedClient = elasticsearchServiceMock.createScopedClusterClient(); mockClusterClient.asScoped.mockReturnValue(mockScopedClient); routerMock = httpServiceMock.createRouter(); - registerAlertRoutes(routerMock, { + + endpointAppContextService = new EndpointAppContextService(); + endpointAppContextService.start({ indexPatternRetriever: createMockIndexPatternRetriever('events-endpoint-*'), agentService: createMockAgentService(), + }); + + registerAlertRoutes(routerMock, { logFactory: loggingServiceMock.create(), + service: endpointAppContextService, config: () => Promise.resolve(EndpointConfigSchema.validate({})), }); }); + afterEach(() => endpointAppContextService.stop()); + it('should fail to validate when `page_size` is not a number', async () => { const validate = () => { alertingIndexGetQuerySchema.validate({ diff --git a/x-pack/plugins/endpoint/server/routes/alerts/details/handlers.ts b/x-pack/plugins/endpoint/server/routes/alerts/details/handlers.ts index 9055ee4110fbb..e438ab853f3b5 100644 --- a/x-pack/plugins/endpoint/server/routes/alerts/details/handlers.ts +++ b/x-pack/plugins/endpoint/server/routes/alerts/details/handlers.ts @@ -26,7 +26,9 @@ export const alertDetailsHandlerWrapper = function( id: alertId, })) as GetResponse; - const indexPattern = await endpointAppContext.indexPatternRetriever.getEventIndexPattern(ctx); + const indexPattern = await endpointAppContext.service + .getIndexPatternRetriever() + .getEventIndexPattern(ctx); const config = await endpointAppContext.config(); const pagination: AlertDetailsPagination = new AlertDetailsPagination( diff --git a/x-pack/plugins/endpoint/server/routes/alerts/list/handlers.ts b/x-pack/plugins/endpoint/server/routes/alerts/list/handlers.ts index f23dffd13db4f..44a0cf8744a9e 100644 --- a/x-pack/plugins/endpoint/server/routes/alerts/list/handlers.ts +++ b/x-pack/plugins/endpoint/server/routes/alerts/list/handlers.ts @@ -18,7 +18,9 @@ export const alertListHandlerWrapper = function( res ) => { try { - const indexPattern = await endpointAppContext.indexPatternRetriever.getEventIndexPattern(ctx); + const indexPattern = await endpointAppContext.service + .getIndexPatternRetriever() + .getEventIndexPattern(ctx); const reqData = await getRequestData(req, endpointAppContext); const response = await searchESForAlerts( ctx.core.elasticsearch.dataClient, diff --git a/x-pack/plugins/endpoint/server/routes/index_pattern.ts b/x-pack/plugins/endpoint/server/routes/index_pattern.ts index 3b71f6a6957ba..79083f5f05e14 100644 --- a/x-pack/plugins/endpoint/server/routes/index_pattern.ts +++ b/x-pack/plugins/endpoint/server/routes/index_pattern.ts @@ -8,14 +8,14 @@ import { IRouter, Logger, RequestHandler } from 'kibana/server'; import { EndpointAppContext } from '../types'; import { IndexPatternGetParamsResult, EndpointAppConstants } from '../../common/types'; import { indexPatternGetParamsSchema } from '../../common/schema/index_pattern'; -import { IndexPatternRetriever } from '../index_pattern'; function handleIndexPattern( log: Logger, - indexRetriever: IndexPatternRetriever + endpointAppContext: EndpointAppContext ): RequestHandler { return async (context, req, res) => { try { + const indexRetriever = endpointAppContext.service.getIndexPatternRetriever(); return res.ok({ body: { indexPattern: await indexRetriever.getIndexPattern(context, req.params.datasetPath), @@ -37,6 +37,6 @@ export function registerIndexPatternRoute(router: IRouter, endpointAppContext: E validate: { params: indexPatternGetParamsSchema }, options: { authRequired: true }, }, - handleIndexPattern(log, endpointAppContext.indexPatternRetriever) + handleIndexPattern(log, endpointAppContext) ); } diff --git a/x-pack/plugins/endpoint/server/routes/metadata/index.ts b/x-pack/plugins/endpoint/server/routes/metadata/index.ts index bc79b828576e0..99dc4ac9f9e33 100644 --- a/x-pack/plugins/endpoint/server/routes/metadata/index.ts +++ b/x-pack/plugins/endpoint/server/routes/metadata/index.ts @@ -61,9 +61,9 @@ export function registerEndpointRoutes(router: IRouter, endpointAppContext: Endp }, async (context, req, res) => { try { - const index = await endpointAppContext.indexPatternRetriever.getMetadataIndexPattern( - context - ); + const index = await endpointAppContext.service + .getIndexPatternRetriever() + .getMetadataIndexPattern(context); const queryParams = await kibanaRequestToMetadataListESQuery( req, endpointAppContext, @@ -117,9 +117,9 @@ export async function getHostData( metadataRequestContext: MetadataRequestContext, id: string ): Promise { - const index = await metadataRequestContext.endpointAppContext.indexPatternRetriever.getMetadataIndexPattern( - metadataRequestContext.requestHandlerContext - ); + const index = await metadataRequestContext.endpointAppContext.service + .getIndexPatternRetriever() + .getMetadataIndexPattern(metadataRequestContext.requestHandlerContext); const query = getESQueryHostMetadataByID(id, index); const response = (await metadataRequestContext.requestHandlerContext.core.elasticsearch.dataClient.callAsCurrentUser( 'search', @@ -179,10 +179,12 @@ async function enrichHostMetadata( log.warn(`Missing elastic agent id, using host id instead ${elasticAgentId}`); } - const status = await metadataRequestContext.endpointAppContext.agentService.getAgentStatusById( - metadataRequestContext.requestHandlerContext.core.savedObjects.client, - elasticAgentId - ); + const status = await metadataRequestContext.endpointAppContext.service + .getAgentService() + .getAgentStatusById( + metadataRequestContext.requestHandlerContext.core.savedObjects.client, + elasticAgentId + ); hostStatus = HOST_STATUS_MAPPING.get(status) || HostStatus.ERROR; } catch (e) { if (e.isBoom && e.output.statusCode === 404) { diff --git a/x-pack/plugins/endpoint/server/routes/metadata/metadata.test.ts b/x-pack/plugins/endpoint/server/routes/metadata/metadata.test.ts index a1186aabc7a66..84930118df238 100644 --- a/x-pack/plugins/endpoint/server/routes/metadata/metadata.test.ts +++ b/x-pack/plugins/endpoint/server/routes/metadata/metadata.test.ts @@ -28,6 +28,7 @@ import * as data from '../../test_data/all_metadata_data.json'; import { createMockAgentService, createMockMetadataIndexPatternRetriever } from '../../mocks'; import { AgentService } from '../../../../ingest_manager/common/types'; import Boom from 'boom'; +import { EndpointAppContextService } from '../../endpoint_app_context_services'; describe('test endpoint route', () => { let routerMock: jest.Mocked; @@ -38,6 +39,7 @@ describe('test endpoint route', () => { let routeHandler: RequestHandler; let routeConfig: RouteConfig; let mockAgentService: jest.Mocked; + let endpointAppContextService: EndpointAppContextService; beforeEach(() => { mockClusterClient = elasticsearchServiceMock.createClusterClient() as jest.Mocked< @@ -49,14 +51,21 @@ describe('test endpoint route', () => { routerMock = httpServiceMock.createRouter(); mockResponse = httpServerMock.createResponseFactory(); mockAgentService = createMockAgentService(); - registerEndpointRoutes(routerMock, { + endpointAppContextService = new EndpointAppContextService(); + endpointAppContextService.start({ indexPatternRetriever: createMockMetadataIndexPatternRetriever(), agentService: mockAgentService, + }); + + registerEndpointRoutes(routerMock, { logFactory: loggingServiceMock.create(), + service: endpointAppContextService, config: () => Promise.resolve(EndpointConfigSchema.validate({})), }); }); + afterEach(() => endpointAppContextService.stop()); + function createRouteHandlerContext( dataClient: jest.Mocked, savedObjectsClient: jest.Mocked diff --git a/x-pack/plugins/endpoint/server/routes/metadata/query_builders.test.ts b/x-pack/plugins/endpoint/server/routes/metadata/query_builders.test.ts index 7e6e3f875cd4c..28bac2fa10e0c 100644 --- a/x-pack/plugins/endpoint/server/routes/metadata/query_builders.test.ts +++ b/x-pack/plugins/endpoint/server/routes/metadata/query_builders.test.ts @@ -6,11 +6,8 @@ import { httpServerMock, loggingServiceMock } from '../../../../../../src/core/server/mocks'; import { EndpointConfigSchema } from '../../config'; import { kibanaRequestToMetadataListESQuery, getESQueryHostMetadataByID } from './query_builders'; -import { - createMockAgentService, - createMockMetadataIndexPatternRetriever, - MetadataIndexPattern, -} from '../../mocks'; +import { MetadataIndexPattern } from '../../mocks'; +import { EndpointAppContextService } from '../../endpoint_app_context_services'; describe('query builder', () => { describe('MetadataListESQuery', () => { @@ -21,9 +18,8 @@ describe('query builder', () => { const query = await kibanaRequestToMetadataListESQuery( mockRequest, { - indexPatternRetriever: createMockMetadataIndexPatternRetriever(), - agentService: createMockAgentService(), logFactory: loggingServiceMock.create(), + service: new EndpointAppContextService(), config: () => Promise.resolve(EndpointConfigSchema.validate({})), }, MetadataIndexPattern @@ -73,9 +69,8 @@ describe('query builder', () => { const query = await kibanaRequestToMetadataListESQuery( mockRequest, { - indexPatternRetriever: createMockMetadataIndexPatternRetriever(), - agentService: createMockAgentService(), logFactory: loggingServiceMock.create(), + service: new EndpointAppContextService(), config: () => Promise.resolve(EndpointConfigSchema.validate({})), }, MetadataIndexPattern diff --git a/x-pack/plugins/endpoint/server/routes/resolver.ts b/x-pack/plugins/endpoint/server/routes/resolver.ts index 77fcbc87baeb1..a96d431225b15 100644 --- a/x-pack/plugins/endpoint/server/routes/resolver.ts +++ b/x-pack/plugins/endpoint/server/routes/resolver.ts @@ -12,7 +12,6 @@ import { handleLifecycle, validateLifecycle } from './resolver/lifecycle'; export function registerResolverRoutes(router: IRouter, endpointAppContext: EndpointAppContext) { const log = endpointAppContext.logFactory.get('resolver'); - const indexPatternService = endpointAppContext.indexPatternRetriever; router.get( { @@ -20,7 +19,7 @@ export function registerResolverRoutes(router: IRouter, endpointAppContext: Endp validate: validateRelatedEvents, options: { authRequired: true }, }, - handleRelatedEvents(log, indexPatternService) + handleRelatedEvents(log, endpointAppContext) ); router.get( @@ -29,7 +28,7 @@ export function registerResolverRoutes(router: IRouter, endpointAppContext: Endp validate: validateChildren, options: { authRequired: true }, }, - handleChildren(log, indexPatternService) + handleChildren(log, endpointAppContext) ); router.get( @@ -38,6 +37,6 @@ export function registerResolverRoutes(router: IRouter, endpointAppContext: Endp validate: validateLifecycle, options: { authRequired: true }, }, - handleLifecycle(log, indexPatternService) + handleLifecycle(log, endpointAppContext) ); } diff --git a/x-pack/plugins/endpoint/server/routes/resolver/children.ts b/x-pack/plugins/endpoint/server/routes/resolver/children.ts index 05b8f0b5f8608..c3b19b6c912b6 100644 --- a/x-pack/plugins/endpoint/server/routes/resolver/children.ts +++ b/x-pack/plugins/endpoint/server/routes/resolver/children.ts @@ -11,7 +11,7 @@ import { extractEntityID } from './utils/normalize'; import { getPaginationParams } from './utils/pagination'; import { LifecycleQuery } from './queries/lifecycle'; import { ChildrenQuery } from './queries/children'; -import { IndexPatternRetriever } from '../../index_pattern'; +import { EndpointAppContext } from '../../types'; interface ChildrenQueryParams { after?: string; @@ -47,7 +47,7 @@ export const validateChildren = { export function handleChildren( log: Logger, - indexRetriever: IndexPatternRetriever + endpointAppContext: EndpointAppContext ): RequestHandler { return async (context, req, res) => { const { @@ -55,6 +55,7 @@ export function handleChildren( query: { limit, after, legacyEndpointID }, } = req; try { + const indexRetriever = endpointAppContext.service.getIndexPatternRetriever(); const pagination = getPaginationParams(limit, after); const indexPattern = await indexRetriever.getEventIndexPattern(context); diff --git a/x-pack/plugins/endpoint/server/routes/resolver/lifecycle.ts b/x-pack/plugins/endpoint/server/routes/resolver/lifecycle.ts index 6d155b79651a7..91a4c5d49bc54 100644 --- a/x-pack/plugins/endpoint/server/routes/resolver/lifecycle.ts +++ b/x-pack/plugins/endpoint/server/routes/resolver/lifecycle.ts @@ -4,13 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import _ from 'lodash'; import { schema } from '@kbn/config-schema'; import { RequestHandler, Logger } from 'kibana/server'; import { extractParentEntityID } from './utils/normalize'; import { LifecycleQuery } from './queries/lifecycle'; import { ResolverEvent } from '../../../common/types'; -import { IndexPatternRetriever } from '../../index_pattern'; +import { EndpointAppContext } from '../../types'; interface LifecycleQueryParams { ancestors: number; @@ -48,7 +47,7 @@ function getParentEntityID(results: ResolverEvent[]) { export function handleLifecycle( log: Logger, - indexRetriever: IndexPatternRetriever + endpointAppContext: EndpointAppContext ): RequestHandler { return async (context, req, res) => { const { @@ -56,6 +55,7 @@ export function handleLifecycle( query: { ancestors, legacyEndpointID }, } = req; try { + const indexRetriever = endpointAppContext.service.getIndexPatternRetriever(); const ancestorLifecycles = []; const client = context.core.elasticsearch.dataClient; const indexPattern = await indexRetriever.getEventIndexPattern(context); diff --git a/x-pack/plugins/endpoint/server/routes/resolver/related_events.ts b/x-pack/plugins/endpoint/server/routes/resolver/related_events.ts index 46e205464f53c..83e111a1e62e6 100644 --- a/x-pack/plugins/endpoint/server/routes/resolver/related_events.ts +++ b/x-pack/plugins/endpoint/server/routes/resolver/related_events.ts @@ -8,7 +8,7 @@ import { schema } from '@kbn/config-schema'; import { RequestHandler, Logger } from 'kibana/server'; import { getPaginationParams } from './utils/pagination'; import { RelatedEventsQuery } from './queries/related_events'; -import { IndexPatternRetriever } from '../../index_pattern'; +import { EndpointAppContext } from '../../types'; interface RelatedEventsQueryParams { after?: string; @@ -44,7 +44,7 @@ export const validateRelatedEvents = { export function handleRelatedEvents( log: Logger, - indexRetriever: IndexPatternRetriever + endpointAppContext: EndpointAppContext ): RequestHandler { return async (context, req, res) => { const { @@ -52,6 +52,7 @@ export function handleRelatedEvents( query: { limit, after, legacyEndpointID }, } = req; try { + const indexRetriever = endpointAppContext.service.getIndexPatternRetriever(); const pagination = getPaginationParams(limit, after); const client = context.core.elasticsearch.dataClient; diff --git a/x-pack/plugins/endpoint/server/types.ts b/x-pack/plugins/endpoint/server/types.ts index d43ec58aec428..dfa5950adba5c 100644 --- a/x-pack/plugins/endpoint/server/types.ts +++ b/x-pack/plugins/endpoint/server/types.ts @@ -5,15 +5,17 @@ */ import { LoggerFactory } from 'kibana/server'; import { EndpointConfigType } from './config'; -import { IndexPatternRetriever } from './index_pattern'; -import { AgentService } from '../../ingest_manager/common/types'; +import { EndpointAppContextService } from './endpoint_app_context_services'; /** * The context for Endpoint apps. */ export interface EndpointAppContext { - indexPatternRetriever: IndexPatternRetriever; - agentService: AgentService; logFactory: LoggerFactory; config(): Promise; + + /** + * Object readiness is tied to plugin start method + */ + service: EndpointAppContextService; } diff --git a/x-pack/plugins/graph/public/angular/templates/_graph.scss b/x-pack/plugins/graph/public/angular/templates/_graph.scss index e6bd4693d1e9b..4ba65e7ec6b96 100644 --- a/x-pack/plugins/graph/public/angular/templates/_graph.scss +++ b/x-pack/plugins/graph/public/angular/templates/_graph.scss @@ -1,5 +1,3 @@ -@import 'src/legacy/ui/public/chrome/variables'; - @mixin gphSvgText() { font-family: $euiFontFamily; font-size: $euiSizeS; diff --git a/x-pack/plugins/graph/public/application.ts b/x-pack/plugins/graph/public/application.ts index f804265f1f5ab..fee42bdbeaf3b 100644 --- a/x-pack/plugins/graph/public/application.ts +++ b/x-pack/plugins/graph/public/application.ts @@ -12,6 +12,8 @@ import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/angular'; import '../../../../webpackShims/ace'; // required for i18nIdDirective import 'angular-sanitize'; +// required for ngRoute +import 'angular-route'; // type imports import { AppMountContext, diff --git a/x-pack/plugins/graph/public/components/field_manager/field_editor.tsx b/x-pack/plugins/graph/public/components/field_manager/field_editor.tsx index 78e4180aa2b2a..211458e67d05b 100644 --- a/x-pack/plugins/graph/public/components/field_manager/field_editor.tsx +++ b/x-pack/plugins/graph/public/components/field_manager/field_editor.tsx @@ -129,7 +129,7 @@ export function FieldEditor({ })} onClickAriaLabel={badgeDescription} title="" - onClick={e => { + onClick={(e: React.MouseEvent) => { if (e.shiftKey) { toggleDisabledState(); } else { diff --git a/x-pack/plugins/graph/public/components/field_manager/field_manager.test.tsx b/x-pack/plugins/graph/public/components/field_manager/field_manager.test.tsx index 948f89705a5d5..ac656ebdd9512 100644 --- a/x-pack/plugins/graph/public/components/field_manager/field_manager.test.tsx +++ b/x-pack/plugins/graph/public/components/field_manager/field_manager.test.tsx @@ -252,7 +252,11 @@ describe('field_manager', () => { act(() => { getDisplayForm() .find(EuiColorPicker) - .prop('onChange')!('#aaa'); + .prop('onChange')!('#aaa', { + rgba: [170, 170, 170, 1], + hex: '#aaa', + isValid: true, + }); }); fieldEditor.update(); getDisplayForm() diff --git a/x-pack/plugins/graph/public/index.scss b/x-pack/plugins/graph/public/index.scss index f4e38de3e93a4..964ef320e4352 100644 --- a/x-pack/plugins/graph/public/index.scss +++ b/x-pack/plugins/graph/public/index.scss @@ -12,3 +12,5 @@ @import './main'; @import './angular/templates/index'; @import './components/index'; +// Local application mount wrapper styles +@import 'src/legacy/core_plugins/kibana/public/local_application_service/index'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/notification.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/notification.ts index 7d24bc31006b4..aa3ac9ea75c22 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/services/notification.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/services/notification.ts @@ -4,10 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -export let toasts: any; -export let fatalErrors: any; +import { IToasts, FatalErrorsSetup } from 'src/core/public'; -export function init(_toasts: any, _fatalErrors: any): void { +export let toasts: IToasts; +export let fatalErrors: FatalErrorsSetup; + +export function init(_toasts: IToasts, _fatalErrors: FatalErrorsSetup): void { toasts = _toasts; fatalErrors = _fatalErrors; } diff --git a/x-pack/plugins/infra/public/apps/start_app.tsx b/x-pack/plugins/infra/public/apps/start_app.tsx index ebf9562c38d7a..4c213700b62e6 100644 --- a/x-pack/plugins/infra/public/apps/start_app.tsx +++ b/x-pack/plugins/infra/public/apps/start_app.tsx @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { createBrowserHistory } from 'history'; import React from 'react'; import ReactDOM from 'react-dom'; import { ApolloProvider } from 'react-apollo'; @@ -25,6 +24,7 @@ import { AppRouter } from '../routers'; import { TriggersAndActionsUIPublicPluginSetup } from '../../../triggers_actions_ui/public'; import { TriggersActionsProvider } from '../utils/triggers_actions_context'; import '../index.scss'; +import { NavigationWarningPromptProvider } from '../utils/navigation_warning_prompt'; export const CONTAINER_CLASSNAME = 'infra-container-element'; @@ -36,8 +36,8 @@ export async function startApp( Router: AppRouter, triggersActionsUI: TriggersAndActionsUIPublicPluginSetup ) { - const { element, appBasePath } = params; - const history = createBrowserHistory({ basename: appBasePath }); + const { element, history } = params; + const InfraPluginRoot: React.FunctionComponent = () => { const [darkMode] = useUiSetting$('theme:darkMode'); @@ -49,7 +49,9 @@ export async function startApp( - + + + diff --git a/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx b/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx index 665da25dcbfff..cdefffeb35c15 100644 --- a/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx +++ b/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx @@ -20,6 +20,7 @@ import { i18n } from '@kbn/i18n'; import { MetricExpressionParams, Comparator, + Aggregators, // eslint-disable-next-line @kbn/eslint/no-restricted-paths } from '../../../../server/lib/alerting/metric_threshold/types'; import { euiStyled } from '../../../../../observability/public'; @@ -31,6 +32,8 @@ import { // eslint-disable-next-line @kbn/eslint/no-restricted-paths } from '../../../../../triggers_actions_ui/public/common'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { builtInComparators } from '../../../../../triggers_actions_ui/public/common/constants'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths import { IErrorObject } from '../../../../../triggers_actions_ui/public/types'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { AlertsContextValue } from '../../../../../triggers_actions_ui/public/application/context/alerts_context'; @@ -64,24 +67,25 @@ type MetricExpression = Omit & { metric?: string; }; -enum AGGREGATION_TYPES { - COUNT = 'count', - AVERAGE = 'avg', - SUM = 'sum', - MIN = 'min', - MAX = 'max', - RATE = 'rate', - CARDINALITY = 'cardinality', -} - const defaultExpression = { - aggType: AGGREGATION_TYPES.AVERAGE, + aggType: Aggregators.AVERAGE, comparator: Comparator.GT, threshold: [], timeSize: 1, timeUnit: 'm', } as MetricExpression; +const customComparators = { + ...builtInComparators, + [Comparator.OUTSIDE_RANGE]: { + text: i18n.translate('xpack.infra.metrics.alertFlyout.outsideRangeLabel', { + defaultMessage: 'Is not between', + }), + value: Comparator.OUTSIDE_RANGE, + requiredValues: 2, + }, +}; + export const Expressions: React.FC = props => { const { setAlertParams, alertParams, errors, alertsContext } = props; const { source, createDerivedIndexPattern } = useSourceViaHttp({ @@ -339,7 +343,7 @@ const StyledExpression = euiStyled.div` export const ExpressionRow: React.FC = props => { const { setAlertParams, expression, errors, expressionId, remove, fields, canDelete } = props; const { - aggType = AGGREGATION_TYPES.MAX, + aggType = Aggregators.MAX, metric, comparator = Comparator.GT, threshold = [], @@ -410,6 +414,7 @@ export const ExpressionRow: React.FC = props => { { const INTERNAL_APP = 'metrics'; -// Note: Memory history doesn't support basename, -// we'll work around this by re-assigning 'createHref' so that -// it includes a basename, this then acts as our browserHistory instance would. const history = createMemoryHistory(); -const originalCreateHref = history.createHref; -history.createHref = (location: LocationDescriptorObject): string => { - return `${PREFIX}${INTERNAL_APP}${originalCreateHref.call(history, location)}`; -}; +history.push(`${PREFIX}${INTERNAL_APP}`); +const scopedHistory = new ScopedHistory(history, `${PREFIX}${INTERNAL_APP}`); const ProviderWrapper: React.FC = ({ children }) => { return ( - + {children}; ); @@ -111,7 +107,7 @@ describe('useLinkProps hook', () => { pathname: '/', }); expect(result.current.href).toBe('/test-basepath/s/test-space/app/ml/'); - expect(result.current.onClick).not.toBeDefined(); + expect(result.current.onClick).toBeDefined(); }); it('Provides the correct props with pathname options', () => { @@ -127,7 +123,7 @@ describe('useLinkProps hook', () => { expect(result.current.href).toBe( '/test-basepath/s/test-space/app/ml/explorer?type=host&id=some-id&count=12345' ); - expect(result.current.onClick).not.toBeDefined(); + expect(result.current.onClick).toBeDefined(); }); it('Provides the correct props with hash options', () => { @@ -143,7 +139,7 @@ describe('useLinkProps hook', () => { expect(result.current.href).toBe( '/test-basepath/s/test-space/app/ml#/explorer?type=host&id=some-id&count=12345' ); - expect(result.current.onClick).not.toBeDefined(); + expect(result.current.onClick).toBeDefined(); }); it('Provides the correct props with more complex encoding', () => { @@ -161,7 +157,7 @@ describe('useLinkProps hook', () => { expect(result.current.href).toBe( '/test-basepath/s/test-space/app/ml#/explorer?type=host%20%2B%20host&name=this%20name%20has%20spaces%20and%20**%20and%20%25&id=some-id&count=12345&animals=dog,cat,bear' ); - expect(result.current.onClick).not.toBeDefined(); + expect(result.current.onClick).toBeDefined(); }); it('Provides the correct props with a consumer using Rison encoding for search', () => { @@ -180,7 +176,7 @@ describe('useLinkProps hook', () => { expect(result.current.href).toBe( '/test-basepath/s/test-space/app/rison-app#rison-route?type=host%20%2B%20host&state=(refreshInterval:(pause:!t,value:0),time:(from:12345,to:54321))' ); - expect(result.current.onClick).not.toBeDefined(); + expect(result.current.onClick).toBeDefined(); }); }); }); diff --git a/x-pack/plugins/infra/public/hooks/use_link_props.tsx b/x-pack/plugins/infra/public/hooks/use_link_props.tsx index e60ab32046832..8c522bb7fa764 100644 --- a/x-pack/plugins/infra/public/hooks/use_link_props.tsx +++ b/x-pack/plugins/infra/public/hooks/use_link_props.tsx @@ -9,7 +9,8 @@ import { stringify } from 'query-string'; import url from 'url'; import { url as urlUtils } from '../../../../../src/plugins/kibana_utils/public'; import { usePrefixPathWithBasepath } from './use_prefix_path_with_basepath'; -import { useHistory } from '../utils/history_context'; +import { useKibana } from '../../../../../src/plugins/kibana_react/public'; +import { useNavigationWarningPrompt } from '../utils/navigation_warning_prompt'; type Search = Record; @@ -28,31 +29,26 @@ interface LinkProps { export const useLinkProps = ({ app, pathname, hash, search }: LinkDescriptor): LinkProps => { validateParams({ app, pathname, hash, search }); - const history = useHistory(); + const { prompt } = useNavigationWarningPrompt(); const prefixer = usePrefixPathWithBasepath(); + const navigateToApp = useKibana().services.application?.navigateToApp; const encodedSearch = useMemo(() => { return search ? encodeSearch(search) : undefined; }, [search]); - const internalLinkResult = useMemo(() => { - // When the logs / metrics apps are first mounted a history instance is setup with a 'basename' equal to the - // 'appBasePath' received from Core's 'AppMountParams', e.g. /BASE_PATH/s/SPACE_ID/app/APP_ID. With internal - // linking we are using 'createHref' and 'push' on top of this history instance. So a pathname of /inventory used within - // the metrics app will ultimatey end up as /BASE_PATH/s/SPACE_ID/app/metrics/inventory. React-router responds to this - // as it is instantiated with the same history instance. - return history?.createHref({ - pathname: pathname ? formatPathname(pathname) : undefined, - search: encodedSearch, - }); - }, [history, pathname, encodedSearch]); - - const externalLinkResult = useMemo(() => { + const mergedHash = useMemo(() => { // The URI spec defines that the query should appear before the fragment // https://tools.ietf.org/html/rfc3986#section-3 (e.g. url.format()). However, in Kibana, apps that use // hash based routing expect the query to be part of the hash. This will handle that. - const mergedHash = hash && encodedSearch ? `${hash}?${encodedSearch}` : hash; + return hash && encodedSearch ? `${hash}?${encodedSearch}` : hash; + }, [hash, encodedSearch]); + + const mergedPathname = useMemo(() => { + return pathname && encodedSearch ? `${pathname}?${encodedSearch}` : pathname; + }, [pathname, encodedSearch]); + const href = useMemo(() => { const link = url.format({ pathname, hash: mergedHash, @@ -60,28 +56,36 @@ export const useLinkProps = ({ app, pathname, hash, search }: LinkDescriptor): L }); return prefixer(app, link); - }, [hash, encodedSearch, pathname, prefixer, app]); + }, [mergedHash, hash, encodedSearch, pathname, prefixer, app]); const onClick = useMemo(() => { - // If these results are equal we know we're trying to navigate within the same application - // that the current history instance is representing - if (internalLinkResult && linksAreEquivalent(externalLinkResult, internalLinkResult)) { - return (e: React.MouseEvent | React.MouseEvent) => { - e.preventDefault(); - if (history) { - history.push({ - pathname: pathname ? formatPathname(pathname) : undefined, - search: encodedSearch, - }); + return (e: React.MouseEvent | React.MouseEvent) => { + e.preventDefault(); + + const navigate = () => { + if (navigateToApp) { + const navigationPath = mergedHash ? `#${mergedHash}` : mergedPathname; + navigateToApp(app, { path: navigationPath ? navigationPath : undefined }); } }; - } else { - return undefined; - } - }, [internalLinkResult, externalLinkResult, history, pathname, encodedSearch]); + + // A component somewhere within the app hierarchy is requesting that we + // prompt the user before navigating. + if (prompt) { + const wantsToNavigate = window.confirm(prompt); + if (wantsToNavigate) { + navigate(); + } else { + return; + } + } else { + navigate(); + } + }; + }, [navigateToApp, mergedHash, mergedPathname, app, prompt]); return { - href: externalLinkResult, + href, onClick, }; }; @@ -90,10 +94,6 @@ const encodeSearch = (search: Search) => { return stringify(urlUtils.encodeQuery(search), { sort: false, encode: false }); }; -const formatPathname = (pathname: string) => { - return pathname[0] === '/' ? pathname : `/${pathname}`; -}; - const validateParams = ({ app, pathname, hash, search }: LinkDescriptor) => { if (!app && hash) { throw new Error( @@ -101,9 +101,3 @@ const validateParams = ({ app, pathname, hash, search }: LinkDescriptor) => { ); } }; - -const linksAreEquivalent = (externalLink: string, internalLink: string): boolean => { - // Compares with trailing slashes removed. This handles the case where the pathname is '/' - // and 'createHref' will include the '/' but Kibana's 'getUrlForApp' will remove it. - return externalLink.replace(/\/$/, '') === internalLink.replace(/\/$/, ''); -}; diff --git a/x-pack/plugins/infra/public/utils/history_context.ts b/x-pack/plugins/infra/public/utils/history_context.ts index fe036e3179ec1..844d5b5e8e76f 100644 --- a/x-pack/plugins/infra/public/utils/history_context.ts +++ b/x-pack/plugins/infra/public/utils/history_context.ts @@ -5,9 +5,9 @@ */ import { createContext, useContext } from 'react'; -import { History } from 'history'; +import { ScopedHistory } from 'src/core/public'; -export const HistoryContext = createContext(undefined); +export const HistoryContext = createContext(undefined); export const useHistory = () => { return useContext(HistoryContext); diff --git a/x-pack/plugins/infra/public/utils/navigation_warning_prompt/context.tsx b/x-pack/plugins/infra/public/utils/navigation_warning_prompt/context.tsx new file mode 100644 index 0000000000000..10f8fb9e71f43 --- /dev/null +++ b/x-pack/plugins/infra/public/utils/navigation_warning_prompt/context.tsx @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useState } from 'react'; +import { createContext, useContext } from 'react'; + +interface ContextValues { + prompt?: string; + setPrompt: (prompt: string | undefined) => void; +} + +export const NavigationWarningPromptContext = createContext({ + setPrompt: (prompt: string | undefined) => {}, +}); + +export const useNavigationWarningPrompt = () => { + return useContext(NavigationWarningPromptContext); +}; + +export const NavigationWarningPromptProvider: React.FC = ({ children }) => { + const [prompt, setPrompt] = useState(undefined); + + return ( + + {children} + + ); +}; diff --git a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/lib/is_es_error_factory/index.ts b/x-pack/plugins/infra/public/utils/navigation_warning_prompt/index.ts similarity index 80% rename from x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/lib/is_es_error_factory/index.ts rename to x-pack/plugins/infra/public/utils/navigation_warning_prompt/index.ts index 441648a8701e0..dcdbf8e912a83 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/lib/is_es_error_factory/index.ts +++ b/x-pack/plugins/infra/public/utils/navigation_warning_prompt/index.ts @@ -4,4 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ -export { isEsErrorFactory } from './is_es_error_factory'; +export * from './context'; +export * from './prompt'; diff --git a/x-pack/plugins/infra/public/utils/navigation_warning_prompt/prompt.tsx b/x-pack/plugins/infra/public/utils/navigation_warning_prompt/prompt.tsx new file mode 100644 index 0000000000000..65ec4729c036d --- /dev/null +++ b/x-pack/plugins/infra/public/utils/navigation_warning_prompt/prompt.tsx @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useEffect } from 'react'; +import { useNavigationWarningPrompt } from './context'; + +interface Props { + prompt?: string; +} + +export const Prompt: React.FC = ({ prompt }) => { + const { setPrompt } = useNavigationWarningPrompt(); + + useEffect(() => { + setPrompt(prompt); + return () => { + setPrompt(undefined); + }; + }, [prompt, setPrompt]); + + return null; +}; diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts index 000d0823311b3..a52659dae01f1 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts @@ -149,6 +149,14 @@ describe('The metric threshold alert type', () => { expect(mostRecentAction(instanceID)).toBe(undefined); expect(getState(instanceID).alertState).toBe(AlertStates.OK); }); + test('alerts as expected with the outside range comparator', async () => { + await execute(Comparator.OUTSIDE_RANGE, [0, 0.75]); + expect(mostRecentAction(instanceID).id).toBe(FIRED_ACTIONS.id); + expect(getState(instanceID).alertState).toBe(AlertStates.ALERT); + await execute(Comparator.OUTSIDE_RANGE, [0, 1.5]); + expect(mostRecentAction(instanceID)).toBe(undefined); + expect(getState(instanceID).alertState).toBe(AlertStates.OK); + }); test('reports expected values to the action context', async () => { await execute(Comparator.GT, [0.75]); const { action } = mostRecentAction(instanceID); diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts index c5ea65f7a4d1a..946f1c14bf593 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts @@ -11,7 +11,7 @@ import { InfraDatabaseSearchResponse } from '../../adapters/framework/adapter_ty import { createAfterKeyHandler } from '../../../utils/create_afterkey_handler'; import { getAllCompositeData } from '../../../utils/get_all_composite_data'; import { networkTraffic } from '../../../../common/inventory_models/shared/metrics/snapshot/network_traffic'; -import { MetricExpressionParams, Comparator, AlertStates } from './types'; +import { MetricExpressionParams, Comparator, Aggregators, AlertStates } from './types'; import { AlertServices, AlertExecutorOptions } from '../../../../../alerting/server'; import { getIntervalInSeconds } from '../../../utils/get_interval_in_seconds'; import { getDateHistogramOffset } from '../../snapshot/query_helpers'; @@ -39,7 +39,7 @@ const getCurrentValueFromAggregations = ( const { buckets } = aggregations.aggregatedIntervals; if (!buckets.length) return null; // No Data state const mostRecentBucket = buckets[buckets.length - 1]; - if (aggType === 'count') { + if (aggType === Aggregators.COUNT) { return mostRecentBucket.doc_count; } const { value } = mostRecentBucket.aggregatedValue; @@ -70,10 +70,10 @@ export const getElasticsearchMetricQuery = ( groupBy?: string, filterQuery?: string ) => { - if (aggType === 'count' && metric) { + if (aggType === Aggregators.COUNT && metric) { throw new Error('Cannot aggregate document count with a metric'); } - if (aggType !== 'count' && !metric) { + if (aggType !== Aggregators.COUNT && !metric) { throw new Error('Can only aggregate without a metric if using the document count aggregator'); } const interval = `${timeSize}${timeUnit}`; @@ -85,9 +85,9 @@ export const getElasticsearchMetricQuery = ( const offset = getDateHistogramOffset(from, interval); const aggregations = - aggType === 'count' + aggType === Aggregators.COUNT ? {} - : aggType === 'rate' + : aggType === Aggregators.RATE ? networkTraffic('aggregatedValue', metric) : { aggregatedValue: { @@ -242,7 +242,8 @@ const getMetric: ( const comparatorMap = { [Comparator.BETWEEN]: (value: number, [a, b]: number[]) => value >= Math.min(a, b) && value <= Math.max(a, b), - // `threshold` is always an array of numbers in case the BETWEEN comparator is + [Comparator.OUTSIDE_RANGE]: (value: number, [a, b]: number[]) => value < a || value > b, + // `threshold` is always an array of numbers in case the BETWEEN/OUTSIDE_RANGE comparator is // used; all other compartors will just destructure the first value in the array [Comparator.GT]: (a: number, [b]: number[]) => a > b, [Comparator.LT]: (a: number, [b]: number[]) => a < b, diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_alert_type.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_alert_type.ts index 8808219cabaa7..b697af4fa4c3b 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_alert_type.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_alert_type.ts @@ -7,8 +7,15 @@ import { i18n } from '@kbn/i18n'; import uuid from 'uuid'; import { schema } from '@kbn/config-schema'; import { PluginSetupContract } from '../../../../../alerting/server'; +import { METRIC_EXPLORER_AGGREGATIONS } from '../../../../common/http_api/metrics_explorer'; import { createMetricThresholdExecutor, FIRED_ACTIONS } from './metric_threshold_executor'; -import { METRIC_THRESHOLD_ALERT_TYPE_ID } from './types'; +import { METRIC_THRESHOLD_ALERT_TYPE_ID, Comparator } from './types'; + +const oneOfLiterals = (arrayOfLiterals: Readonly) => + schema.string({ + validate: value => + arrayOfLiterals.includes(value) ? undefined : `must be one of ${arrayOfLiterals.join(' | ')}`, + }); export async function registerMetricThresholdAlertType(alertingPlugin: PluginSetupContract) { if (!alertingPlugin) { @@ -20,13 +27,7 @@ export async function registerMetricThresholdAlertType(alertingPlugin: PluginSet const baseCriterion = { threshold: schema.arrayOf(schema.number()), - comparator: schema.oneOf([ - schema.literal('>'), - schema.literal('<'), - schema.literal('>='), - schema.literal('<='), - schema.literal('between'), - ]), + comparator: oneOfLiterals(Object.values(Comparator)), timeUnit: schema.string(), timeSize: schema.number(), }; @@ -34,13 +35,7 @@ export async function registerMetricThresholdAlertType(alertingPlugin: PluginSet const nonCountCriterion = schema.object({ ...baseCriterion, metric: schema.string(), - aggType: schema.oneOf([ - schema.literal('avg'), - schema.literal('min'), - schema.literal('max'), - schema.literal('rate'), - schema.literal('cardinality'), - ]), + aggType: oneOfLiterals(METRIC_EXPLORER_AGGREGATIONS), }); const countCriterion = schema.object({ diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/types.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/types.ts index abed691f109c0..18f5503fe2c9e 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/types.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/types.ts @@ -4,8 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { MetricsExplorerAggregation } from '../../../../common/http_api/metrics_explorer'; - export const METRIC_THRESHOLD_ALERT_TYPE_ID = 'metrics.alert.threshold'; export enum Comparator { @@ -14,6 +12,17 @@ export enum Comparator { GT_OR_EQ = '>=', LT_OR_EQ = '<=', BETWEEN = 'between', + OUTSIDE_RANGE = 'outside', +} + +export enum Aggregators { + COUNT = 'count', + AVERAGE = 'avg', + SUM = 'sum', + MIN = 'min', + MAX = 'max', + RATE = 'rate', + CARDINALITY = 'cardinality', } export enum AlertStates { @@ -34,7 +43,7 @@ interface BaseMetricExpressionParams { } interface NonCountMetricExpressionParams extends BaseMetricExpressionParams { - aggType: Exclude; + aggType: Exclude; metric: string; } diff --git a/x-pack/plugins/ingest_manager/common/constants/routes.ts b/x-pack/plugins/ingest_manager/common/constants/routes.ts index a31d38a723c2c..616195b32f266 100644 --- a/x-pack/plugins/ingest_manager/common/constants/routes.ts +++ b/x-pack/plugins/ingest_manager/common/constants/routes.ts @@ -53,7 +53,8 @@ export const AGENT_API_ROUTES = { ACKS_PATTERN: `${FLEET_API_ROOT}/agents/{agentId}/acks`, ACTIONS_PATTERN: `${FLEET_API_ROOT}/agents/{agentId}/actions`, ENROLL_PATTERN: `${FLEET_API_ROOT}/agents/enroll`, - UNENROLL_PATTERN: `${FLEET_API_ROOT}/agents/unenroll`, + UNENROLL_PATTERN: `${FLEET_API_ROOT}/agents/{agentId}/unenroll`, + REASSIGN_PATTERN: `${FLEET_API_ROOT}/agents/{agentId}/reassign`, STATUS_PATTERN: `${FLEET_API_ROOT}/agent-status`, }; diff --git a/x-pack/plugins/ingest_manager/common/services/routes.ts b/x-pack/plugins/ingest_manager/common/services/routes.ts index 7cc6fc3c66afb..f2343b1039151 100644 --- a/x-pack/plugins/ingest_manager/common/services/routes.ts +++ b/x-pack/plugins/ingest_manager/common/services/routes.ts @@ -97,7 +97,10 @@ export const agentRouteService = { getInfoPath: (agentId: string) => AGENT_API_ROUTES.INFO_PATTERN.replace('{agentId}', agentId), getUpdatePath: (agentId: string) => AGENT_API_ROUTES.UPDATE_PATTERN.replace('{agentId}', agentId), getEventsPath: (agentId: string) => AGENT_API_ROUTES.EVENTS_PATTERN.replace('{agentId}', agentId), - getUnenrollPath: () => AGENT_API_ROUTES.UNENROLL_PATTERN, + getUnenrollPath: (agentId: string) => + AGENT_API_ROUTES.UNENROLL_PATTERN.replace('{agentId}', agentId), + getReassignPath: (agentId: string) => + AGENT_API_ROUTES.REASSIGN_PATTERN.replace('{agentId}', agentId), getListPath: () => AGENT_API_ROUTES.LIST_PATTERN, getStatusPath: () => AGENT_API_ROUTES.STATUS_PATTERN, }; diff --git a/x-pack/plugins/ingest_manager/common/types/index.ts b/x-pack/plugins/ingest_manager/common/types/index.ts index 150a4c9d60280..438db9a25b8ee 100644 --- a/x-pack/plugins/ingest_manager/common/types/index.ts +++ b/x-pack/plugins/ingest_manager/common/types/index.ts @@ -9,6 +9,25 @@ import { AgentStatus } from './models'; export * from './models'; export * from './rest_spec'; +/** + * Service to return the index pattern of EPM packages + */ +export interface ESIndexPatternService { + getESIndexPattern( + savedObjectsClient: SavedObjectsClientContract, + pkgName: string, + datasetPath: string + ): Promise; +} + +/** + * Describes public IngestManager plugin contract returned at the `startup` stage. + */ +export interface IngestManagerStartupContract { + esIndexPatternService: ESIndexPatternService; + agentService: AgentService; +} + /** * A service that provides exported functions that return information about an Agent */ diff --git a/x-pack/plugins/ingest_manager/common/types/models/agent.ts b/x-pack/plugins/ingest_manager/common/types/models/agent.ts index 4d03a30f9a590..14b2b2e47d17f 100644 --- a/x-pack/plugins/ingest_manager/common/types/models/agent.ts +++ b/x-pack/plugins/ingest_manager/common/types/models/agent.ts @@ -65,7 +65,7 @@ interface AgentBase { access_api_key_id?: string; default_api_key?: string; config_id?: string; - config_revision?: number; + config_revision?: number | null; config_newest_revision?: number; last_checkin?: string; } diff --git a/x-pack/plugins/ingest_manager/common/types/models/epm.ts b/x-pack/plugins/ingest_manager/common/types/models/epm.ts index a13e1655d5666..c750aa99204fa 100644 --- a/x-pack/plugins/ingest_manager/common/types/models/epm.ts +++ b/x-pack/plugins/ingest_manager/common/types/models/epm.ts @@ -57,6 +57,7 @@ export interface RegistryPackage { icons?: RegistryImage[]; assets?: string[]; internal?: boolean; + removable?: boolean; format_version: string; datasets?: Dataset[]; datasources?: RegistryDatasource[]; diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/agent.ts b/x-pack/plugins/ingest_manager/common/types/rest_spec/agent.ts index 21ab41740ce3e..64ed95db74f4c 100644 --- a/x-pack/plugins/ingest_manager/common/types/rest_spec/agent.ts +++ b/x-pack/plugins/ingest_manager/common/types/rest_spec/agent.ts @@ -96,16 +96,23 @@ export interface PostNewAgentActionResponse { } export interface PostAgentUnenrollRequest { - body: { kuery: string } | { ids: string[] }; + params: { + agentId: string; + }; } export interface PostAgentUnenrollResponse { - results: Array<{ - success: boolean; - error?: any; - id: string; - action: string; - }>; + success: boolean; +} + +export interface PutAgentReassignRequest { + params: { + agentId: string; + }; + body: { config_id: string }; +} + +export interface PutAgentReassignResponse { success: boolean; } diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/agents.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/agents.ts index f08b950e71ea8..453bcf2bd81e7 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/agents.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/agents.ts @@ -10,6 +10,8 @@ import { GetOneAgentResponse, GetOneAgentEventsResponse, GetOneAgentEventsRequest, + PutAgentReassignRequest, + PutAgentReassignResponse, GetAgentsRequest, GetAgentsResponse, GetAgentStatusRequest, @@ -59,3 +61,16 @@ export function sendGetAgentStatus( ...options, }); } + +export function sendPutAgentReassign( + agentId: string, + body: PutAgentReassignRequest['body'], + options?: RequestOptions +) { + return sendRequest({ + method: 'put', + path: agentRouteService.getReassignPath(agentId), + body, + ...options, + }); +} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/content.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/content.tsx index 0d4b395895322..a3d24e7806f34 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/content.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/content.tsx @@ -50,10 +50,18 @@ export function Content(props: ContentProps) { type ContentPanelProps = PackageInfo & Pick; export function ContentPanel(props: ContentPanelProps) { - const { panel, name, version, assets, title } = props; + const { panel, name, version, assets, title, removable } = props; switch (panel) { case 'settings': - return ; + return ( + + ); case 'data-sources': return ; case 'overview': diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/settings_panel.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/settings_panel.tsx index ff7ecf97714b6..f947466caf4b0 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/settings_panel.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/screens/detail/settings_panel.tsx @@ -13,8 +13,14 @@ import { InstallStatus, PackageInfo } from '../../../../types'; import { InstallationButton } from './installation_button'; import { useGetDatasources } from '../../../../hooks'; +const NoteLabel = () => ( + +); export const SettingsPanel = ( - props: Pick + props: Pick ) => { const getPackageInstallStatus = useGetPackageInstallStatus(); const { data: datasourcesData } = useGetDatasources({ @@ -22,10 +28,9 @@ export const SettingsPanel = ( page: 1, kuery: `datasources.package.name:${props.name}`, }); - const { name, title } = props; + const { name, title, removable } = props; const packageInstallStatus = getPackageInstallStatus(name); const packageHasDatasources = !!datasourcesData?.total; - return ( @@ -89,12 +94,12 @@ export const SettingsPanel = (

    - {packageHasDatasources && ( + {packageHasDatasources && removable === true && (

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

    + )} + {removable === false && ( +

    + + ), }} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/details_section.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/details_section.tsx index 0844368dc214b..653e2eb9a3a3b 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/details_section.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_details_page/components/details_section.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useState, Fragment } from 'react'; +import React, { useState, Fragment, useCallback } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiTitle, @@ -13,10 +13,13 @@ import { EuiFlexItem, EuiDescriptionList, EuiButton, + EuiPopover, EuiDescriptionListTitle, EuiDescriptionListDescription, EuiButtonEmpty, EuiIconTip, + EuiContextMenuPanel, + EuiContextMenuItem, EuiTextColor, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -26,7 +29,7 @@ import { Agent } from '../../../../types'; import { AgentHealth } from '../../components/agent_health'; import { useCapabilities, useGetOneAgentConfig } from '../../../../hooks'; import { Loading } from '../../../../components'; -import { ConnectedLink } from '../../components'; +import { ConnectedLink, AgentReassignConfigFlyout } from '../../components'; import { AgentUnenrollProvider } from '../../components/agent_unenroll_provider'; const Item: React.FunctionComponent<{ label: string }> = ({ label, children }) => { @@ -56,6 +59,15 @@ export const AgentDetailSection: React.FunctionComponent = ({ agent }) => const hasWriteCapabilites = useCapabilities().write; const metadataFlyout = useFlyout(); const refreshAgent = useAgentRefresh(); + // Actions menu + const [isActionsPopoverOpen, setIsActionsPopoverOpen] = useState(false); + const handleCloseMenu = useCallback(() => setIsActionsPopoverOpen(false), [ + setIsActionsPopoverOpen, + ]); + const handleToggleMenu = useCallback(() => setIsActionsPopoverOpen(!isActionsPopoverOpen), [ + isActionsPopoverOpen, + ]); + const [isReassignFlyoutOpen, setIsReassignFlyoutOpen] = useState(false); // Fetch AgentConfig information const { isLoading: isAgentConfigLoading, data: agentConfigData } = useGetOneAgentConfig( @@ -111,6 +123,9 @@ export const AgentDetailSection: React.FunctionComponent = ({ agent }) => return ( <> + {isReassignFlyoutOpen && ( + setIsReassignFlyoutOpen(false)} /> + )} @@ -123,21 +138,55 @@ export const AgentDetailSection: React.FunctionComponent = ({ agent }) => - - {unenrollAgentsPrompt => ( - { - unenrollAgentsPrompt([agent.id], 1, refreshAgent); - }} - > + - )} - + } + isOpen={isActionsPopoverOpen} + closePopover={handleCloseMenu} + > + { + handleCloseMenu(); + setIsReassignFlyoutOpen(true); + }} + key="reassignConfig" + > + + , + + + {unenrollAgentsPrompt => ( + { + unenrollAgentsPrompt([agent.id], 1, refreshAgent); + }} + > + + + )} + , + ]} + /> + diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/index.tsx index d363c472f2305..c79255104a030 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/agent_list_page/index.tsx @@ -35,7 +35,7 @@ import { useUrlParams, useLink, } from '../../../hooks'; -import { ConnectedLink } from '../components'; +import { ConnectedLink, AgentReassignConfigFlyout } from '../components'; import { SearchBar } from '../../../components/search_bar'; import { AgentHealth } from '../components/agent_health'; import { AgentUnenrollProvider } from '../components/agent_unenroll_provider'; @@ -71,61 +71,76 @@ const statusFilters = [ }, ] as Array<{ label: string; status: string }>; -const RowActions = React.memo<{ agent: Agent; refresh: () => void }>(({ agent, refresh }) => { - const hasWriteCapabilites = useCapabilities().write; - const DETAILS_URI = useLink(FLEET_AGENT_DETAIL_PATH); - const [isOpen, setIsOpen] = useState(false); - const handleCloseMenu = useCallback(() => setIsOpen(false), [setIsOpen]); - const handleToggleMenu = useCallback(() => setIsOpen(!isOpen), [isOpen]); +const RowActions = React.memo<{ agent: Agent; onReassignClick: () => void; refresh: () => void }>( + ({ agent, refresh, onReassignClick }) => { + const hasWriteCapabilites = useCapabilities().write; + const DETAILS_URI = useLink(FLEET_AGENT_DETAIL_PATH); + const [isOpen, setIsOpen] = useState(false); + const handleCloseMenu = useCallback(() => setIsOpen(false), [setIsOpen]); + const handleToggleMenu = useCallback(() => setIsOpen(!isOpen), [isOpen]); - return ( - - } - isOpen={isOpen} - closePopover={handleCloseMenu} - > - - - , + return ( + + } + isOpen={isOpen} + closePopover={handleCloseMenu} + > + + + , + { + handleCloseMenu(); + onReassignClick(); + }} + key="reassignConfig" + > + + , - - {unenrollAgentsPrompt => ( - { - unenrollAgentsPrompt([agent.id], 1, () => { - refresh(); - }); - }} - > - - - )} - , - ]} - /> - - ); -}); + + {unenrollAgentsPrompt => ( + { + unenrollAgentsPrompt([agent.id], 1, () => { + refresh(); + }); + }} + > + + + )} + , + ]} + /> + + ); + } +); export const AgentListPage: React.FunctionComponent<{}> = () => { const defaultKuery: string = (useUrlParams().urlParams.kuery as string) || ''; @@ -136,8 +151,6 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { // Table and search states const [search, setSearch] = useState(defaultKuery); const { pagination, pageSizeOptions, setPagination } = usePagination(); - const [selectedAgents, setSelectedAgents] = useState([]); - const [areAllAgentsSelected, setAreAllAgentsSelected] = useState(false); // Configs state (for filtering) const [isConfigsFilterOpen, setIsConfigsFilterOpen] = useState(false); @@ -159,6 +172,9 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { // Agent enrollment flyout state const [isEnrollmentFlyoutOpen, setIsEnrollmentFlyoutOpen] = useState(false); + // Agent reassignment flyout state + const [agentToReassignId, setAgentToReassignId] = useState(undefined); + let kuery = search.trim(); if (selectedConfigs.length) { if (kuery) { @@ -227,47 +243,6 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { {host} ), - footer: () => { - if (selectedAgents.length === agents.length && totalAgents > selectedAgents.length) { - return areAllAgentsSelected ? ( - setAreAllAgentsSelected(false)}> - - - ), - }} - /> - ) : ( - setAreAllAgentsSelected(true)}> - - - ), - }} - /> - ); - } - return null; - }, }, { field: 'active', @@ -350,7 +325,13 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { actions: [ { render: (agent: Agent) => { - return agentsRequest.sendRequest()} />; + return ( + agentsRequest.sendRequest()} + onReassignClick={() => setAgentToReassignId(agent.id)} + /> + ); }, }, ], @@ -381,6 +362,8 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { /> ); + const agentToReassign = agentToReassignId && agents.find(a => a.id === agentToReassignId); + return ( <> {isEnrollmentFlyoutOpen ? ( @@ -389,48 +372,16 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { onClose={() => setIsEnrollmentFlyoutOpen(false)} /> ) : null} + {agentToReassign && ( + { + setAgentToReassignId(undefined); + agentsRequest.sendRequest(); + }} + /> + )} - {selectedAgents.length ? ( - - - {unenrollAgentsPrompt => ( - { - unenrollAgentsPrompt( - areAllAgentsSelected ? search : selectedAgents.map(agent => agent.id), - areAllAgentsSelected ? totalAgents : selectedAgents.length, - () => { - // Reload agents if on first page and no search query, otherwise - // reset to first page and reset search, which will trigger a reload - if (pagination.currentPage === 1 && !search) { - agentsRequest.sendRequest(); - } else { - setPagination({ - ...pagination, - currentPage: 1, - }); - setSearch(''); - } - - setAreAllAgentsSelected(false); - setSelectedAgents([]); - } - ); - }} - > - - - )} - - - ) : null} @@ -575,14 +526,6 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { items={totalAgents ? agents : []} itemId="id" columns={columns} - isSelectable={true} - selection={{ - selectable: (agent: Agent) => agent.active, - onSelectionChange: (newSelectedAgents: Agent[]) => { - setSelectedAgents(newSelectedAgents); - setAreAllAgentsSelected(false); - }, - }} pagination={{ pageIndex: pagination.currentPage - 1, pageSize: pagination.pageSize, diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_reassign_config_flyout/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_reassign_config_flyout/index.tsx new file mode 100644 index 0000000000000..11a049047b787 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_reassign_config_flyout/index.tsx @@ -0,0 +1,186 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React, { useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutHeader, + EuiSpacer, + EuiTitle, + EuiFlexGroup, + EuiFlexItem, + EuiButtonEmpty, + EuiButton, + EuiFlyoutFooter, + EuiSelect, + EuiFormRow, + EuiText, + EuiBadge, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { Datasource, Agent } from '../../../../types'; +import { + useGetOneAgentConfig, + sendPutAgentReassign, + useCore, + useGetAgentConfigs, +} from '../../../../hooks'; +import { PackageIcon } from '../../../../components/package_icon'; + +interface Props { + onClose: () => void; + agent: Agent; +} + +export const AgentReassignConfigFlyout: React.FunctionComponent = ({ onClose, agent }) => { + const { notifications } = useCore(); + const [selectedAgentConfigId, setSelectedAgentConfigId] = useState( + agent.config_id + ); + + const agentConfigsRequest = useGetAgentConfigs(); + const agentConfigs = agentConfigsRequest.data ? agentConfigsRequest.data.items : []; + + const agentConfigRequest = useGetOneAgentConfig(selectedAgentConfigId as string); + const agentConfig = agentConfigRequest.data ? agentConfigRequest.data.item : null; + + const [isSubmitting, setIsSubmitting] = useState(false); + + async function onSubmit() { + try { + setIsSubmitting(true); + if (!selectedAgentConfigId) { + throw new Error('No selected config id'); + } + const res = await sendPutAgentReassign(agent.id, { + config_id: selectedAgentConfigId, + }); + if (res.error) { + throw res.error; + } + setIsSubmitting(false); + const successMessage = i18n.translate( + 'xpack.ingestManager.agentReassignConfig.successSingleNotificationTitle', + { + defaultMessage: 'Agent configuration reassigned', + } + ); + notifications.toasts.addSuccess(successMessage); + onClose(); + } catch (error) { + setIsSubmitting(false); + notifications.toasts.addError(error, { + title: 'Unable to reassign agent configuration', + }); + } + } + + return ( + + + +

    + +

    +
    + + + + + + + + + + ({ + value: config.id, + text: config.name, + }))} + value={selectedAgentConfigId} + onChange={e => setSelectedAgentConfigId(e.target.value)} + /> + + + + + + {agentConfig && ( + + {agentConfig.datasources.length}, + }} + /> + + )} + + {agentConfig && + (agentConfig.datasources as Datasource[]).map((datasource, idx) => { + if (!datasource.package) { + return null; + } + return ( + + + + + + {datasource.package.title} + + + ); + })} + + + + + + + + + + + + + + + +
    + ); +}; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_unenroll_provider.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_unenroll_provider.tsx index 25499495a7897..fec2253c0dd56 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_unenroll_provider.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/agent_unenroll_provider.tsx @@ -39,7 +39,8 @@ export const AgentUnenrollProvider: React.FunctionComponent = ({ children ) => { if ( agentsToUnenroll === undefined || - (Array.isArray(agentsToUnenroll) && agentsToUnenroll.length === 0) + // !Only supports unenrolling one agent + (Array.isArray(agentsToUnenroll) && agentsToUnenroll.length !== 1) ) { throw new Error('No agents specified for unenrollment'); } @@ -60,55 +61,27 @@ export const AgentUnenrollProvider: React.FunctionComponent = ({ children setIsLoading(true); try { - const unenrollByKuery = typeof agents === 'string'; - const { data, error } = await sendRequest({ - path: agentRouteService.getUnenrollPath(), + const agentId = agents[0]; + const { error } = await sendRequest({ + path: agentRouteService.getUnenrollPath(agentId), method: 'post', - body: JSON.stringify({ - kuery: unenrollByKuery ? agents : undefined, - ids: !unenrollByKuery ? agents : undefined, - }), }); if (error) { throw new Error(error.message); } - const results = data ? data.results : []; - - const successfulResults = results.filter(result => result.success); - const failedResults = results.filter(result => !result.success); - - if (successfulResults.length) { - const hasMultipleSuccesses = successfulResults.length > 1; - const successMessage = hasMultipleSuccesses - ? i18n.translate('xpack.ingestManager.unenrollAgents.successMultipleNotificationTitle', { - defaultMessage: 'Unenrolled {count} agents', - values: { count: successfulResults.length }, - }) - : i18n.translate('xpack.ingestManager.unenrollAgents.successSingleNotificationTitle', { - defaultMessage: "Unenrolled agent '{id}'", - values: { id: successfulResults[0].id }, - }); - core.notifications.toasts.addSuccess(successMessage); - } - - if (failedResults.length) { - const hasMultipleFailures = failedResults.length > 1; - const failureMessage = hasMultipleFailures - ? i18n.translate('xpack.ingestManager.unenrollAgents.failureMultipleNotificationTitle', { - defaultMessage: 'Error unenrolling {count} agents', - values: { count: failedResults.length }, - }) - : i18n.translate('xpack.ingestManager.unenrollAgents.failureSingleNotificationTitle', { - defaultMessage: "Error unenrolling agent '{id}'", - values: { id: failedResults[0].id }, - }); - core.notifications.toasts.addDanger(failureMessage); - } + const successMessage = i18n.translate( + 'xpack.ingestManager.unenrollAgents.successSingleNotificationTitle', + { + defaultMessage: "Unenrolled agent '{id}'", + values: { id: agentId }, + } + ); + core.notifications.toasts.addSuccess(successMessage); if (onSuccessCallback.current) { - onSuccessCallback.current(successfulResults.map(result => result.id)); + onSuccessCallback.current([agentId]); } } catch (e) { core.notifications.toasts.addDanger( diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/index.tsx index 19378fe2fb952..a0092f4073e5a 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/components/index.tsx @@ -5,5 +5,6 @@ */ export * from './loading'; +export * from './agent_reassign_config_flyout'; export * from './navigation/child_routes'; export * from './navigation/connected_link'; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts index 32615278b67d7..75194d3397f90 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts @@ -39,6 +39,8 @@ export { GetOneAgentEventsResponse, GetAgentStatusRequest, GetAgentStatusResponse, + PutAgentReassignRequest, + PutAgentReassignResponse, // API schemas - Enrollment API Keys GetEnrollmentAPIKeysResponse, GetEnrollmentAPIKeysRequest, diff --git a/x-pack/plugins/ingest_manager/server/index.ts b/x-pack/plugins/ingest_manager/server/index.ts index d99eb2a9bb4bb..0f49de0d5fdd1 100644 --- a/x-pack/plugins/ingest_manager/server/index.ts +++ b/x-pack/plugins/ingest_manager/server/index.ts @@ -7,9 +7,6 @@ import { schema, TypeOf } from '@kbn/config-schema'; import { PluginInitializerContext } from 'src/core/server'; import { IngestManagerPlugin } from './plugin'; -export { ESIndexPatternService } from './services'; -export { IngestManagerSetupContract } from './plugin'; - export const config = { exposeToBrowser: { epm: true, diff --git a/x-pack/plugins/ingest_manager/server/plugin.ts b/x-pack/plugins/ingest_manager/server/plugin.ts index 075a0917b9fae..80e35eadb4603 100644 --- a/x-pack/plugins/ingest_manager/server/plugin.ts +++ b/x-pack/plugins/ingest_manager/server/plugin.ts @@ -39,22 +39,10 @@ import { registerInstallScriptRoutes, } from './routes'; -import { AgentService, IngestManagerConfigType } from '../common'; -import { - appContextService, - ESIndexPatternService, - ESIndexPatternSavedObjectService, -} from './services'; +import { IngestManagerConfigType, IngestManagerStartupContract } from '../common'; +import { appContextService, ESIndexPatternSavedObjectService } from './services'; import { getAgentStatusById } from './services/agents'; -/** - * Describes public IngestManager plugin contract returned at the `setup` stage. - */ -export interface IngestManagerSetupContract { - esIndexPatternService: ESIndexPatternService; - agentService: AgentService; -} - export interface IngestManagerSetupDeps { licensing: LicensingPluginSetup; security?: SecurityPluginSetup; @@ -78,7 +66,7 @@ const allSavedObjectTypes = [ ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE, ]; -export class IngestManagerPlugin implements Plugin { +export class IngestManagerPlugin implements Plugin { private config$: Observable; private security: SecurityPluginSetup | undefined; @@ -86,10 +74,7 @@ export class IngestManagerPlugin implements Plugin { this.config$ = this.initializerContext.config.create(); } - public async setup( - core: CoreSetup, - deps: IngestManagerSetupDeps - ): Promise> { + public async setup(core: CoreSetup, deps: IngestManagerSetupDeps) { if (deps.security) { this.security = deps.security; } @@ -148,12 +133,6 @@ export class IngestManagerPlugin implements Plugin { basePath: core.http.basePath, }); } - return deepFreeze({ - esIndexPatternService: new ESIndexPatternSavedObjectService(), - agentService: { - getAgentStatusById, - }, - }); } public async start( @@ -161,13 +140,19 @@ export class IngestManagerPlugin implements Plugin { plugins: { encryptedSavedObjects: EncryptedSavedObjectsPluginStart; } - ) { + ): Promise> { appContextService.start({ encryptedSavedObjects: plugins.encryptedSavedObjects, security: this.security, config$: this.config$, savedObjects: core.savedObjects, }); + return deepFreeze({ + esIndexPatternService: new ESIndexPatternSavedObjectService(), + agentService: { + getAgentStatusById, + }, + }); } public async stop() { diff --git a/x-pack/plugins/ingest_manager/server/routes/agent/handlers.ts b/x-pack/plugins/ingest_manager/server/routes/agent/handlers.ts index 89c827abe30ec..5820303e2a1a7 100644 --- a/x-pack/plugins/ingest_manager/server/routes/agent/handlers.ts +++ b/x-pack/plugins/ingest_manager/server/routes/agent/handlers.ts @@ -14,6 +14,7 @@ import { PostAgentEnrollResponse, PostAgentUnenrollResponse, GetAgentStatusResponse, + PutAgentReassignResponse, } from '../../../common/types'; import { GetAgentsRequestSchema, @@ -25,6 +26,7 @@ import { PostAgentEnrollRequestSchema, PostAgentUnenrollRequestSchema, GetAgentStatusRequestSchema, + PutAgentReassignRequestSchema, } from '../../types'; import * as AgentService from '../../services/agents'; import * as APIKeyService from '../../services/api_keys'; @@ -293,60 +295,36 @@ export const getAgentsHandler: RequestHandler< } }; -export const postAgentsUnenrollHandler: RequestHandler< - undefined, +export const postAgentsUnenrollHandler: RequestHandler> = async (context, request, response) => { + const soClient = context.core.savedObjects.client; + try { + await AgentService.unenrollAgent(soClient, request.params.agentId); + + const body: PostAgentUnenrollResponse = { + success: true, + }; + return response.ok({ body }); + } catch (e) { + return response.customError({ + statusCode: 500, + body: { message: e.message }, + }); + } +}; + +export const putAgentsReassignHandler: RequestHandler< + TypeOf, undefined, - TypeOf + TypeOf > = async (context, request, response) => { const soClient = context.core.savedObjects.client; try { - const kuery = (request.body as { kuery: string }).kuery; - let toUnenrollIds: string[] = (request.body as { ids: string[] }).ids || []; - - if (kuery) { - let hasMore = true; - let page = 1; - while (hasMore) { - const { agents } = await AgentService.listAgents(soClient, { - page: page++, - perPage: 100, - kuery, - showInactive: true, - }); - if (agents.length === 0) { - hasMore = false; - } - const agentIds = agents.filter(a => a.active).map(a => a.id); - toUnenrollIds = toUnenrollIds.concat(agentIds); - } - } - const results = (await AgentService.unenrollAgents(soClient, toUnenrollIds)).map( - ({ - success, - id, - error, - }): { - success: boolean; - id: string; - action: 'unenrolled'; - error?: { - message: string; - }; - } => { - return { - success, - id, - action: 'unenrolled', - error: error && { - message: error.message, - }, - }; - } - ); + await AgentService.reassignAgent(soClient, request.params.agentId, request.body.config_id); - const body: PostAgentUnenrollResponse = { - results, - success: results.every(result => result.success), + const body: PutAgentReassignResponse = { + success: true, }; return response.ok({ body }); } catch (e) { diff --git a/x-pack/plugins/ingest_manager/server/routes/agent/index.ts b/x-pack/plugins/ingest_manager/server/routes/agent/index.ts index ac27e47db155e..78bb178dce402 100644 --- a/x-pack/plugins/ingest_manager/server/routes/agent/index.ts +++ b/x-pack/plugins/ingest_manager/server/routes/agent/index.ts @@ -23,6 +23,7 @@ import { PostAgentUnenrollRequestSchema, GetAgentStatusRequestSchema, PostNewAgentActionRequestSchema, + PutAgentReassignRequestSchema, } from '../../types'; import { getAgentsHandler, @@ -35,6 +36,7 @@ import { postAgentsUnenrollHandler, getAgentStatusForConfigHandler, getInternalUserSOClient, + putAgentsReassignHandler, } from './handlers'; import { postAgentAcksHandlerBuilder } from './acks_handlers'; import * as AgentService from '../../services/agents'; @@ -135,6 +137,15 @@ export const registerRoutes = (router: IRouter) => { postAgentsUnenrollHandler ); + router.put( + { + path: AGENT_API_ROUTES.REASSIGN_PATTERN, + validate: PutAgentReassignRequestSchema, + options: { tags: [`access:${PLUGIN_ID}-all`] }, + }, + putAgentsReassignHandler + ); + // Get agent events router.get( { diff --git a/x-pack/plugins/ingest_manager/server/saved_objects.ts b/x-pack/plugins/ingest_manager/server/saved_objects.ts index dc0b4695603e4..0a7229b1f2807 100644 --- a/x-pack/plugins/ingest_manager/server/saved_objects.ts +++ b/x-pack/plugins/ingest_manager/server/saved_objects.ts @@ -150,6 +150,7 @@ export const savedObjectMappings = { name: { type: 'keyword' }, version: { type: 'keyword' }, internal: { type: 'boolean' }, + removable: { type: 'boolean' }, es_index_patterns: { dynamic: false, type: 'object', diff --git a/x-pack/plugins/ingest_manager/server/services/agents/checkin.test.ts b/x-pack/plugins/ingest_manager/server/services/agents/checkin.test.ts index d98052ea87e86..ec10ca6e77e05 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/checkin.test.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/checkin.test.ts @@ -118,5 +118,19 @@ describe('Agent checkin service', () => { expect(res).toBeTruthy(); }); + + it('should return true if this agent has no revision currently set', () => { + const res = shouldCreateConfigAction( + getAgent({ + config_id: 'config1', + last_checkin: '2018-01-02T00:00:00', + config_revision: null, + config_newest_revision: 2, + }), + [] + ); + + expect(res).toBeTruthy(); + }); }); }); diff --git a/x-pack/plugins/ingest_manager/server/services/agents/checkin.ts b/x-pack/plugins/ingest_manager/server/services/agents/checkin.ts index 9a2b3f22b9431..2873aad7f691a 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/checkin.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/checkin.ts @@ -156,9 +156,13 @@ export function shouldCreateConfigAction(agent: Agent, actions: AgentAction[]): } const isAgentConfigOutdated = - agent.config_revision && - agent.config_newest_revision && - agent.config_revision < agent.config_newest_revision; + // Config reassignment + (!agent.config_revision && agent.config_newest_revision) || + // new revision of a config + (agent.config_revision && + agent.config_newest_revision && + agent.config_revision < agent.config_newest_revision); + if (!isAgentConfigOutdated) { return false; } diff --git a/x-pack/plugins/ingest_manager/server/services/agents/index.ts b/x-pack/plugins/ingest_manager/server/services/agents/index.ts index c95c9ecc2a1d8..257091af0ebd0 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/index.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/index.ts @@ -13,3 +13,4 @@ export * from './status'; export * from './crud'; export * from './update'; export * from './actions'; +export * from './reassign'; diff --git a/x-pack/plugins/ingest_manager/server/services/agents/reassign.ts b/x-pack/plugins/ingest_manager/server/services/agents/reassign.ts new file mode 100644 index 0000000000000..f8142af376eb3 --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/services/agents/reassign.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { SavedObjectsClientContract } from 'kibana/server'; +import Boom from 'boom'; +import { AGENT_SAVED_OBJECT_TYPE } from '../../constants'; +import { AgentSOAttributes } from '../../types'; +import { agentConfigService } from '../agent_config'; + +export async function reassignAgent( + soClient: SavedObjectsClientContract, + agentId: string, + newConfigId: string +) { + const config = await agentConfigService.get(soClient, newConfigId); + if (!config) { + throw Boom.notFound(`Agent Configuration not found: ${newConfigId}`); + } + + await soClient.update(AGENT_SAVED_OBJECT_TYPE, agentId, { + config_id: newConfigId, + config_revision: null, + config_newest_revision: config.revision, + }); +} diff --git a/x-pack/plugins/ingest_manager/server/services/agents/unenroll.ts b/x-pack/plugins/ingest_manager/server/services/agents/unenroll.ts index 18af9fd4de73f..7f729b0b531ca 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/unenroll.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/unenroll.ts @@ -10,31 +10,7 @@ import { AGENT_SAVED_OBJECT_TYPE } from '../../constants'; import { getAgent } from './crud'; import * as APIKeyService from '../api_keys'; -export async function unenrollAgents( - soClient: SavedObjectsClientContract, - toUnenrollIds: string[] -) { - const response = []; - for (const id of toUnenrollIds) { - try { - await unenrollAgent(soClient, id); - response.push({ - id, - success: true, - }); - } catch (error) { - response.push({ - id, - error, - success: false, - }); - } - } - - return response; -} - -async function unenrollAgent(soClient: SavedObjectsClientContract, agentId: string) { +export async function unenrollAgent(soClient: SavedObjectsClientContract, agentId: string) { const agent = await getAgent(soClient, agentId); await Promise.all([ diff --git a/x-pack/plugins/ingest_manager/server/services/agents/update.ts b/x-pack/plugins/ingest_manager/server/services/agents/update.ts index 59d0ad31d1a64..948e518dff5b4 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/update.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/update.ts @@ -7,7 +7,7 @@ import { SavedObjectsClientContract } from 'src/core/server'; import { listAgents } from './crud'; import { AGENT_SAVED_OBJECT_TYPE } from '../../constants'; -import { unenrollAgents } from './unenroll'; +import { unenrollAgent } from './unenroll'; import { agentConfigService } from '../agent_config'; export async function updateAgentsForConfigId( @@ -55,9 +55,8 @@ export async function unenrollForConfigId(soClient: SavedObjectsClientContract, if (agents.length === 0) { hasMore = false; } - await unenrollAgents( - soClient, - agents.map(a => a.id) - ); + for (const agent of agents) { + await unenrollAgent(soClient, agent.id); + } } } diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts index f3bd49eab6038..06f3decdbbe6f 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/install.ts @@ -98,7 +98,7 @@ export async function installPackage(options: { const reinstall = pkgVersion === installedPkg?.attributes.version; const registryPackageInfo = await Registry.fetchInfo(pkgName, pkgVersion); - const { internal = false } = registryPackageInfo; + const { internal = false, removable = true } = registryPackageInfo; // delete the previous version's installation's SO kibana assets before installing new ones // in case some assets were removed in the new version @@ -170,6 +170,7 @@ export async function installPackage(options: { pkgName, pkgVersion, internal, + removable, toSaveAssetRefs, toSaveESIndexPatterns, }); @@ -200,6 +201,7 @@ export async function saveInstallationReferences(options: { pkgName: string; pkgVersion: string; internal: boolean; + removable: boolean; toSaveAssetRefs: AssetReference[]; toSaveESIndexPatterns: Record; }) { @@ -208,6 +210,7 @@ export async function saveInstallationReferences(options: { pkgName, pkgVersion, internal, + removable, toSaveAssetRefs, toSaveESIndexPatterns, } = options; @@ -220,6 +223,7 @@ export async function saveInstallationReferences(options: { name: pkgName, version: pkgVersion, internal, + removable, }, { id: pkgName, overwrite: true } ); diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/remove.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/remove.ts index ed7b7f3301327..498796438c6c8 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/remove.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/remove.ts @@ -20,7 +20,10 @@ export async function removeInstallation(options: { // TODO: the epm api should change to /name/version so we don't need to do this const [pkgName] = pkgkey.split('-'); const installation = await getInstallation({ savedObjectsClient, pkgName }); - const installedObjects = installation?.installed || []; + if (!installation) throw new Error('integration does not exist'); + if (installation.removable === false) + throw new Error(`The ${pkgName} integration is installed by default and cannot be removed`); + const installedObjects = installation.installed || []; // Delete the manager saved object with references to the asset objects // could also update with [] or some other state diff --git a/x-pack/plugins/ingest_manager/server/services/es_index_pattern.ts b/x-pack/plugins/ingest_manager/server/services/es_index_pattern.ts index 167e22873979c..e2c27cdacda2f 100644 --- a/x-pack/plugins/ingest_manager/server/services/es_index_pattern.ts +++ b/x-pack/plugins/ingest_manager/server/services/es_index_pattern.ts @@ -4,15 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ import { SavedObjectsClientContract } from 'kibana/server'; -import { getInstallation } from './epm/packages/get'; - -export interface ESIndexPatternService { - getESIndexPattern( - savedObjectsClient: SavedObjectsClientContract, - pkgName: string, - datasetPath: string - ): Promise; -} +import { getInstallation } from './epm/packages'; +import { ESIndexPatternService } from '../../common/types'; export class ESIndexPatternSavedObjectService implements ESIndexPatternService { public async getESIndexPattern( diff --git a/x-pack/plugins/ingest_manager/server/services/index.ts b/x-pack/plugins/ingest_manager/server/services/index.ts index d64f1b0c2b6fb..4dfc3cb58b733 100644 --- a/x-pack/plugins/ingest_manager/server/services/index.ts +++ b/x-pack/plugins/ingest_manager/server/services/index.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ export { appContextService } from './app_context'; -export { ESIndexPatternService, ESIndexPatternSavedObjectService } from './es_index_pattern'; +export { ESIndexPatternSavedObjectService } from './es_index_pattern'; // Saved object services export { datasourceService } from './datasource'; diff --git a/x-pack/plugins/ingest_manager/server/types/rest_spec/agent.ts b/x-pack/plugins/ingest_manager/server/types/rest_spec/agent.ts index f94c02ccee40b..ac1679101312e 100644 --- a/x-pack/plugins/ingest_manager/server/types/rest_spec/agent.ts +++ b/x-pack/plugins/ingest_manager/server/types/rest_spec/agent.ts @@ -62,14 +62,18 @@ export const PostNewAgentActionRequestSchema = { }; export const PostAgentUnenrollRequestSchema = { - body: schema.oneOf([ - schema.object({ - kuery: schema.string(), - }), - schema.object({ - ids: schema.arrayOf(schema.string()), - }), - ]), + params: schema.object({ + agentId: schema.string(), + }), +}; + +export const PutAgentReassignRequestSchema = { + params: schema.object({ + agentId: schema.string(), + }), + body: schema.object({ + config_id: schema.string(), + }), }; export const GetOneAgentEventsRequestSchema = { diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/_chart_switch.scss b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/_chart_switch.scss similarity index 100% rename from x-pack/plugins/lens/public/editor_frame_service/editor_frame/_chart_switch.scss rename to x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/_chart_switch.scss diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/_config_panel.scss b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/_config_panel.scss new file mode 100644 index 0000000000000..1965b51f97034 --- /dev/null +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/_config_panel.scss @@ -0,0 +1,7 @@ +.lnsConfigPanel__addLayerBtn { + color: transparentize($euiColorMediumShade, .3); + // Remove EuiButton's default shadow to make button more subtle + // sass-lint:disable-block no-important + box-shadow: none !important; + border: 1px dashed currentColor; +} diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/_dimension_popover.scss b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/_dimension_popover.scss new file mode 100644 index 0000000000000..254807d06d386 --- /dev/null +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/_dimension_popover.scss @@ -0,0 +1,9 @@ +.lnsDimensionPopover { + line-height: 0; + flex-grow: 1; +} + +.lnsDimensionPopover__trigger { + max-width: 100%; + display: block; +} diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/_index.scss b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/_index.scss new file mode 100644 index 0000000000000..8f09a358dd5e4 --- /dev/null +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/_index.scss @@ -0,0 +1,4 @@ +@import 'chart_switch'; +@import 'config_panel'; +@import 'dimension_popover'; +@import 'layer_panel'; diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/_config_panel_wrapper.scss b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/_layer_panel.scss similarity index 52% rename from x-pack/plugins/lens/public/editor_frame_service/editor_frame/_config_panel_wrapper.scss rename to x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/_layer_panel.scss index 62a7f6b023f31..3fbc42f9a25a0 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/_config_panel_wrapper.scss +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/_layer_panel.scss @@ -1,8 +1,8 @@ -.lnsConfigPanel__panel { +.lnsLayerPanel { margin-bottom: $euiSizeS; } -.lnsConfigPanel__row { +.lnsLayerPanel__row { background: $euiColorLightestShade; padding: $euiSizeS; border-radius: $euiBorderRadius; @@ -13,15 +13,7 @@ } } -.lnsConfigPanel__addLayerBtn { - color: transparentize($euiColorMediumShade, .3); - // Remove EuiButton's default shadow to make button more subtle - // sass-lint:disable-block no-important - box-shadow: none !important; - border: 1px dashed currentColor; -} - -.lnsConfigPanel__dimension { +.lnsLayerPanel__dimension { @include euiFontSizeS; background: lightOrDarkTheme($euiColorEmptyShade, $euiColorLightestShade); border-radius: $euiBorderRadius; @@ -31,12 +23,7 @@ overflow: hidden; } -.lnsConfigPanel__trigger { - max-width: 100%; - display: block; -} - -.lnsConfigPanel__triggerLink { +.lnsLayerPanel__triggerLink { padding: $euiSizeS; width: 100%; display: flex; @@ -44,7 +31,3 @@ min-height: $euiSizeXXL; } -.lnsConfigPanel__popover { - line-height: 0; - flex-grow: 1; -} diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/chart_switch.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/chart_switch.test.tsx similarity index 98% rename from x-pack/plugins/lens/public/editor_frame_service/editor_frame/chart_switch.test.tsx rename to x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/chart_switch.test.tsx index 6698c9e68b98c..3c61d270b1bcf 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/chart_switch.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/chart_switch.test.tsx @@ -5,13 +5,17 @@ */ import React from 'react'; -import { createMockVisualization, createMockFramePublicAPI, createMockDatasource } from '../mocks'; -import { mountWithIntl as mount } from 'test_utils/enzyme_helpers'; import { ReactWrapper } from 'enzyme'; +import { + createMockVisualization, + createMockFramePublicAPI, + createMockDatasource, +} from '../../mocks'; +import { EuiKeyPadMenuItem } from '@elastic/eui'; +import { mountWithIntl as mount } from 'test_utils/enzyme_helpers'; +import { Visualization, FramePublicAPI, DatasourcePublicAPI } from '../../../types'; +import { Action } from '../state_management'; import { ChartSwitch } from './chart_switch'; -import { Visualization, FramePublicAPI, DatasourcePublicAPI } from '../../types'; -import { EuiKeyPadMenuItemButton } from '@elastic/eui'; -import { Action } from './state_management'; describe('chart_switch', () => { function generateVisualization(id: string): jest.Mocked { @@ -129,7 +133,7 @@ describe('chart_switch', () => { function getMenuItem(subType: string, component: ReactWrapper) { showFlyout(component); return component - .find(EuiKeyPadMenuItemButton) + .find(EuiKeyPadMenuItem) .find(`[data-test-subj="lnsChartSwitchPopover_${subType}"]`) .first(); } diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/chart_switch.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/chart_switch.tsx similarity index 97% rename from x-pack/plugins/lens/public/editor_frame_service/editor_frame/chart_switch.tsx rename to x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/chart_switch.tsx index 5e2fced577724..1461449f3c1c8 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/chart_switch.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/chart_switch.tsx @@ -10,15 +10,15 @@ import { EuiPopover, EuiPopoverTitle, EuiKeyPadMenu, - EuiKeyPadMenuItemButton, + EuiKeyPadMenuItem, EuiButtonEmpty, } from '@elastic/eui'; import { flatten } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { Visualization, FramePublicAPI, Datasource } from '../../types'; -import { Action } from './state_management'; -import { getSuggestions, switchToSuggestion, Suggestion } from './suggestion_helpers'; -import { trackUiEvent } from '../../lens_ui_telemetry'; +import { Visualization, FramePublicAPI, Datasource } from '../../../types'; +import { Action } from '../state_management'; +import { getSuggestions, switchToSuggestion, Suggestion } from '../suggestion_helpers'; +import { trackUiEvent } from '../../../lens_ui_telemetry'; interface VisualizationSelection { visualizationId: string; @@ -215,7 +215,7 @@ export function ChartSwitch(props: Props) { {(visualizationTypes || []).map(v => ( - {v.label}} role="menuitem" @@ -238,7 +238,7 @@ export function ChartSwitch(props: Props) { betaBadgeIconType={v.selection.dataLoss !== 'nothing' ? 'alert' : undefined} > - + ))} diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx new file mode 100644 index 0000000000000..e5d3e93258c0a --- /dev/null +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx @@ -0,0 +1,177 @@ +/* + * 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, { useMemo, memo } from 'react'; +import { EuiFlexItem, EuiToolTip, EuiButton, EuiForm } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { Visualization } from '../../../types'; +import { ChartSwitch } from './chart_switch'; +import { LayerPanel } from './layer_panel'; +import { trackUiEvent } from '../../../lens_ui_telemetry'; +import { generateId } from '../../../id_generator'; +import { removeLayer, appendLayer } from './layer_actions'; +import { ConfigPanelWrapperProps } from './types'; + +export const ConfigPanelWrapper = memo(function ConfigPanelWrapper(props: ConfigPanelWrapperProps) { + const activeVisualization = props.visualizationMap[props.activeVisualizationId || '']; + const { visualizationState } = props; + + return ( + <> + + {activeVisualization && visualizationState && ( + + )} + + ); +}); + +function LayerPanels( + props: ConfigPanelWrapperProps & { + activeDatasourceId: string; + activeVisualization: Visualization; + } +) { + const { + framePublicAPI, + activeVisualization, + visualizationState, + dispatch, + activeDatasourceId, + datasourceMap, + } = props; + const setVisualizationState = useMemo( + () => (newState: unknown) => { + props.dispatch({ + type: 'UPDATE_VISUALIZATION_STATE', + visualizationId: activeVisualization.id, + newState, + clearStagedPreview: false, + }); + }, + [props.dispatch, activeVisualization] + ); + const updateDatasource = useMemo( + () => (datasourceId: string, newState: unknown) => { + props.dispatch({ + type: 'UPDATE_DATASOURCE_STATE', + updater: () => newState, + datasourceId, + clearStagedPreview: false, + }); + }, + [props.dispatch] + ); + const updateAll = useMemo( + () => (datasourceId: string, newDatasourceState: unknown, newVisualizationState: unknown) => { + props.dispatch({ + type: 'UPDATE_STATE', + subType: 'UPDATE_ALL_STATES', + updater: prevState => { + return { + ...prevState, + datasourceStates: { + ...prevState.datasourceStates, + [datasourceId]: { + state: newDatasourceState, + isLoading: false, + }, + }, + visualization: { + ...prevState.visualization, + state: newVisualizationState, + }, + stagedPreview: undefined, + }; + }, + }); + }, + [props.dispatch] + ); + const layerIds = activeVisualization.getLayerIds(visualizationState); + + return ( + + {layerIds.map(layerId => ( + { + dispatch({ + type: 'UPDATE_STATE', + subType: 'REMOVE_OR_CLEAR_LAYER', + updater: state => + removeLayer({ + activeVisualization, + layerId, + trackUiEvent, + datasourceMap, + state, + }), + }); + }} + /> + ))} + {activeVisualization.appendLayer && visualizationState && ( + + + { + dispatch({ + type: 'UPDATE_STATE', + subType: 'ADD_LAYER', + updater: state => + appendLayer({ + activeVisualization, + generateId, + trackUiEvent, + activeDatasource: datasourceMap[activeDatasourceId], + state, + }), + }); + }} + iconType="plusInCircleFilled" + /> + + + )} + + ); +} diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_popover.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_popover.tsx new file mode 100644 index 0000000000000..36db13b74ac4f --- /dev/null +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/dimension_popover.tsx @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { EuiPopover } from '@elastic/eui'; +import { VisualizationDimensionGroupConfig } from '../../../types'; +import { DimensionPopoverState } from './types'; + +export function DimensionPopover({ + popoverState, + setPopoverState, + groups, + accessor, + groupId, + trigger, + panel, +}: { + popoverState: DimensionPopoverState; + setPopoverState: (newState: DimensionPopoverState) => void; + groups: VisualizationDimensionGroupConfig[]; + accessor: string; + groupId: string; + trigger: React.ReactElement; + panel: React.ReactElement; +}) { + const noMatch = popoverState.isOpen ? !groups.some(d => d.accessors.includes(accessor)) : false; + return ( + { + setPopoverState({ isOpen: false, openId: null, addingToGroupId: null }); + }} + button={trigger} + anchorPosition="leftUp" + withTitle + panelPaddingSize="s" + > + {panel} + + ); +} diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/index.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/index.ts new file mode 100644 index 0000000000000..754b3fb5c6fde --- /dev/null +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { ConfigPanelWrapper } from './config_panel'; diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/layer_actions.test.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_actions.test.ts similarity index 100% rename from x-pack/plugins/lens/public/editor_frame_service/editor_frame/layer_actions.test.ts rename to x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_actions.test.ts diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/layer_actions.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_actions.ts similarity index 95% rename from x-pack/plugins/lens/public/editor_frame_service/editor_frame/layer_actions.ts rename to x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_actions.ts index cc2cbb172d23e..3d1d590664238 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/layer_actions.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_actions.ts @@ -5,8 +5,8 @@ */ import _ from 'lodash'; -import { EditorFrameState } from './state_management'; -import { Datasource, Visualization } from '../../types'; +import { EditorFrameState } from '../state_management'; +import { Datasource, Visualization } from '../../../types'; interface RemoveLayerOptions { trackUiEvent: (name: string) => void; diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx new file mode 100644 index 0000000000000..f7be82dd34ba3 --- /dev/null +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx @@ -0,0 +1,405 @@ +/* + * 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, { useContext, useState } from 'react'; +import { + EuiPanel, + EuiSpacer, + EuiButtonIcon, + EuiFlexGroup, + EuiFlexItem, + EuiButtonEmpty, + EuiFormRow, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { NativeRenderer } from '../../../native_renderer'; +import { Visualization, FramePublicAPI, StateSetter } from '../../../types'; +import { DragContext, DragDrop, ChildDragDropProvider } from '../../../drag_drop'; +import { LayerSettings } from './layer_settings'; +import { trackUiEvent } from '../../../lens_ui_telemetry'; +import { generateId } from '../../../id_generator'; +import { ConfigPanelWrapperProps, DimensionPopoverState } from './types'; +import { DimensionPopover } from './dimension_popover'; + +export function LayerPanel( + props: Exclude & { + frame: FramePublicAPI; + layerId: string; + isOnlyLayer: boolean; + activeVisualization: Visualization; + visualizationState: unknown; + updateVisualization: StateSetter; + updateDatasource: (datasourceId: string, newState: unknown) => void; + updateAll: ( + datasourceId: string, + newDatasourcestate: unknown, + newVisualizationState: unknown + ) => void; + onRemoveLayer: () => void; + } +) { + const dragDropContext = useContext(DragContext); + const { framePublicAPI, layerId, activeVisualization, isOnlyLayer, onRemoveLayer } = props; + const datasourcePublicAPI = framePublicAPI.datasourceLayers[layerId]; + if (!datasourcePublicAPI) { + return null; + } + const layerVisualizationConfigProps = { + layerId, + dragDropContext, + state: props.visualizationState, + frame: props.framePublicAPI, + dateRange: props.framePublicAPI.dateRange, + }; + const datasourceId = datasourcePublicAPI.datasourceId; + const layerDatasourceState = props.datasourceStates[datasourceId].state; + const layerDatasource = props.datasourceMap[datasourceId]; + + const layerDatasourceDropProps = { + layerId, + dragDropContext, + state: layerDatasourceState, + setState: (newState: unknown) => { + props.updateDatasource(datasourceId, newState); + }, + }; + + const layerDatasourceConfigProps = { + ...layerDatasourceDropProps, + frame: props.framePublicAPI, + dateRange: props.framePublicAPI.dateRange, + }; + + const [popoverState, setPopoverState] = useState({ + isOpen: false, + openId: null, + addingToGroupId: null, + }); + + const { groups } = activeVisualization.getConfiguration(layerVisualizationConfigProps); + const isEmptyLayer = !groups.some(d => d.accessors.length > 0); + + return ( + + + + + + + + {layerDatasource && ( + + { + const newState = + typeof updater === 'function' ? updater(layerDatasourceState) : updater; + // Look for removed columns + const nextPublicAPI = layerDatasource.getPublicAPI({ + state: newState, + layerId, + dateRange: props.framePublicAPI.dateRange, + }); + const nextTable = new Set( + nextPublicAPI.getTableSpec().map(({ columnId }) => columnId) + ); + const removed = datasourcePublicAPI + .getTableSpec() + .map(({ columnId }) => columnId) + .filter(columnId => !nextTable.has(columnId)); + let nextVisState = props.visualizationState; + removed.forEach(columnId => { + nextVisState = activeVisualization.removeDimension({ + layerId, + columnId, + prevState: nextVisState, + }); + }); + + props.updateAll(datasourceId, newState, nextVisState); + }, + }} + /> + + )} + + + + + {groups.map((group, index) => { + const newId = generateId(); + const isMissing = !isEmptyLayer && group.required && group.accessors.length === 0; + return ( + + <> + {group.accessors.map(accessor => ( + { + layerDatasource.onDrop({ + ...layerDatasourceDropProps, + droppedItem, + columnId: accessor, + filterOperations: group.filterOperations, + }); + }} + > + { + if (popoverState.isOpen) { + setPopoverState({ + isOpen: false, + openId: null, + addingToGroupId: null, + }); + } else { + setPopoverState({ + isOpen: true, + openId: accessor, + addingToGroupId: null, // not set for existing dimension + }); + } + }, + }} + /> + } + panel={ + + } + /> + + { + trackUiEvent('indexpattern_dimension_removed'); + props.updateAll( + datasourceId, + layerDatasource.removeColumn({ + layerId, + columnId: accessor, + prevState: layerDatasourceState, + }), + props.activeVisualization.removeDimension({ + layerId, + columnId: accessor, + prevState: props.visualizationState, + }) + ); + }} + /> + + ))} + {group.supportsMoreColumns ? ( + { + const dropSuccess = layerDatasource.onDrop({ + ...layerDatasourceDropProps, + droppedItem, + columnId: newId, + filterOperations: group.filterOperations, + }); + if (dropSuccess) { + props.updateVisualization( + activeVisualization.setDimension({ + layerId, + groupId: group.groupId, + columnId: newId, + prevState: props.visualizationState, + }) + ); + } + }} + > + + { + if (popoverState.isOpen) { + setPopoverState({ + isOpen: false, + openId: null, + addingToGroupId: null, + }); + } else { + setPopoverState({ + isOpen: true, + openId: newId, + addingToGroupId: group.groupId, + }); + } + }} + size="xs" + > + + +
+ } + panel={ + { + props.updateAll( + datasourceId, + newState, + activeVisualization.setDimension({ + layerId, + groupId: group.groupId, + columnId: newId, + prevState: props.visualizationState, + }) + ); + setPopoverState({ + isOpen: true, + openId: newId, + addingToGroupId: null, // clear now that dimension exists + }); + }, + }} + /> + } + /> + + ) : null} + + + ); + })} + + + + + + { + // If we don't blur the remove / clear button, it remains focused + // which is a strange UX in this case. e.target.blur doesn't work + // due to who knows what, but probably event re-writing. Additionally, + // activeElement does not have blur so, we need to do some casting + safeguards. + const el = (document.activeElement as unknown) as { blur: () => void }; + + if (el?.blur) { + el.blur(); + } + + onRemoveLayer(); + }} + > + {isOnlyLayer + ? i18n.translate('xpack.lens.resetLayer', { + defaultMessage: 'Reset layer', + }) + : i18n.translate('xpack.lens.deleteLayer', { + defaultMessage: 'Delete layer', + })} + + + + + + ); +} diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_settings.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_settings.tsx new file mode 100644 index 0000000000000..57588e31590b4 --- /dev/null +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_settings.tsx @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useState } from 'react'; +import { EuiPopover, EuiButtonIcon } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { NativeRenderer } from '../../../native_renderer'; +import { Visualization, VisualizationLayerWidgetProps } from '../../../types'; + +export function LayerSettings({ + layerId, + activeVisualization, + layerConfigProps, +}: { + layerId: string; + activeVisualization: Visualization; + layerConfigProps: VisualizationLayerWidgetProps; +}) { + const [isOpen, setIsOpen] = useState(false); + + if (!activeVisualization.renderLayerContextMenu) { + return null; + } + + return ( + setIsOpen(!isOpen)} + data-test-subj="lns_layer_settings" + /> + } + isOpen={isOpen} + closePopover={() => setIsOpen(false)} + anchorPosition="leftUp" + > + + + ); +} diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/types.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/types.ts new file mode 100644 index 0000000000000..df510d3648f8c --- /dev/null +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/types.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Action } from '../state_management'; +import { + Visualization, + FramePublicAPI, + Datasource, + DatasourceDimensionEditorProps, +} from '../../../types'; + +export interface ConfigPanelWrapperProps { + activeDatasourceId: string; + visualizationState: unknown; + visualizationMap: Record; + activeVisualizationId: string | null; + dispatch: (action: Action) => void; + framePublicAPI: FramePublicAPI; + datasourceMap: Record; + datasourceStates: Record< + string, + { + isLoading: boolean; + state: unknown; + } + >; + core: DatasourceDimensionEditorProps['core']; +} + +export interface DimensionPopoverState { + isOpen: boolean; + openId: string | null; + addingToGroupId: string | null; +} diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel_wrapper.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel_wrapper.tsx deleted file mode 100644 index da812e948b23f..0000000000000 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel_wrapper.tsx +++ /dev/null @@ -1,655 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { useMemo, useContext, memo, useState } from 'react'; -import { - EuiPanel, - EuiSpacer, - EuiPopover, - EuiButtonIcon, - EuiFlexGroup, - EuiFlexItem, - EuiButtonEmpty, - EuiToolTip, - EuiButton, - EuiForm, - EuiFormRow, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { NativeRenderer } from '../../native_renderer'; -import { Action } from './state_management'; -import { - Visualization, - FramePublicAPI, - Datasource, - VisualizationLayerWidgetProps, - DatasourceDimensionEditorProps, - StateSetter, -} from '../../types'; -import { DragContext, DragDrop, ChildDragDropProvider } from '../../drag_drop'; -import { ChartSwitch } from './chart_switch'; -import { trackUiEvent } from '../../lens_ui_telemetry'; -import { generateId } from '../../id_generator'; -import { removeLayer, appendLayer } from './layer_actions'; - -interface ConfigPanelWrapperProps { - activeDatasourceId: string; - visualizationState: unknown; - visualizationMap: Record; - activeVisualizationId: string | null; - dispatch: (action: Action) => void; - framePublicAPI: FramePublicAPI; - datasourceMap: Record; - datasourceStates: Record< - string, - { - isLoading: boolean; - state: unknown; - } - >; - core: DatasourceDimensionEditorProps['core']; -} - -export const ConfigPanelWrapper = memo(function ConfigPanelWrapper(props: ConfigPanelWrapperProps) { - const activeVisualization = props.visualizationMap[props.activeVisualizationId || '']; - const { visualizationState } = props; - - return ( - <> - - {activeVisualization && visualizationState && ( - - )} - - ); -}); - -function LayerPanels( - props: ConfigPanelWrapperProps & { - activeDatasourceId: string; - activeVisualization: Visualization; - } -) { - const { - framePublicAPI, - activeVisualization, - visualizationState, - dispatch, - activeDatasourceId, - datasourceMap, - } = props; - const setVisualizationState = useMemo( - () => (newState: unknown) => { - props.dispatch({ - type: 'UPDATE_VISUALIZATION_STATE', - visualizationId: activeVisualization.id, - newState, - clearStagedPreview: false, - }); - }, - [props.dispatch, activeVisualization] - ); - const updateDatasource = useMemo( - () => (datasourceId: string, newState: unknown) => { - props.dispatch({ - type: 'UPDATE_DATASOURCE_STATE', - updater: () => newState, - datasourceId, - clearStagedPreview: false, - }); - }, - [props.dispatch] - ); - const updateAll = useMemo( - () => (datasourceId: string, newDatasourceState: unknown, newVisualizationState: unknown) => { - props.dispatch({ - type: 'UPDATE_STATE', - subType: 'UPDATE_ALL_STATES', - updater: prevState => { - return { - ...prevState, - datasourceStates: { - ...prevState.datasourceStates, - [datasourceId]: { - state: newDatasourceState, - isLoading: false, - }, - }, - visualization: { - ...prevState.visualization, - state: newVisualizationState, - }, - stagedPreview: undefined, - }; - }, - }); - }, - [props.dispatch] - ); - const layerIds = activeVisualization.getLayerIds(visualizationState); - - return ( - - {layerIds.map(layerId => ( - { - dispatch({ - type: 'UPDATE_STATE', - subType: 'REMOVE_OR_CLEAR_LAYER', - updater: state => - removeLayer({ - activeVisualization, - layerId, - trackUiEvent, - datasourceMap, - state, - }), - }); - }} - /> - ))} - {activeVisualization.appendLayer && ( - - - { - dispatch({ - type: 'UPDATE_STATE', - subType: 'ADD_LAYER', - updater: state => - appendLayer({ - activeVisualization, - generateId, - trackUiEvent, - activeDatasource: datasourceMap[activeDatasourceId], - state, - }), - }); - }} - iconType="plusInCircleFilled" - /> - - - )} - - ); -} - -function LayerPanel( - props: Exclude & { - frame: FramePublicAPI; - layerId: string; - isOnlyLayer: boolean; - activeVisualization: Visualization; - visualizationState: unknown; - updateVisualization: StateSetter; - updateDatasource: (datasourceId: string, newState: unknown) => void; - updateAll: ( - datasourceId: string, - newDatasourcestate: unknown, - newVisualizationState: unknown - ) => void; - onRemoveLayer: () => void; - } -) { - const dragDropContext = useContext(DragContext); - const { framePublicAPI, layerId, activeVisualization, isOnlyLayer, onRemoveLayer } = props; - const datasourcePublicAPI = framePublicAPI.datasourceLayers[layerId]; - if (!datasourcePublicAPI) { - return null; - } - const layerVisualizationConfigProps = { - layerId, - dragDropContext, - state: props.visualizationState, - frame: props.framePublicAPI, - dateRange: props.framePublicAPI.dateRange, - }; - const datasourceId = datasourcePublicAPI.datasourceId; - const layerDatasourceState = props.datasourceStates[datasourceId].state; - const layerDatasource = props.datasourceMap[datasourceId]; - - const layerDatasourceDropProps = { - layerId, - dragDropContext, - state: layerDatasourceState, - setState: (newState: unknown) => { - props.updateDatasource(datasourceId, newState); - }, - }; - - const layerDatasourceConfigProps = { - ...layerDatasourceDropProps, - frame: props.framePublicAPI, - dateRange: props.framePublicAPI.dateRange, - }; - - const [popoverState, setPopoverState] = useState<{ - isOpen: boolean; - openId: string | null; - addingToGroupId: string | null; - }>({ - isOpen: false, - openId: null, - addingToGroupId: null, - }); - - const { groups } = activeVisualization.getConfiguration(layerVisualizationConfigProps); - const isEmptyLayer = !groups.some(d => d.accessors.length > 0); - - function wrapInPopover( - id: string, - groupId: string, - trigger: React.ReactElement, - panel: React.ReactElement - ) { - const noMatch = popoverState.isOpen ? !groups.some(d => d.accessors.includes(id)) : false; - return ( - { - setPopoverState({ isOpen: false, openId: null, addingToGroupId: null }); - }} - button={trigger} - anchorPosition="leftUp" - withTitle - panelPaddingSize="s" - > - {panel} - - ); - } - - return ( - - - - - - - - {layerDatasource && ( - - { - const newState = - typeof updater === 'function' ? updater(layerDatasourceState) : updater; - // Look for removed columns - const nextPublicAPI = layerDatasource.getPublicAPI({ - state: newState, - layerId, - dateRange: props.framePublicAPI.dateRange, - }); - const nextTable = new Set( - nextPublicAPI.getTableSpec().map(({ columnId }) => columnId) - ); - const removed = datasourcePublicAPI - .getTableSpec() - .map(({ columnId }) => columnId) - .filter(columnId => !nextTable.has(columnId)); - let nextVisState = props.visualizationState; - removed.forEach(columnId => { - nextVisState = activeVisualization.removeDimension({ - layerId, - columnId, - prevState: nextVisState, - }); - }); - - props.updateAll(datasourceId, newState, nextVisState); - }, - }} - /> - - )} - - - - - {groups.map((group, index) => { - const newId = generateId(); - const isMissing = !isEmptyLayer && group.required && group.accessors.length === 0; - return ( - - <> - {group.accessors.map(accessor => ( - { - layerDatasource.onDrop({ - ...layerDatasourceDropProps, - droppedItem, - columnId: accessor, - filterOperations: group.filterOperations, - }); - }} - > - {wrapInPopover( - accessor, - group.groupId, - { - if (popoverState.isOpen) { - setPopoverState({ - isOpen: false, - openId: null, - addingToGroupId: null, - }); - } else { - setPopoverState({ - isOpen: true, - openId: accessor, - addingToGroupId: null, // not set for existing dimension - }); - } - }, - }} - />, - - )} - - { - trackUiEvent('indexpattern_dimension_removed'); - props.updateAll( - datasourceId, - layerDatasource.removeColumn({ - layerId, - columnId: accessor, - prevState: layerDatasourceState, - }), - props.activeVisualization.removeDimension({ - layerId, - columnId: accessor, - prevState: props.visualizationState, - }) - ); - }} - /> - - ))} - {group.supportsMoreColumns ? ( - { - const dropSuccess = layerDatasource.onDrop({ - ...layerDatasourceDropProps, - droppedItem, - columnId: newId, - filterOperations: group.filterOperations, - }); - if (dropSuccess) { - props.updateVisualization( - activeVisualization.setDimension({ - layerId, - groupId: group.groupId, - columnId: newId, - prevState: props.visualizationState, - }) - ); - } - }} - > - {wrapInPopover( - newId, - group.groupId, -
- { - if (popoverState.isOpen) { - setPopoverState({ - isOpen: false, - openId: null, - addingToGroupId: null, - }); - } else { - setPopoverState({ - isOpen: true, - openId: newId, - addingToGroupId: group.groupId, - }); - } - }} - size="xs" - > - - -
, - { - props.updateAll( - datasourceId, - newState, - activeVisualization.setDimension({ - layerId, - groupId: group.groupId, - columnId: newId, - prevState: props.visualizationState, - }) - ); - setPopoverState({ - isOpen: true, - openId: newId, - addingToGroupId: null, // clear now that dimension exists - }); - }, - }} - /> - )} -
- ) : null} - -
- ); - })} - - - - - - { - // If we don't blur the remove / clear button, it remains focused - // which is a strange UX in this case. e.target.blur doesn't work - // due to who knows what, but probably event re-writing. Additionally, - // activeElement does not have blur so, we need to do some casting + safeguards. - const el = (document.activeElement as unknown) as { blur: () => void }; - - if (el?.blur) { - el.blur(); - } - - onRemoveLayer(); - }} - > - {isOnlyLayer - ? i18n.translate('xpack.lens.resetLayer', { - defaultMessage: 'Reset layer', - }) - : i18n.translate('xpack.lens.deleteLayer', { - defaultMessage: 'Delete layer', - })} - - - -
-
- ); -} - -function LayerSettings({ - layerId, - activeVisualization, - layerConfigProps, -}: { - layerId: string; - activeVisualization: Visualization; - layerConfigProps: VisualizationLayerWidgetProps; -}) { - const [isOpen, setIsOpen] = useState(false); - - if (!activeVisualization.renderLayerContextMenu) { - return null; - } - - return ( - setIsOpen(!isOpen)} - data-test-subj="lns_layer_settings" - /> - } - isOpen={isOpen} - closePopover={() => setIsOpen(false)} - anchorPosition="leftUp" - > - - - ); -} diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx index b72d9081bbc91..5cd803e7cebbc 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx @@ -16,7 +16,7 @@ import { } from '../../types'; import { reducer, getInitialState } from './state_management'; import { DataPanelWrapper } from './data_panel_wrapper'; -import { ConfigPanelWrapper } from './config_panel_wrapper'; +import { ConfigPanelWrapper } from './config_panel'; import { FrameLayout } from './frame_layout'; import { SuggestionPanel } from './suggestion_panel'; import { WorkspacePanel } from './workspace_panel'; diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/index.scss b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/index.scss index d4b27c6c98b3c..5e3726c953f11 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/index.scss +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/index.scss @@ -1,8 +1,6 @@ -@import 'chart_switch'; -@import 'config_panel_wrapper'; +@import 'config_panel/index'; @import 'data_panel_wrapper'; @import 'expression_renderer'; @import 'frame_layout'; @import 'suggestion_panel'; @import 'workspace_panel_wrapper'; - diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.tsx index b3bd08d3bbfbe..583832aafcbe8 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.tsx @@ -192,7 +192,7 @@ export const IndexPatternDimensionTriggerComponent = function IndexPatternDimens return ( { props.togglePopover(); }} diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index 0b432c0c70727..181f192520d0d 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -273,7 +273,7 @@ export type VisualizationLayerWidgetProps = VisualizationConfigProp setState: (newState: T) => void; }; -type VisualizationDimensionGroupConfig = SharedDimensionProps & { +export type VisualizationDimensionGroupConfig = SharedDimensionProps & { groupLabel: string; /** ID is passed back to visualization. For example, `x` */ @@ -368,55 +368,86 @@ export interface VisualizationType { } export interface Visualization { + /** Plugin ID, such as "lnsXY" */ id: string; + /** + * Initialize is allowed to modify the state stored in memory. The initialize function + * is called with a previous state in two cases: + * - Loadingn from a saved visualization + * - When using suggestions, the suggested state is passed in + */ + initialize: (frame: FramePublicAPI, state?: P) => T; + /** + * Can remove any state that should not be persisted to saved object, such as UI state + */ + getPersistableState: (state: T) => P; + + /** + * Visualizations must provide at least one type for the chart switcher, + * but can register multiple subtypes + */ visualizationTypes: VisualizationType[]; + /** + * If the visualization has subtypes, update the subtype in state. + */ + switchVisualizationType?: (visualizationTypeId: string, state: T) => T; + /** Description is displayed as the clickable text in the chart switcher */ + getDescription: (state: T) => { icon?: IconType; label: string }; + /** Frame needs to know which layers the visualization is currently using */ getLayerIds: (state: T) => string[]; + /** Reset button on each layer triggers this */ clearLayer: (state: T, layerId: string) => T; + /** Optional, if the visualization supports multiple layers */ removeLayer?: (state: T, layerId: string) => T; + /** Track added layers in internal state */ appendLayer?: (state: T, layerId: string) => T; - // Layer context menu is used by visualizations for styling the entire layer - // For example, the XY visualization uses this to have multiple chart types - getLayerContextMenuIcon?: (opts: { state: T; layerId: string }) => IconType | undefined; - renderLayerContextMenu?: (domElement: Element, props: VisualizationLayerWidgetProps) => void; - + /** + * For consistency across different visualizations, the dimension configuration UI is standardized + */ getConfiguration: ( props: VisualizationConfigProps ) => { groups: VisualizationDimensionGroupConfig[] }; - getDescription: ( - state: T - ) => { - icon?: IconType; - label: string; - }; - - switchVisualizationType?: (visualizationTypeId: string, state: T) => T; - - // For initializing from saved object - initialize: (frame: FramePublicAPI, state?: P) => T; - - getPersistableState: (state: T) => P; + /** + * Popover contents that open when the user clicks the contextMenuIcon. This can be used + * for extra configurability, such as for styling the legend or axis + */ + renderLayerContextMenu?: (domElement: Element, props: VisualizationLayerWidgetProps) => void; + /** + * Visualizations can provide a custom icon which will open a layer-specific popover + * If no icon is provided, gear icon is default + */ + getLayerContextMenuIcon?: (opts: { state: T; layerId: string }) => IconType | undefined; - // Actions triggered by the frame which tell the datasource that a dimension is being changed - setDimension: ( - props: VisualizationDimensionChangeProps & { - groupId: string; - } - ) => T; + /** + * The frame is telling the visualization to update or set a dimension based on user interaction + * groupId is coming from the groupId provided in getConfiguration + */ + setDimension: (props: VisualizationDimensionChangeProps & { groupId: string }) => T; + /** + * The frame is telling the visualization to remove a dimension. The visualization needs to + * look at its internal state to determine which dimension is being affected. + */ removeDimension: (props: VisualizationDimensionChangeProps) => T; - toExpression: (state: T, frame: FramePublicAPI) => Ast | string | null; + /** + * The frame will call this function on all visualizations at different times. The + * main use cases where visualization suggestions are requested are: + * - When dragging a field + * - When opening the chart switcher + * If the state is provided when requesting suggestions, the visualization is active. + * Most visualizations will apply stricter filtering to suggestions when they are active, + * because suggestions have the potential to remove the users's work in progress. + */ + getSuggestions: (context: SuggestionRequest) => Array>; + toExpression: (state: T, frame: FramePublicAPI) => Ast | string | null; /** - * Epression to render a preview version of the chart in very constraint space. + * Expression to render a preview version of the chart in very constrained space. * If there is no expression provided, the preview icon is used. */ toPreviewExpression?: (state: T, frame: FramePublicAPI) => Ast | string | null; - - // The frame will call this function on all visualizations when the table changes, or when - // rendering additional ways of using the data - getSuggestions: (context: SuggestionRequest) => Array>; } diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_expression.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_expression.tsx index f12a0e5b907c7..d6b6de479acfb 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_expression.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_expression.tsx @@ -28,7 +28,7 @@ import { import { EuiIcon, EuiText, IconType, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { EmbeddableVisTriggerContext } from '../../../../../src/plugins/embeddable/public'; +import { ValueClickTriggerContext } from '../../../../../src/plugins/embeddable/public'; import { VIS_EVENT_TO_TRIGGER } from '../../../../../src/plugins/visualizations/public'; import { LensMultiTable, FormatFactory } from '../types'; import { XYArgs, SeriesType, visualizationTypes } from './types'; @@ -277,7 +277,7 @@ export function XYChart({ const timeFieldName = xDomain && xAxisFieldName; - const context: EmbeddableVisTriggerContext = { + const context: ValueClickTriggerContext = { data: { data: points.map(point => ({ row: point.row, diff --git a/x-pack/plugins/maps/common/constants.ts b/x-pack/plugins/maps/common/constants.ts index a4006732224ce..fd972219563a8 100644 --- a/x-pack/plugins/maps/common/constants.ts +++ b/x-pack/plugins/maps/common/constants.ts @@ -166,6 +166,7 @@ export enum STYLE_TYPE { export enum LAYER_STYLE_TYPE { VECTOR = 'VECTOR', HEATMAP = 'HEATMAP', + TILE = 'TILE', } export const COLOR_MAP_TYPE = { @@ -214,3 +215,5 @@ export enum SCALING_TYPES { } export const RGBA_0000 = 'rgba(0,0,0,0)'; + +export const SPATIAL_FILTERS_LAYER_ID = 'SPATIAL_FILTERS_LAYER_ID'; diff --git a/x-pack/plugins/maps/common/descriptor_types/descriptor_types.d.ts b/x-pack/plugins/maps/common/descriptor_types/descriptor_types.d.ts index f8175b0ed3f10..6980f14d0788a 100644 --- a/x-pack/plugins/maps/common/descriptor_types/descriptor_types.d.ts +++ b/x-pack/plugins/maps/common/descriptor_types/descriptor_types.d.ts @@ -5,8 +5,9 @@ */ /* eslint-disable @typescript-eslint/consistent-type-definitions */ +import { Query } from 'src/plugins/data/public'; import { AGG_TYPE, GRID_RESOLUTION, RENDER_AS, SORT_ORDER, SCALING_TYPES } from '../constants'; -import { VectorStyleDescriptor } from './style_property_descriptor_types'; +import { StyleDescriptor, VectorStyleDescriptor } from './style_property_descriptor_types'; import { DataRequestDescriptor } from './data_request_descriptor_types'; export type AttributionDescriptor = { @@ -17,6 +18,7 @@ export type AttributionDescriptor = { export type AbstractSourceDescriptor = { id?: string; type: string; + applyGlobalQuery?: boolean; }; export type EMSTMSSourceDescriptor = AbstractSourceDescriptor & { @@ -71,17 +73,15 @@ export type ESTermSourceDescriptor = AbstractESAggSourceDescriptor & { term: string; // term field name }; -export type KibanaRegionmapSourceDescriptor = { - type: string; +export type KibanaRegionmapSourceDescriptor = AbstractSourceDescriptor & { name: string; }; -export type KibanaTilemapSourceDescriptor = { - type: string; -}; +// This is for symmetry with other sources only. +// It takes no additional configuration since all params are in the .yml. +export type KibanaTilemapSourceDescriptor = AbstractSourceDescriptor; -export type WMSSourceDescriptor = { - type: string; +export type WMSSourceDescriptor = AbstractSourceDescriptor & { serviceUrl: string; layers: string; styles: string; @@ -111,6 +111,8 @@ export type JoinDescriptor = { right: ESTermSourceDescriptor; }; +// todo : this union type is incompatible with dynamic extensibility of sources. +// Reconsider using SourceDescriptor in type signatures for top-level classes export type SourceDescriptor = | XYZTMSSourceDescriptor | WMSSourceDescriptor @@ -121,7 +123,9 @@ export type SourceDescriptor = | ESGeoGridSourceDescriptor | EMSFileSourceDescriptor | ESPewPewSourceDescriptor - | TiledSingleLayerVectorSourceDescriptor; + | TiledSingleLayerVectorSourceDescriptor + | EMSTMSSourceDescriptor + | EMSFileSourceDescriptor; export type LayerDescriptor = { __dataRequests?: DataRequestDescriptor[]; @@ -129,12 +133,14 @@ export type LayerDescriptor = { __errorMessage?: string; alpha?: number; id: string; - label?: string; + label?: string | null; minZoom?: number; maxZoom?: number; - sourceDescriptor: SourceDescriptor; + sourceDescriptor: SourceDescriptor | null; type?: string; visible?: boolean; + style?: StyleDescriptor | null; + query?: Query; }; export type VectorLayerDescriptor = LayerDescriptor & { diff --git a/x-pack/plugins/maps/common/descriptor_types/style_property_descriptor_types.d.ts b/x-pack/plugins/maps/common/descriptor_types/style_property_descriptor_types.d.ts index 47e56ff96d623..381bc5bba01c0 100644 --- a/x-pack/plugins/maps/common/descriptor_types/style_property_descriptor_types.d.ts +++ b/x-pack/plugins/maps/common/descriptor_types/style_property_descriptor_types.d.ts @@ -182,7 +182,11 @@ export type VectorStylePropertiesDescriptor = { [VECTOR_STYLES.LABEL_BORDER_SIZE]?: LabelBorderSizeStylePropertyDescriptor; }; -export type VectorStyleDescriptor = { +export type StyleDescriptor = { + type: string; +}; + +export type VectorStyleDescriptor = StyleDescriptor & { type: LAYER_STYLE_TYPE.VECTOR; properties: VectorStylePropertiesDescriptor; }; diff --git a/x-pack/plugins/maps/public/angular/get_initial_layers.test.js b/x-pack/plugins/maps/public/angular/get_initial_layers.test.js index f41ed26b2a05d..4b5cad8d19260 100644 --- a/x-pack/plugins/maps/public/angular/get_initial_layers.test.js +++ b/x-pack/plugins/maps/public/angular/get_initial_layers.test.js @@ -52,7 +52,7 @@ describe('kibana.yml configured with map.tilemap.url', () => { sourceDescriptor: { type: 'KIBANA_TILEMAP', }, - style: {}, + style: { type: 'TILE' }, type: 'TILE', visible: true, }, @@ -96,7 +96,7 @@ describe('EMS is enabled', () => { isAutoSelect: true, type: 'EMS_TMS', }, - style: {}, + style: { type: 'TILE' }, type: 'VECTOR_TILE', visible: true, }, diff --git a/x-pack/plugins/maps/public/components/alpha_slider.tsx b/x-pack/plugins/maps/public/components/alpha_slider.tsx new file mode 100644 index 0000000000000..921c386292050 --- /dev/null +++ b/x-pack/plugins/maps/public/components/alpha_slider.tsx @@ -0,0 +1,46 @@ +/* + * 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 { EuiFormRow } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +// @ts-ignore +import { ValidatedRange } from './validated_range'; + +interface Props { + alpha: number; + onChange: (alpha: number) => void; +} + +export function AlphaSlider({ alpha, onChange }: Props) { + const onAlphaChange = (newAlpha: number) => { + onChange(newAlpha / 100); + }; + + return ( + + + + ); +} diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/layer_settings/layer_settings.js b/x-pack/plugins/maps/public/connected_components/layer_panel/layer_settings/layer_settings.js index 168c735ab7a6c..d84d05260f982 100644 --- a/x-pack/plugins/maps/public/connected_components/layer_panel/layer_settings/layer_settings.js +++ b/x-pack/plugins/maps/public/connected_components/layer_panel/layer_settings/layer_settings.js @@ -8,7 +8,7 @@ import React, { Fragment } from 'react'; import { EuiTitle, EuiPanel, EuiFormRow, EuiFieldText, EuiSpacer } from '@elastic/eui'; -import { ValidatedRange } from '../../../components/validated_range'; +import { AlphaSlider } from '../../../components/alpha_slider'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { ValidatedDualRange } from '../../../../../../../src/plugins/kibana_react/public'; @@ -24,8 +24,7 @@ export function LayerSettings(props) { }; const onAlphaChange = alpha => { - const alphaDecimal = alpha / 100; - props.updateAlpha(props.layerId, alphaDecimal); + props.updateAlpha(props.layerId, alpha); }; const renderZoomSliders = () => { @@ -64,34 +63,6 @@ export function LayerSettings(props) { ); }; - const renderAlphaSlider = () => { - const alphaPercent = Math.round(props.alpha * 100); - - return ( - - - - ); - }; - return ( @@ -107,7 +78,7 @@ export function LayerSettings(props) { {renderLabel()} {renderZoomSliders()} - {renderAlphaSlider()} + diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/draw_control/draw_control.js b/x-pack/plugins/maps/public/connected_components/map/mb/draw_control/draw_control.js index d20faa39d6492..a69e06458a6a0 100644 --- a/x-pack/plugins/maps/public/connected_components/map/mb/draw_control/draw_control.js +++ b/x-pack/plugins/maps/public/connected_components/map/mb/draw_control/draw_control.js @@ -64,13 +64,28 @@ export class DrawControl extends React.Component { if (this.props.drawState.drawType === DRAW_TYPE.DISTANCE) { const circle = e.features[0]; - roundCoordinates(circle.properties.center); + const distanceKm = _.round( + circle.properties.radiusKm, + circle.properties.radiusKm > 10 ? 0 : 2 + ); + // Only include as much precision as needed for distance + let precision = 2; + if (distanceKm <= 1) { + precision = 5; + } else if (distanceKm <= 10) { + precision = 4; + } else if (distanceKm <= 100) { + precision = 3; + } const filter = createDistanceFilterWithMeta({ alias: this.props.drawState.filterLabel, - distanceKm: _.round(circle.properties.radiusKm, circle.properties.radiusKm > 10 ? 0 : 2), + distanceKm, geoFieldName: this.props.drawState.geoFieldName, indexPatternId: this.props.drawState.indexPatternId, - point: circle.properties.center, + point: [ + _.round(circle.properties.center[0], precision), + _.round(circle.properties.center[1], precision), + ], }); this.props.addFilters([filter]); this.props.disableDrawState(); diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/index.js b/x-pack/plugins/maps/public/connected_components/map/mb/index.js index 459b38d422694..f8daf0804265b 100644 --- a/x-pack/plugins/maps/public/connected_components/map/mb/index.js +++ b/x-pack/plugins/maps/public/connected_components/map/mb/index.js @@ -23,6 +23,7 @@ import { isInteractiveDisabled, isTooltipControlDisabled, isViewControlHidden, + getSpatialFiltersLayer, getMapSettings, } from '../../../selectors/map_selectors'; @@ -33,6 +34,7 @@ function mapStateToProps(state = {}) { isMapReady: getMapReady(state), settings: getMapSettings(state), layerList: getLayerList(state), + spatialFiltersLayer: getSpatialFiltersLayer(state), goto: getGoto(state), inspectorAdapters: getInspectorAdapters(state), scrollZoom: getScrollZoom(state), diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/mb.utils.test.js b/x-pack/plugins/maps/public/connected_components/map/mb/mb.utils.test.js index a8c4f61a00da3..4774cdc556c24 100644 --- a/x-pack/plugins/maps/public/connected_components/map/mb/mb.utils.test.js +++ b/x-pack/plugins/maps/public/connected_components/map/mb/mb.utils.test.js @@ -5,6 +5,7 @@ */ import { removeOrphanedSourcesAndLayers, syncLayerOrderForSingleLayer } from './utils'; +import { SPATIAL_FILTERS_LAYER_ID } from '../../../../common/constants'; import _ from 'lodash'; class MockMbMap { @@ -121,7 +122,8 @@ function makeMultiSourceMockLayer(layerId) { ); } -describe('mb/utils', () => { +describe('removeOrphanedSourcesAndLayers', () => { + const spatialFilterLayer = makeMultiSourceMockLayer(SPATIAL_FILTERS_LAYER_ID); test('should remove foo and bar layer', async () => { const bazLayer = makeSingleSourceMockLayer('baz'); const fooLayer = makeSingleSourceMockLayer('foo'); @@ -133,7 +135,7 @@ describe('mb/utils', () => { const currentStyle = getMockStyle(currentLayerList); const mockMbMap = new MockMbMap(currentStyle); - removeOrphanedSourcesAndLayers(mockMbMap, nextLayerList); + removeOrphanedSourcesAndLayers(mockMbMap, nextLayerList, spatialFilterLayer); const removedStyle = mockMbMap.getStyle(); const nextStyle = getMockStyle(nextLayerList); @@ -151,7 +153,7 @@ describe('mb/utils', () => { const currentStyle = getMockStyle(currentLayerList); const mockMbMap = new MockMbMap(currentStyle); - removeOrphanedSourcesAndLayers(mockMbMap, nextLayerList); + removeOrphanedSourcesAndLayers(mockMbMap, nextLayerList, spatialFilterLayer); const removedStyle = mockMbMap.getStyle(); const nextStyle = getMockStyle(nextLayerList); @@ -169,13 +171,23 @@ describe('mb/utils', () => { const currentStyle = getMockStyle(currentLayerList); const mockMbMap = new MockMbMap(currentStyle); - removeOrphanedSourcesAndLayers(mockMbMap, nextLayerList); + removeOrphanedSourcesAndLayers(mockMbMap, nextLayerList, spatialFilterLayer); const removedStyle = mockMbMap.getStyle(); const nextStyle = getMockStyle(nextLayerList); expect(removedStyle).toEqual(nextStyle); }); + test('should not remove spatial filter layer and sources when spatialFilterLayer is provided', async () => { + const styleWithSpatialFilters = getMockStyle([spatialFilterLayer]); + const mockMbMap = new MockMbMap(styleWithSpatialFilters); + + removeOrphanedSourcesAndLayers(mockMbMap, [], spatialFilterLayer); + expect(mockMbMap.getStyle()).toEqual(styleWithSpatialFilters); + }); +}); + +describe('syncLayerOrderForSingleLayer', () => { test('should move bar layer in front of foo layer', async () => { const fooLayer = makeSingleSourceMockLayer('foo'); const barLayer = makeSingleSourceMockLayer('bar'); @@ -250,40 +262,4 @@ describe('mb/utils', () => { const nextStyle = getMockStyle(nextLayerListOrder); expect(orderedStyle).toEqual(nextStyle); }); - - test('should reorder foo and bar and remove baz', async () => { - const bazLayer = makeSingleSourceMockLayer('baz'); - const fooLayer = makeSingleSourceMockLayer('foo'); - const barLayer = makeSingleSourceMockLayer('bar'); - - const currentLayerOrder = [bazLayer, fooLayer, barLayer]; - const nextLayerListOrder = [barLayer, fooLayer]; - - const currentStyle = getMockStyle(currentLayerOrder); - const mockMbMap = new MockMbMap(currentStyle); - removeOrphanedSourcesAndLayers(mockMbMap, nextLayerListOrder); - syncLayerOrderForSingleLayer(mockMbMap, nextLayerListOrder); - const orderedStyle = mockMbMap.getStyle(); - - const nextStyle = getMockStyle(nextLayerListOrder); - expect(orderedStyle).toEqual(nextStyle); - }); - - test('should reorder foo and bar and remove baz, when having multi-source multi-layer data', async () => { - const bazLayer = makeMultiSourceMockLayer('baz'); - const fooLayer = makeSingleSourceMockLayer('foo'); - const barLayer = makeMultiSourceMockLayer('bar'); - - const currentLayerOrder = [bazLayer, fooLayer, barLayer]; - const nextLayerListOrder = [barLayer, fooLayer]; - - const currentStyle = getMockStyle(currentLayerOrder); - const mockMbMap = new MockMbMap(currentStyle); - removeOrphanedSourcesAndLayers(mockMbMap, nextLayerListOrder); - syncLayerOrderForSingleLayer(mockMbMap, nextLayerListOrder); - const orderedStyle = mockMbMap.getStyle(); - - const nextStyle = getMockStyle(nextLayerListOrder); - expect(orderedStyle).toEqual(nextStyle); - }); }); diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/utils.js b/x-pack/plugins/maps/public/connected_components/map/mb/utils.js index 7be2cd9e67084..adf109a087d27 100644 --- a/x-pack/plugins/maps/public/connected_components/map/mb/utils.js +++ b/x-pack/plugins/maps/public/connected_components/map/mb/utils.js @@ -7,11 +7,16 @@ import _ from 'lodash'; import { RGBAImage } from './image_utils'; -export function removeOrphanedSourcesAndLayers(mbMap, layerList) { +export function removeOrphanedSourcesAndLayers(mbMap, layerList, spatialFilterLayer) { const mbStyle = mbMap.getStyle(); const mbLayerIdsToRemove = []; mbStyle.layers.forEach(mbLayer => { + // ignore mapbox layers from spatial filter layer + if (spatialFilterLayer.ownsMbLayerId(mbLayer.id)) { + return; + } + const layer = layerList.find(layer => { return layer.ownsMbLayerId(mbLayer.id); }); @@ -24,6 +29,11 @@ export function removeOrphanedSourcesAndLayers(mbMap, layerList) { const mbSourcesToRemove = []; for (const mbSourceId in mbStyle.sources) { if (mbStyle.sources.hasOwnProperty(mbSourceId)) { + // ignore mapbox sources from spatial filter layer + if (spatialFilterLayer.ownsMbSourceId(mbSourceId)) { + return; + } + const layer = layerList.find(layer => { return layer.ownsMbSourceId(mbSourceId); }); @@ -35,6 +45,21 @@ export function removeOrphanedSourcesAndLayers(mbMap, layerList) { mbSourcesToRemove.forEach(mbSourceId => mbMap.removeSource(mbSourceId)); } +export function moveLayerToTop(mbMap, layer) { + const mbStyle = mbMap.getStyle(); + + if (!mbStyle.layers || mbStyle.layers.length === 0) { + return; + } + + layer.getMbLayerIds().forEach(mbLayerId => { + const mbLayer = mbMap.getLayer(mbLayerId); + if (mbLayer) { + mbMap.moveLayer(mbLayerId); + } + }); +} + /** * This is function assumes only a single layer moved in the layerList, compared to mbMap * It is optimized to minimize the amount of mbMap.moveLayer calls. @@ -47,9 +72,12 @@ export function syncLayerOrderForSingleLayer(mbMap, layerList) { } const mbLayers = mbMap.getStyle().layers.slice(); - const layerIds = mbLayers.map(mbLayer => { + const layerIds = []; + mbLayers.forEach(mbLayer => { const layer = layerList.find(layer => layer.ownsMbLayerId(mbLayer.id)); - return layer.getId(); + if (layer) { + layerIds.push(layer.getId()); + } }); const currentLayerOrderLayerIds = _.uniq(layerIds); diff --git a/x-pack/plugins/maps/public/connected_components/map/mb/view.js b/x-pack/plugins/maps/public/connected_components/map/mb/view.js index 71c1af44e493b..6bb5a4fed6e52 100644 --- a/x-pack/plugins/maps/public/connected_components/map/mb/view.js +++ b/x-pack/plugins/maps/public/connected_components/map/mb/view.js @@ -11,6 +11,7 @@ import { syncLayerOrderForSingleLayer, removeOrphanedSourcesAndLayers, addSpritesheetToMap, + moveLayerToTop, } from './utils'; import { getGlyphUrl, isRetina } from '../../../meta'; import { DECIMAL_DEGREES_PRECISION, ZOOM_PRECISION } from '../../../../common/constants'; @@ -74,7 +75,7 @@ export class MBMapContainer extends React.Component { } _debouncedSync = _.debounce(() => { - if (this._isMounted || !this.props.isMapReady) { + if (this._isMounted && this.props.isMapReady) { if (!this.state.hasSyncedLayerList) { this.setState( { @@ -86,6 +87,7 @@ export class MBMapContainer extends React.Component { } ); } + this.props.spatialFiltersLayer.syncLayerWithMB(this.state.mbMap); this._syncSettings(); } }, 256); @@ -260,9 +262,14 @@ export class MBMapContainer extends React.Component { }; _syncMbMapWithLayerList = () => { - removeOrphanedSourcesAndLayers(this.state.mbMap, this.props.layerList); + removeOrphanedSourcesAndLayers( + this.state.mbMap, + this.props.layerList, + this.props.spatialFiltersLayer + ); this.props.layerList.forEach(layer => layer.syncLayerWithMB(this.state.mbMap)); syncLayerOrderForSingleLayer(this.state.mbMap, this.props.layerList); + moveLayerToTop(this.state.mbMap, this.props.spatialFiltersLayer); }; _syncMbMapWithInspector = () => { diff --git a/x-pack/plugins/maps/public/connected_components/map_settings_panel/map_settings_panel.tsx b/x-pack/plugins/maps/public/connected_components/map_settings_panel/map_settings_panel.tsx index 36ed29e92cf69..a89f4461fff06 100644 --- a/x-pack/plugins/maps/public/connected_components/map_settings_panel/map_settings_panel.tsx +++ b/x-pack/plugins/maps/public/connected_components/map_settings_panel/map_settings_panel.tsx @@ -19,6 +19,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { MapSettings } from '../../reducers/map'; import { NavigationPanel } from './navigation_panel'; +import { SpatialFiltersPanel } from './spatial_filters_panel'; interface Props { cancelChanges: () => void; @@ -60,6 +61,8 @@ export function MapSettingsPanel({
+ +
diff --git a/x-pack/plugins/maps/public/connected_components/map_settings_panel/spatial_filters_panel.tsx b/x-pack/plugins/maps/public/connected_components/map_settings_panel/spatial_filters_panel.tsx new file mode 100644 index 0000000000000..cae703e982966 --- /dev/null +++ b/x-pack/plugins/maps/public/connected_components/map_settings_panel/spatial_filters_panel.tsx @@ -0,0 +1,98 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { EuiFormRow, EuiPanel, EuiSpacer, EuiSwitch, EuiSwitchEvent, EuiTitle } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { MapSettings } from '../../reducers/map'; +import { AlphaSlider } from '../../components/alpha_slider'; +import { MbValidatedColorPicker } from '../../layers/styles/vector/components/color/mb_validated_color_picker'; + +interface Props { + settings: MapSettings; + updateMapSetting: (settingKey: string, settingValue: string | number | boolean) => void; +} + +export function SpatialFiltersPanel({ settings, updateMapSetting }: Props) { + const onAlphaChange = (alpha: number) => { + updateMapSetting('spatialFiltersAlpa', alpha); + }; + + const onFillColorChange = (color: string) => { + updateMapSetting('spatialFiltersFillColor', color); + }; + + const onLineColorChange = (color: string) => { + updateMapSetting('spatialFiltersLineColor', color); + }; + + const onShowSpatialFiltersChange = (event: EuiSwitchEvent) => { + updateMapSetting('showSpatialFilters', event.target.checked); + }; + + const renderStyleInputs = () => { + if (!settings.showSpatialFilters) { + return null; + } + + return ( + <> + + + + + + + + + + + ); + }; + + return ( + + +
+ +
+
+ + + + + + {renderStyleInputs()} +
+ ); +} diff --git a/x-pack/plugins/maps/public/elasticsearch_geo_utils.js b/x-pack/plugins/maps/public/elasticsearch_geo_utils.js index 417c5d84f8916..888fce7e7afe0 100644 --- a/x-pack/plugins/maps/public/elasticsearch_geo_utils.js +++ b/x-pack/plugins/maps/public/elasticsearch_geo_utils.js @@ -18,6 +18,7 @@ import { } from '../common/constants'; import { getEsSpatialRelationLabel } from '../common/i18n_getters'; import { SPATIAL_FILTER_TYPE } from './kibana_services'; +import turfCircle from '@turf/circle'; function ensureGeoField(type) { const expectedTypes = [ES_GEO_FIELD_TYPE.GEO_POINT, ES_GEO_FIELD_TYPE.GEO_SHAPE]; @@ -330,7 +331,7 @@ export function createDistanceFilterWithMeta({ values: { distanceKm, geoFieldName, - pointLabel: point.join(','), + pointLabel: point.join(', '), }, }), }; @@ -451,3 +452,40 @@ export function clamp(val, min, max) { return val; } } + +export function extractFeaturesFromFilters(filters) { + const features = []; + filters + .filter(filter => { + return filter.meta.key && filter.meta.type === SPATIAL_FILTER_TYPE; + }) + .forEach(filter => { + let geometry; + if (filter.geo_distance && filter.geo_distance[filter.meta.key]) { + const distanceSplit = filter.geo_distance.distance.split('km'); + const distance = parseFloat(distanceSplit[0]); + const circleFeature = turfCircle(filter.geo_distance[filter.meta.key], distance); + geometry = circleFeature.geometry; + } else if ( + filter.geo_shape && + filter.geo_shape[filter.meta.key] && + filter.geo_shape[filter.meta.key].shape + ) { + geometry = filter.geo_shape[filter.meta.key].shape; + } else { + // do not know how to convert spatial filter to geometry + // this includes pre-indexed shapes + return; + } + + features.push({ + type: 'Feature', + geometry, + properties: { + filter: filter.meta.alias, + }, + }); + }); + + return features; +} diff --git a/x-pack/plugins/maps/public/elasticsearch_geo_utils.test.js b/x-pack/plugins/maps/public/elasticsearch_geo_utils.test.js index fc02e19173843..d13291a8e2ba5 100644 --- a/x-pack/plugins/maps/public/elasticsearch_geo_utils.test.js +++ b/x-pack/plugins/maps/public/elasticsearch_geo_utils.test.js @@ -19,6 +19,7 @@ import { createExtentFilter, convertMapExtentToPolygon, roundCoordinates, + extractFeaturesFromFilters, } from './elasticsearch_geo_utils'; import { indexPatterns } from '../../../../src/plugins/data/public'; @@ -503,3 +504,131 @@ describe('roundCoordinates', () => { ]); }); }); + +describe('extractFeaturesFromFilters', () => { + it('should ignore non-spatial filers', () => { + const phraseFilter = { + meta: { + alias: null, + disabled: false, + index: '90943e30-9a47-11e8-b64d-95841ca0b247', + key: 'machine.os', + negate: false, + params: { + query: 'ios', + }, + type: 'phrase', + }, + query: { + match_phrase: { + 'machine.os': 'ios', + }, + }, + }; + expect(extractFeaturesFromFilters([phraseFilter])).toEqual([]); + }); + + it('should convert geo_distance filter to feature', () => { + const spatialFilter = { + geo_distance: { + distance: '1096km', + 'geo.coordinates': [-89.87125, 53.49454], + }, + meta: { + alias: 'geo.coordinates within 1096km of -89.87125,53.49454', + disabled: false, + index: '90943e30-9a47-11e8-b64d-95841ca0b247', + key: 'geo.coordinates', + negate: false, + type: 'spatial_filter', + value: '', + }, + }; + + const features = extractFeaturesFromFilters([spatialFilter]); + expect(features[0].geometry.coordinates[0][0]).toEqual([-89.87125, 63.35109118642093]); + expect(features[0].properties).toEqual({ + filter: 'geo.coordinates within 1096km of -89.87125,53.49454', + }); + }); + + it('should convert geo_shape filter to feature', () => { + const spatialFilter = { + geo_shape: { + 'geo.coordinates': { + relation: 'INTERSECTS', + shape: { + coordinates: [ + [ + [-101.21639, 48.1413], + [-101.21639, 41.84905], + [-90.95149, 41.84905], + [-90.95149, 48.1413], + [-101.21639, 48.1413], + ], + ], + type: 'Polygon', + }, + }, + ignore_unmapped: true, + }, + meta: { + alias: 'geo.coordinates in bounds', + disabled: false, + index: '90943e30-9a47-11e8-b64d-95841ca0b247', + key: 'geo.coordinates', + negate: false, + type: 'spatial_filter', + value: '', + }, + }; + + expect(extractFeaturesFromFilters([spatialFilter])).toEqual([ + { + type: 'Feature', + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-101.21639, 48.1413], + [-101.21639, 41.84905], + [-90.95149, 41.84905], + [-90.95149, 48.1413], + [-101.21639, 48.1413], + ], + ], + }, + properties: { + filter: 'geo.coordinates in bounds', + }, + }, + ]); + }); + + it('should ignore geo_shape filter with pre-index shape', () => { + const spatialFilter = { + geo_shape: { + 'geo.coordinates': { + indexed_shape: { + id: 's5gldXEBkTB2HMwpC8y0', + index: 'world_countries_v1', + path: 'coordinates', + }, + relation: 'INTERSECTS', + }, + ignore_unmapped: true, + }, + meta: { + alias: 'geo.coordinates in multipolygon', + disabled: false, + index: '90943e30-9a47-11e8-b64d-95841ca0b247', + key: 'geo.coordinates', + negate: false, + type: 'spatial_filter', + value: '', + }, + }; + + expect(extractFeaturesFromFilters([spatialFilter])).toEqual([]); + }); +}); diff --git a/x-pack/plugins/maps/public/kibana_services.d.ts b/x-pack/plugins/maps/public/kibana_services.d.ts index 867557c296292..3d346fe1acdd5 100644 --- a/x-pack/plugins/maps/public/kibana_services.d.ts +++ b/x-pack/plugins/maps/public/kibana_services.d.ts @@ -3,8 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - -import { IIndexPattern } from 'src/plugins/data/public'; +import { IIndexPattern, DataPublicPluginStart } from 'src/plugins/data/public'; export function getLicenseId(): any; export function getInspector(): any; @@ -30,6 +29,7 @@ export function getUiActions(): any; export function getCore(): any; export function getNavigation(): any; export function getCoreI18n(): any; +export function getSearchService(): DataPublicPluginStart['search']; export function setLicenseId(args: unknown): void; export function setInspector(args: unknown): void; @@ -53,3 +53,4 @@ export function setUiActions(args: unknown): void; export function setCore(args: unknown): void; export function setNavigation(args: unknown): void; export function setCoreI18n(args: unknown): void; +export function setSearchService(args: DataPublicPluginStart['search']): void; diff --git a/x-pack/plugins/maps/public/kibana_services.js b/x-pack/plugins/maps/public/kibana_services.js index dcbd54a09381f..431d7a3b339b7 100644 --- a/x-pack/plugins/maps/public/kibana_services.js +++ b/x-pack/plugins/maps/public/kibana_services.js @@ -5,8 +5,6 @@ */ import { esFilters, search } from '../../../../src/plugins/data/public'; -export { SearchSource } from '../../../../src/plugins/data/public'; - export const SPATIAL_FILTER_TYPE = esFilters.FILTERS.SPATIAL_FILTER; const { getRequestInspectorStats, getResponseInspectorStats } = search; @@ -137,3 +135,7 @@ export const getNavigation = () => navigation; let coreI18n; export const setCoreI18n = kibanaCoreI18n => (coreI18n = kibanaCoreI18n); export const getCoreI18n = () => coreI18n; + +let dataSearchService; +export const setSearchService = searchService => (dataSearchService = searchService); +export const getSearchService = () => dataSearchService; diff --git a/x-pack/plugins/maps/public/layers/blended_vector_layer.ts b/x-pack/plugins/maps/public/layers/blended_vector_layer.ts index 9a9ea2968ceeb..1fc3ad203706f 100644 --- a/x-pack/plugins/maps/public/layers/blended_vector_layer.ts +++ b/x-pack/plugins/maps/public/layers/blended_vector_layer.ts @@ -24,7 +24,7 @@ import { } from '../../common/constants'; import { ESGeoGridSource } from './sources/es_geo_grid_source/es_geo_grid_source'; import { canSkipSourceUpdate } from './util/can_skip_fetch'; -import { IVectorLayer, VectorLayerArguments } from './vector_layer'; +import { IVectorLayer } from './vector_layer'; import { IESSource } from './sources/es_source'; import { IESAggSource } from './sources/es_agg_source'; import { ISource } from './sources/source'; @@ -36,6 +36,8 @@ import { DynamicStylePropertyOptions, VectorLayerDescriptor, } from '../../common/descriptor_types'; +import { IStyle } from './styles/style'; +import { IVectorSource } from './sources/vector_source'; const ACTIVE_COUNT_DATA_ID = 'ACTIVE_COUNT_DATA_ID'; @@ -145,6 +147,11 @@ function getClusterStyleDescriptor( return clusterStyleDescriptor; } +export interface BlendedVectorLayerArguments { + source: IVectorSource; + layerDescriptor: VectorLayerDescriptor; +} + export class BlendedVectorLayer extends VectorLayer implements IVectorLayer { static type = LAYER_TYPE.BLENDED_VECTOR; @@ -163,11 +170,14 @@ export class BlendedVectorLayer extends VectorLayer implements IVectorLayer { private readonly _documentSource: IESSource; private readonly _documentStyle: IVectorStyle; - constructor(options: VectorLayerArguments) { - super(options); + constructor(options: BlendedVectorLayerArguments) { + super({ + ...options, + joins: [], + }); this._documentSource = this._source as IESSource; // VectorLayer constructor sets _source as document source - this._documentStyle = this._style; // VectorLayer constructor sets _style as document source + this._documentStyle = this._style as IVectorStyle; // VectorLayer constructor sets _style as document source this._clusterSource = getClusterSource(this._documentSource, this._documentStyle); const clusterStyleDescriptor = getClusterStyleDescriptor( @@ -229,11 +239,11 @@ export class BlendedVectorLayer extends VectorLayer implements IVectorLayer { return this._documentSource; } - getCurrentStyle() { + getCurrentStyle(): IStyle { return this._isClustered ? this._clusterStyle : this._documentStyle; } - getStyleForEditing() { + getStyleForEditing(): IStyle { return this._documentStyle; } @@ -242,8 +252,8 @@ export class BlendedVectorLayer extends VectorLayer implements IVectorLayer { const requestToken = Symbol(`layer-active-count:${this.getId()}`); const searchFilters = this._getSearchFilters( syncContext.dataFilters, - this.getSource(), - this.getCurrentStyle() + this.getSource() as IVectorSource, + this.getCurrentStyle() as IVectorStyle ); const canSkipFetch = await canSkipSourceUpdate({ source: this.getSource(), diff --git a/x-pack/plugins/maps/public/layers/layer.d.ts b/x-pack/plugins/maps/public/layers/layer.d.ts deleted file mode 100644 index e8fc5d473626c..0000000000000 --- a/x-pack/plugins/maps/public/layers/layer.d.ts +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import { LayerDescriptor, MapExtent, MapFilters, MapQuery } from '../../common/descriptor_types'; -import { ISource } from './sources/source'; -import { DataRequest } from './util/data_request'; -import { SyncContext } from '../actions/map_actions'; - -export interface ILayer { - getBounds(mapFilters: MapFilters): Promise; - getDataRequest(id: string): DataRequest | undefined; - getDisplayName(source?: ISource): Promise; - getId(): string; - getSourceDataRequest(): DataRequest | undefined; - getSource(): ISource; - getSourceForEditing(): ISource; - syncData(syncContext: SyncContext): Promise; - isVisible(): boolean; - showAtZoomLevel(zoomLevel: number): boolean; - getMinZoom(): number; - getMaxZoom(): number; - getMinSourceZoom(): number; -} - -export interface ILayerArguments { - layerDescriptor: LayerDescriptor; - source: ISource; -} - -export class AbstractLayer implements ILayer { - static createDescriptor(options: Partial, mapColors?: string[]): LayerDescriptor; - constructor(layerArguments: ILayerArguments); - getBounds(mapFilters: MapFilters): Promise; - getDataRequest(id: string): DataRequest | undefined; - getDisplayName(source?: ISource): Promise; - getId(): string; - getSourceDataRequest(): DataRequest | undefined; - getSource(): ISource; - getSourceForEditing(): ISource; - syncData(syncContext: SyncContext): Promise; - isVisible(): boolean; - showAtZoomLevel(zoomLevel: number): boolean; - getMinZoom(): number; - getMaxZoom(): number; - getMinSourceZoom(): number; - getQuery(): MapQuery; - _removeStaleMbSourcesAndLayers(mbMap: unknown): void; - _requiresPrevSourceCleanup(mbMap: unknown): boolean; -} diff --git a/x-pack/plugins/maps/public/layers/layer.js b/x-pack/plugins/maps/public/layers/layer.js deleted file mode 100644 index 9362ce2c028e6..0000000000000 --- a/x-pack/plugins/maps/public/layers/layer.js +++ /dev/null @@ -1,373 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import _ from 'lodash'; -import React from 'react'; -import { EuiIcon, EuiLoadingSpinner } from '@elastic/eui'; -import { DataRequest } from './util/data_request'; -import { - MAX_ZOOM, - MB_SOURCE_ID_LAYER_ID_PREFIX_DELIMITER, - MIN_ZOOM, - SOURCE_DATA_ID_ORIGIN, -} from '../../common/constants'; -import uuid from 'uuid/v4'; - -import { copyPersistentState } from '../reducers/util.js'; -import { i18n } from '@kbn/i18n'; - -export class AbstractLayer { - constructor({ layerDescriptor, source }) { - this._descriptor = AbstractLayer.createDescriptor(layerDescriptor); - this._source = source; - if (this._descriptor.__dataRequests) { - this._dataRequests = this._descriptor.__dataRequests.map( - dataRequest => new DataRequest(dataRequest) - ); - } else { - this._dataRequests = []; - } - } - - static getBoundDataForSource(mbMap, sourceId) { - const mbStyle = mbMap.getStyle(); - return mbStyle.sources[sourceId].data; - } - - static createDescriptor(options = {}) { - const layerDescriptor = { ...options }; - - layerDescriptor.__dataRequests = _.get(options, '__dataRequests', []); - layerDescriptor.id = _.get(options, 'id', uuid()); - layerDescriptor.label = options.label && options.label.length > 0 ? options.label : null; - layerDescriptor.minZoom = _.get(options, 'minZoom', MIN_ZOOM); - layerDescriptor.maxZoom = _.get(options, 'maxZoom', MAX_ZOOM); - layerDescriptor.alpha = _.get(options, 'alpha', 0.75); - layerDescriptor.visible = _.get(options, 'visible', true); - layerDescriptor.style = _.get(options, 'style', {}); - - return layerDescriptor; - } - - destroy() { - if (this._source) { - this._source.destroy(); - } - } - - async cloneDescriptor() { - const clonedDescriptor = copyPersistentState(this._descriptor); - // layer id is uuid used to track styles/layers in mapbox - clonedDescriptor.id = uuid(); - const displayName = await this.getDisplayName(); - clonedDescriptor.label = `Clone of ${displayName}`; - clonedDescriptor.sourceDescriptor = this.getSource().cloneDescriptor(); - if (clonedDescriptor.joins) { - clonedDescriptor.joins.forEach(joinDescriptor => { - // right.id is uuid used to track requests in inspector - joinDescriptor.right.id = uuid(); - }); - } - return clonedDescriptor; - } - - makeMbLayerId(layerNameSuffix) { - return `${this.getId()}${MB_SOURCE_ID_LAYER_ID_PREFIX_DELIMITER}${layerNameSuffix}`; - } - - isJoinable() { - return this.getSource().isJoinable(); - } - - supportsElasticsearchFilters() { - return this.getSource().isESSource(); - } - - async supportsFitToBounds() { - return await this.getSource().supportsFitToBounds(); - } - - async getDisplayName(source) { - if (this._descriptor.label) { - return this._descriptor.label; - } - - const sourceDisplayName = source - ? await source.getDisplayName() - : await this.getSource().getDisplayName(); - return sourceDisplayName || `Layer ${this._descriptor.id}`; - } - - async getAttributions() { - if (!this.hasErrors()) { - return await this.getSource().getAttributions(); - } - return []; - } - - getLabel() { - return this._descriptor.label ? this._descriptor.label : ''; - } - - getCustomIconAndTooltipContent() { - return { - icon: , - }; - } - - getIconAndTooltipContent(zoomLevel, isUsingSearch) { - let icon; - let tooltipContent = null; - const footnotes = []; - if (this.hasErrors()) { - icon = ( - - ); - tooltipContent = this.getErrors(); - } else if (this.isLayerLoading()) { - icon = ; - } else if (!this.isVisible()) { - icon = ; - tooltipContent = i18n.translate('xpack.maps.layer.layerHiddenTooltip', { - defaultMessage: `Layer is hidden.`, - }); - } else if (!this.showAtZoomLevel(zoomLevel)) { - const minZoom = this.getMinZoom(); - const maxZoom = this.getMaxZoom(); - icon = ; - tooltipContent = i18n.translate('xpack.maps.layer.zoomFeedbackTooltip', { - defaultMessage: `Layer is visible between zoom levels {minZoom} and {maxZoom}.`, - values: { minZoom, maxZoom }, - }); - } else { - const customIconAndTooltipContent = this.getCustomIconAndTooltipContent(); - if (customIconAndTooltipContent) { - icon = customIconAndTooltipContent.icon; - if (!customIconAndTooltipContent.areResultsTrimmed) { - tooltipContent = customIconAndTooltipContent.tooltipContent; - } else { - footnotes.push({ - icon: , - message: customIconAndTooltipContent.tooltipContent, - }); - } - } - - if (isUsingSearch && this.getQueryableIndexPatternIds().length) { - footnotes.push({ - icon: , - message: i18n.translate('xpack.maps.layer.isUsingSearchMsg', { - defaultMessage: 'Results narrowed by search bar', - }), - }); - } - } - - return { - icon, - tooltipContent, - footnotes, - }; - } - - async hasLegendDetails() { - return false; - } - - renderLegendDetails() { - return null; - } - - getId() { - return this._descriptor.id; - } - - getSource() { - return this._source; - } - - getSourceForEditing() { - return this._source; - } - - isVisible() { - return this._descriptor.visible; - } - - showAtZoomLevel(zoom) { - return zoom >= this.getMinZoom() && zoom <= this.getMaxZoom(); - } - - getMinZoom() { - return this._descriptor.minZoom; - } - - getMaxZoom() { - return this._descriptor.maxZoom; - } - - getMinSourceZoom() { - return this._source.getMinZoom(); - } - - _requiresPrevSourceCleanup() { - return false; - } - - _removeStaleMbSourcesAndLayers(mbMap) { - if (this._requiresPrevSourceCleanup(mbMap)) { - const mbStyle = mbMap.getStyle(); - mbStyle.layers.forEach(mbLayer => { - if (this.ownsMbLayerId(mbLayer.id)) { - mbMap.removeLayer(mbLayer.id); - } - }); - Object.keys(mbStyle.sources).some(mbSourceId => { - if (this.ownsMbSourceId(mbSourceId)) { - mbMap.removeSource(mbSourceId); - } - }); - } - } - - getAlpha() { - return this._descriptor.alpha; - } - - getQuery() { - return this._descriptor.query; - } - - getCurrentStyle() { - return this._style; - } - - getStyleForEditing() { - return this._style; - } - - async getImmutableSourceProperties() { - return this.getSource().getImmutableProperties(); - } - - renderSourceSettingsEditor = ({ onChange }) => { - return this.getSourceForEditing().renderSourceSettingsEditor({ onChange }); - }; - - getPrevRequestToken(dataId) { - const prevDataRequest = this.getDataRequest(dataId); - if (!prevDataRequest) { - return; - } - - return prevDataRequest.getRequestToken(); - } - - getInFlightRequestTokens() { - if (!this._dataRequests) { - return []; - } - - const requestTokens = this._dataRequests.map(dataRequest => dataRequest.getRequestToken()); - return _.compact(requestTokens); - } - - getSourceDataRequest() { - return this.getDataRequest(SOURCE_DATA_ID_ORIGIN); - } - - getDataRequest(id) { - return this._dataRequests.find(dataRequest => dataRequest.getDataId() === id); - } - - isLayerLoading() { - return this._dataRequests.some(dataRequest => dataRequest.isLoading()); - } - - hasErrors() { - return _.get(this._descriptor, '__isInErrorState', false); - } - - getErrors() { - return this.hasErrors() ? this._descriptor.__errorMessage : ''; - } - - toLayerDescriptor() { - return this._descriptor; - } - - async syncData() { - //no-op by default - } - - getMbLayerIds() { - throw new Error('Should implement AbstractLayer#getMbLayerIds'); - } - - ownsMbLayerId() { - throw new Error('Should implement AbstractLayer#ownsMbLayerId'); - } - - ownsMbSourceId() { - throw new Error('Should implement AbstractLayer#ownsMbSourceId'); - } - - canShowTooltip() { - return false; - } - - syncLayerWithMB() { - throw new Error('Should implement AbstractLayer#syncLayerWithMB'); - } - - getLayerTypeIconName() { - throw new Error('should implement Layer#getLayerTypeIconName'); - } - - isDataLoaded() { - const sourceDataRequest = this.getSourceDataRequest(); - return sourceDataRequest && sourceDataRequest.hasData(); - } - - async getBounds(/* mapFilters: MapFilters */) { - return { - minLon: -180, - maxLon: 180, - minLat: -89, - maxLat: 89, - }; - } - - renderStyleEditor({ onStyleDescriptorChange }) { - const style = this.getStyleForEditing(); - if (!style) { - return null; - } - return style.renderEditor({ layer: this, onStyleDescriptorChange }); - } - - getIndexPatternIds() { - return []; - } - - getQueryableIndexPatternIds() { - return []; - } - - syncVisibilityWithMb(mbMap, mbLayerId) { - mbMap.setLayoutProperty(mbLayerId, 'visibility', this.isVisible() ? 'visible' : 'none'); - } - - getType() { - return this._descriptor.type; - } -} diff --git a/x-pack/plugins/maps/public/layers/layer.tsx b/x-pack/plugins/maps/public/layers/layer.tsx new file mode 100644 index 0000000000000..ce48793e1481b --- /dev/null +++ b/x-pack/plugins/maps/public/layers/layer.tsx @@ -0,0 +1,490 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +/* eslint-disable @typescript-eslint/consistent-type-definitions */ + +import { Query } from 'src/plugins/data/public'; +import _ from 'lodash'; +import React, { ReactElement } from 'react'; +import { EuiIcon, EuiLoadingSpinner } from '@elastic/eui'; +import uuid from 'uuid/v4'; +import { i18n } from '@kbn/i18n'; +import { FeatureCollection } from 'geojson'; +import { DataRequest } from './util/data_request'; +import { + MAX_ZOOM, + MB_SOURCE_ID_LAYER_ID_PREFIX_DELIMITER, + MIN_ZOOM, + SOURCE_DATA_ID_ORIGIN, +} from '../../common/constants'; +// @ts-ignore +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { copyPersistentState } from '../reducers/util.js'; +import { + LayerDescriptor, + MapExtent, + MapFilters, + StyleDescriptor, +} from '../../common/descriptor_types'; +import { Attribution, ImmutableSourceProperty, ISource } from './sources/source'; +import { SyncContext } from '../actions/map_actions'; +import { IStyle } from './styles/style'; + +export interface ILayer { + getBounds(mapFilters: MapFilters): Promise; + getDataRequest(id: string): DataRequest | undefined; + getDisplayName(source?: ISource): Promise; + getId(): string; + getSourceDataRequest(): DataRequest | undefined; + getSource(): ISource; + getSourceForEditing(): ISource; + syncData(syncContext: SyncContext): void; + supportsElasticsearchFilters(): boolean; + supportsFitToBounds(): Promise; + getAttributions(): Promise; + getLabel(): string; + getCustomIconAndTooltipContent(): IconAndTooltipContent; + getIconAndTooltipContent(zoomLevel: number, isUsingSearch: boolean): IconAndTooltipContent; + renderLegendDetails(): ReactElement | null; + showAtZoomLevel(zoom: number): boolean; + getMinZoom(): number; + getMaxZoom(): number; + getMinSourceZoom(): number; + getAlpha(): number; + getQuery(): Query | null; + getStyle(): IStyle; + getStyleForEditing(): IStyle; + getCurrentStyle(): IStyle; + getImmutableSourceProperties(): Promise; + renderSourceSettingsEditor({ onChange }: { onChange: () => void }): ReactElement | null; + isLayerLoading(): boolean; + hasErrors(): boolean; + getErrors(): string; + toLayerDescriptor(): LayerDescriptor; + getMbLayerIds(): string[]; + ownsMbLayerId(mbLayerId: string): boolean; + ownsMbSourceId(mbSourceId: string): boolean; + canShowTooltip(): boolean; + syncLayerWithMB(mbMap: unknown): void; + getLayerTypeIconName(): string; + isDataLoaded(): boolean; + getIndexPatternIds(): string[]; + getQueryableIndexPatternIds(): string[]; + getType(): string | undefined; + isVisible(): boolean; + cloneDescriptor(): Promise; + renderStyleEditor({ + onStyleDescriptorChange, + }: { + onStyleDescriptorChange: (styleDescriptor: StyleDescriptor) => void; + }): ReactElement | null; +} +export type Footnote = { + icon: ReactElement; + message?: string | null; +}; +export type IconAndTooltipContent = { + icon?: ReactElement | null; + tooltipContent?: string | null; + footnotes?: Footnote[] | null; + areResultsTrimmed?: boolean; +}; + +export interface ILayerArguments { + layerDescriptor: LayerDescriptor; + source: ISource; + style: IStyle; +} + +export class AbstractLayer implements ILayer { + protected readonly _descriptor: LayerDescriptor; + protected readonly _source: ISource; + protected readonly _style: IStyle; + protected readonly _dataRequests: DataRequest[]; + + static createDescriptor(options: Partial): LayerDescriptor { + return { + ...options, + sourceDescriptor: options.sourceDescriptor ? options.sourceDescriptor : null, + __dataRequests: _.get(options, '__dataRequests', []), + id: _.get(options, 'id', uuid()), + label: options.label && options.label.length > 0 ? options.label : null, + minZoom: _.get(options, 'minZoom', MIN_ZOOM), + maxZoom: _.get(options, 'maxZoom', MAX_ZOOM), + alpha: _.get(options, 'alpha', 0.75), + visible: _.get(options, 'visible', true), + style: _.get(options, 'style', null), + }; + } + + destroy() { + if (this._source) { + this._source.destroy(); + } + } + + constructor({ layerDescriptor, source, style }: ILayerArguments) { + this._descriptor = AbstractLayer.createDescriptor(layerDescriptor); + this._source = source; + this._style = style; + if (this._descriptor.__dataRequests) { + this._dataRequests = this._descriptor.__dataRequests.map( + dataRequest => new DataRequest(dataRequest) + ); + } else { + this._dataRequests = []; + } + } + + static getBoundDataForSource(mbMap: unknown, sourceId: string): FeatureCollection { + // @ts-ignore + const mbStyle = mbMap.getStyle(); + return mbStyle.sources[sourceId].data; + } + + async cloneDescriptor(): Promise { + // @ts-ignore + const clonedDescriptor = copyPersistentState(this._descriptor); + // layer id is uuid used to track styles/layers in mapbox + clonedDescriptor.id = uuid(); + const displayName = await this.getDisplayName(); + clonedDescriptor.label = `Clone of ${displayName}`; + clonedDescriptor.sourceDescriptor = this.getSource().cloneDescriptor(); + + // todo: remove this + // This should not be in AbstractLayer. It relies on knowledge of VectorLayerDescriptor + // @ts-ignore + if (clonedDescriptor.joins) { + // @ts-ignore + clonedDescriptor.joins.forEach(joinDescriptor => { + // right.id is uuid used to track requests in inspector + // @ts-ignore + joinDescriptor.right.id = uuid(); + }); + } + return clonedDescriptor; + } + + makeMbLayerId(layerNameSuffix: string): string { + return `${this.getId()}${MB_SOURCE_ID_LAYER_ID_PREFIX_DELIMITER}${layerNameSuffix}`; + } + + isJoinable(): boolean { + return this.getSource().isJoinable(); + } + + supportsElasticsearchFilters(): boolean { + return this.getSource().isESSource(); + } + + async supportsFitToBounds(): Promise { + return await this.getSource().supportsFitToBounds(); + } + + async getDisplayName(source?: ISource): Promise { + if (this._descriptor.label) { + return this._descriptor.label; + } + + const sourceDisplayName = source + ? await source.getDisplayName() + : await this.getSource().getDisplayName(); + return sourceDisplayName || `Layer ${this._descriptor.id}`; + } + + async getAttributions(): Promise { + if (!this.hasErrors()) { + return await this.getSource().getAttributions(); + } + return []; + } + + getStyleForEditing(): IStyle { + return this._style; + } + + getStyle() { + return this._style; + } + + getLabel(): string { + return this._descriptor.label ? this._descriptor.label : ''; + } + + getCustomIconAndTooltipContent(): IconAndTooltipContent { + return { + icon: , + }; + } + + getIconAndTooltipContent(zoomLevel: number, isUsingSearch: boolean): IconAndTooltipContent { + let icon; + let tooltipContent = null; + const footnotes = []; + if (this.hasErrors()) { + icon = ( + + ); + tooltipContent = this.getErrors(); + } else if (this.isLayerLoading()) { + icon = ; + } else if (!this.isVisible()) { + icon = ; + tooltipContent = i18n.translate('xpack.maps.layer.layerHiddenTooltip', { + defaultMessage: `Layer is hidden.`, + }); + } else if (!this.showAtZoomLevel(zoomLevel)) { + const minZoom = this.getMinZoom(); + const maxZoom = this.getMaxZoom(); + icon = ; + tooltipContent = i18n.translate('xpack.maps.layer.zoomFeedbackTooltip', { + defaultMessage: `Layer is visible between zoom levels {minZoom} and {maxZoom}.`, + values: { minZoom, maxZoom }, + }); + } else { + const customIconAndTooltipContent = this.getCustomIconAndTooltipContent(); + if (customIconAndTooltipContent) { + icon = customIconAndTooltipContent.icon; + if (!customIconAndTooltipContent.areResultsTrimmed) { + tooltipContent = customIconAndTooltipContent.tooltipContent; + } else { + footnotes.push({ + icon: , + message: customIconAndTooltipContent.tooltipContent, + }); + } + } + + if (isUsingSearch && this.getQueryableIndexPatternIds().length) { + footnotes.push({ + icon: , + message: i18n.translate('xpack.maps.layer.isUsingSearchMsg', { + defaultMessage: 'Results narrowed by search bar', + }), + }); + } + } + + return { + icon, + tooltipContent, + footnotes, + }; + } + + async hasLegendDetails(): Promise { + return false; + } + + renderLegendDetails(): ReactElement | null { + return null; + } + + getId(): string { + return this._descriptor.id; + } + + getSource(): ISource { + return this._source; + } + + getSourceForEditing(): ISource { + return this._source; + } + + isVisible(): boolean { + return !!this._descriptor.visible; + } + + showAtZoomLevel(zoom: number): boolean { + return zoom >= this.getMinZoom() && zoom <= this.getMaxZoom(); + } + + getMinZoom(): number { + return typeof this._descriptor.minZoom === 'number' ? this._descriptor.minZoom : MIN_ZOOM; + } + + getMaxZoom(): number { + return typeof this._descriptor.maxZoom === 'number' ? this._descriptor.maxZoom : MAX_ZOOM; + } + + getMinSourceZoom(): number { + return this._source.getMinZoom(); + } + + _requiresPrevSourceCleanup(mbMap: unknown) { + return false; + } + + _removeStaleMbSourcesAndLayers(mbMap: unknown) { + if (this._requiresPrevSourceCleanup(mbMap)) { + // @ts-ignore + const mbStyle = mbMap.getStyle(); + // @ts-ignore + mbStyle.layers.forEach(mbLayer => { + // @ts-ignore + if (this.ownsMbLayerId(mbLayer.id)) { + // @ts-ignore + mbMap.removeLayer(mbLayer.id); + } + }); + // @ts-ignore + Object.keys(mbStyle.sources).some(mbSourceId => { + // @ts-ignore + if (this.ownsMbSourceId(mbSourceId)) { + // @ts-ignore + mbMap.removeSource(mbSourceId); + } + }); + } + } + + getAlpha(): number { + return typeof this._descriptor.alpha === 'number' ? this._descriptor.alpha : 1; + } + + getQuery(): Query | null { + return this._descriptor.query ? this._descriptor.query : null; + } + + getCurrentStyle(): IStyle { + return this._style; + } + + async getImmutableSourceProperties() { + const source = this.getSource(); + return await source.getImmutableProperties(); + } + + renderSourceSettingsEditor({ onChange }: { onChange: () => void }) { + const source = this.getSourceForEditing(); + return source.renderSourceSettingsEditor({ onChange }); + } + + getPrevRequestToken(dataId: string): symbol | undefined { + const prevDataRequest = this.getDataRequest(dataId); + if (!prevDataRequest) { + return; + } + + return prevDataRequest.getRequestToken(); + } + + getInFlightRequestTokens(): symbol[] { + if (!this._dataRequests) { + return []; + } + + const requestTokens = this._dataRequests.map(dataRequest => dataRequest.getRequestToken()); + + // Compact removes all the undefineds + // @ts-ignore + return _.compact(requestTokens); + } + + getSourceDataRequest(): DataRequest | undefined { + return this.getDataRequest(SOURCE_DATA_ID_ORIGIN); + } + + getDataRequest(id: string): DataRequest | undefined { + return this._dataRequests.find(dataRequest => dataRequest.getDataId() === id); + } + + isLayerLoading(): boolean { + return this._dataRequests.some(dataRequest => dataRequest.isLoading()); + } + + hasErrors(): boolean { + return _.get(this._descriptor, '__isInErrorState', false); + } + + getErrors(): string { + return this.hasErrors() && this._descriptor.__errorMessage + ? this._descriptor.__errorMessage + : ''; + } + + toLayerDescriptor(): LayerDescriptor { + return this._descriptor; + } + + async syncData(syncContext: SyncContext) { + // no-op by default + } + + getMbLayerIds(): string[] { + throw new Error('Should implement AbstractLayer#getMbLayerIds'); + } + + ownsMbLayerId(layerId: string): boolean { + throw new Error('Should implement AbstractLayer#ownsMbLayerId'); + } + + ownsMbSourceId(sourceId: string): boolean { + throw new Error('Should implement AbstractLayer#ownsMbSourceId'); + } + + canShowTooltip() { + return false; + } + + syncLayerWithMB(mbMap: unknown) { + throw new Error('Should implement AbstractLayer#syncLayerWithMB'); + } + + getLayerTypeIconName(): string { + throw new Error('should implement Layer#getLayerTypeIconName'); + } + + isDataLoaded(): boolean { + const sourceDataRequest = this.getSourceDataRequest(); + return sourceDataRequest ? sourceDataRequest.hasData() : false; + } + + async getBounds(mapFilters: MapFilters): Promise { + return { + minLon: -180, + maxLon: 180, + minLat: -89, + maxLat: 89, + }; + } + + renderStyleEditor({ + onStyleDescriptorChange, + }: { + onStyleDescriptorChange: (styleDescriptor: StyleDescriptor) => void; + }): ReactElement | null { + const style = this.getStyleForEditing(); + if (!style) { + return null; + } + return style.renderEditor({ layer: this, onStyleDescriptorChange }); + } + + getIndexPatternIds(): string[] { + return []; + } + + getQueryableIndexPatternIds(): string[] { + return []; + } + + syncVisibilityWithMb(mbMap: unknown, mbLayerId: string) { + // @ts-ignore + mbMap.setLayoutProperty(mbLayerId, 'visibility', this.isVisible() ? 'visible' : 'none'); + } + + getType(): string | undefined { + return this._descriptor.type; + } +} diff --git a/x-pack/plugins/maps/public/layers/sources/client_file_source/geojson_file_source.js b/x-pack/plugins/maps/public/layers/sources/client_file_source/geojson_file_source.js index 137513ad7c612..36f898f723757 100644 --- a/x-pack/plugins/maps/public/layers/sources/client_file_source/geojson_file_source.js +++ b/x-pack/plugins/maps/public/layers/sources/client_file_source/geojson_file_source.js @@ -21,8 +21,6 @@ import { registerSource } from '../source_registry'; export class GeojsonFileSource extends AbstractVectorSource { static type = SOURCE_TYPES.GEOJSON_FILE; - static isIndexingSource = true; - static createDescriptor(geoJson, name) { // Wrap feature as feature collection if needed let featureCollection; @@ -70,7 +68,7 @@ export class GeojsonFileSource extends AbstractVectorSource { } shouldBeIndexed() { - return GeojsonFileSource.isIndexingSource; + return true; } } diff --git a/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.d.ts b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.d.ts index 96347c444dd5b..51ee15e7ea5af 100644 --- a/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.d.ts +++ b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.d.ts @@ -6,7 +6,7 @@ import { AbstractESAggSource } from '../es_agg_source'; import { ESGeoGridSourceDescriptor } from '../../../../common/descriptor_types'; -import { GRID_RESOLUTION, RENDER_AS } from '../../../../common/constants'; +import { GRID_RESOLUTION } from '../../../../common/constants'; export class ESGeoGridSource extends AbstractESAggSource { static createDescriptor({ @@ -14,12 +14,7 @@ export class ESGeoGridSource extends AbstractESAggSource { geoField, requestType, resolution, - }: { - indexPatternId: string; - geoField: string; - requestType: RENDER_AS; - resolution?: GRID_RESOLUTION; - }): ESGeoGridSourceDescriptor; + }: Partial): ESGeoGridSourceDescriptor; constructor(sourceDescriptor: ESGeoGridSourceDescriptor, inspectorAdapters: unknown); diff --git a/x-pack/plugins/maps/public/layers/sources/es_search_source/es_search_source.js b/x-pack/plugins/maps/public/layers/sources/es_search_source/es_search_source.js index aaa56b30c735a..bfbcca1eb3f61 100644 --- a/x-pack/plugins/maps/public/layers/sources/es_search_source/es_search_source.js +++ b/x-pack/plugins/maps/public/layers/sources/es_search_source/es_search_source.js @@ -10,7 +10,7 @@ import uuid from 'uuid/v4'; import { VECTOR_SHAPE_TYPES } from '../vector_feature_types'; import { AbstractESSource } from '../es_source'; -import { SearchSource } from '../../../kibana_services'; +import { getSearchService } from '../../../kibana_services'; import { VectorStyle } from '../../styles/vector/vector_style'; import { VectorLayer } from '../../vector_layer'; import { hitsToGeoJson } from '../../../elasticsearch_geo_utils'; @@ -51,7 +51,7 @@ function getDocValueAndSourceFields(indexPattern, fieldNames) { lang: field.lang, }, }; - } else if (field.type !== ES_GEO_FIELD_TYPE.GEO_SHAPE && field.readFromDocValues) { + } else if (field.readFromDocValues) { const docValueField = field.type === 'date' ? { @@ -427,13 +427,17 @@ export class ESSearchSource extends AbstractESSource { return {}; } - const searchSource = new SearchSource(); + const searchService = getSearchService(); + const searchSource = searchService.searchSource.create(); + searchSource.setField('index', indexPattern); searchSource.setField('size', 1); + const query = { language: 'kuery', query: `_id:"${docId}" and _index:"${index}"`, }; + searchSource.setField('query', query); searchSource.setField('fields', this._getTooltipPropertyNames()); diff --git a/x-pack/plugins/maps/public/layers/sources/es_source/es_source.d.ts b/x-pack/plugins/maps/public/layers/sources/es_source/es_source.d.ts index 092dc3bf0d5a8..d95ec5a64e6c3 100644 --- a/x-pack/plugins/maps/public/layers/sources/es_source/es_source.d.ts +++ b/x-pack/plugins/maps/public/layers/sources/es_source/es_source.d.ts @@ -6,8 +6,10 @@ import { AbstractVectorSource } from '../vector_source'; import { IVectorSource } from '../vector_source'; -import { IndexPattern, SearchSource } from '../../../../../../../src/plugins/data/public'; +import { IndexPattern, ISearchSource } from '../../../../../../../src/plugins/data/public'; import { VectorSourceRequestMeta } from '../../../../common/descriptor_types'; +import { VectorStyle } from '../../styles/vector/vector_style'; +import { IDynamicStyleProperty } from '../../styles/vector/properties/dynamic_style_property'; export interface IESSource extends IVectorSource { getId(): string; @@ -19,7 +21,14 @@ export interface IESSource extends IVectorSource { searchFilters: VectorSourceRequestMeta, limit: number, initialSearchContext?: object - ): Promise; + ): Promise; + loadStylePropsMeta( + layerName: string, + style: VectorStyle, + dynamicStyleProps: IDynamicStyleProperty[], + registerCancelCallback: (requestToken: symbol, callback: () => void) => void, + searchFilters: VectorSourceRequestMeta + ): Promise; } export class AbstractESSource extends AbstractVectorSource implements IESSource { @@ -32,5 +41,12 @@ export class AbstractESSource extends AbstractVectorSource implements IESSource searchFilters: VectorSourceRequestMeta, limit: number, initialSearchContext?: object - ): Promise; + ): Promise; + loadStylePropsMeta( + layerName: string, + style: VectorStyle, + dynamicStyleProps: IDynamicStyleProperty[], + registerCancelCallback: (requestToken: symbol, callback: () => void) => void, + searchFilters: VectorSourceRequestMeta + ): Promise; } diff --git a/x-pack/plugins/maps/public/layers/sources/es_source/es_source.js b/x-pack/plugins/maps/public/layers/sources/es_source/es_source.js index 9ab87577b7780..ccd8bc4a859db 100644 --- a/x-pack/plugins/maps/public/layers/sources/es_source/es_source.js +++ b/x-pack/plugins/maps/public/layers/sources/es_source/es_source.js @@ -9,8 +9,8 @@ import { getAutocompleteService, fetchSearchSourceAndRecordWithInspector, getIndexPatternService, - SearchSource, getTimeFilter, + getSearchService, } from '../../../kibana_services'; import { createExtentFilter } from '../../../elasticsearch_geo_utils'; import _ from 'lodash'; @@ -125,8 +125,9 @@ export class AbstractESSource extends AbstractVectorSource { if (isTimeAware) { allFilters.push(getTimeFilter().createFilter(indexPattern, searchFilters.timeFilters)); } + const searchService = getSearchService(); + const searchSource = searchService.searchSource.create(initialSearchContext); - const searchSource = new SearchSource(initialSearchContext); searchSource.setField('index', indexPattern); searchSource.setField('size', limit); searchSource.setField('filter', allFilters); @@ -135,7 +136,8 @@ export class AbstractESSource extends AbstractVectorSource { } if (searchFilters.sourceQuery) { - const layerSearchSource = new SearchSource(); + const layerSearchSource = searchService.searchSource.create(); + layerSearchSource.setField('index', indexPattern); layerSearchSource.setField('query', searchFilters.sourceQuery); searchSource.setParent(layerSearchSource); @@ -294,7 +296,9 @@ export class AbstractESSource extends AbstractVectorSource { }, {}); const indexPattern = await this.getIndexPattern(); - const searchSource = new SearchSource(); + const searchService = getSearchService(); + const searchSource = searchService.searchSource.create(); + searchSource.setField('index', indexPattern); searchSource.setField('size', 0); searchSource.setField('aggs', aggs); diff --git a/x-pack/plugins/maps/public/layers/sources/es_term_source/es_term_source.d.ts b/x-pack/plugins/maps/public/layers/sources/es_term_source/es_term_source.d.ts index 701bd5e2c8b5e..248ca2b9212b4 100644 --- a/x-pack/plugins/maps/public/layers/sources/es_term_source/es_term_source.d.ts +++ b/x-pack/plugins/maps/public/layers/sources/es_term_source/es_term_source.d.ts @@ -9,4 +9,5 @@ import { IESAggSource } from '../es_agg_source'; export interface IESTermSource extends IESAggSource { getTermField(): IField; + hasCompleteConfig(): boolean; } diff --git a/x-pack/plugins/maps/public/layers/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.ts b/x-pack/plugins/maps/public/layers/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.ts index 0bfda6be72203..a73cfbdc0d043 100644 --- a/x-pack/plugins/maps/public/layers/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.ts +++ b/x-pack/plugins/maps/public/layers/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.ts @@ -15,9 +15,9 @@ import { IField } from '../../fields/field'; import { registerSource } from '../source_registry'; import { getDataSourceLabel, getUrlLabel } from '../../../../common/i18n_getters'; import { - LayerDescriptor, MapExtent, TiledSingleLayerVectorSourceDescriptor, + VectorLayerDescriptor, VectorSourceRequestMeta, VectorSourceSyncMeta, } from '../../../../common/descriptor_types'; @@ -66,12 +66,15 @@ export class MVTSingleLayerVectorSource extends AbstractSource return []; } - createDefaultLayer(options: LayerDescriptor): TiledVectorLayer { - const layerDescriptor = { + createDefaultLayer(options?: Partial): TiledVectorLayer { + const layerDescriptor: Partial = { sourceDescriptor: this._descriptor, ...options, }; - const normalizedLayerDescriptor = TiledVectorLayer.createDescriptor(layerDescriptor, []); + const normalizedLayerDescriptor: VectorLayerDescriptor = TiledVectorLayer.createDescriptor( + layerDescriptor, + [] + ); const vectorLayerArguments: VectorLayerArguments = { layerDescriptor: normalizedLayerDescriptor, source: this, diff --git a/x-pack/plugins/maps/public/layers/sources/source.d.ts b/x-pack/plugins/maps/public/layers/sources/source.d.ts deleted file mode 100644 index 5a01da02adaae..0000000000000 --- a/x-pack/plugins/maps/public/layers/sources/source.d.ts +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -/* eslint-disable @typescript-eslint/consistent-type-definitions */ - -import { AbstractSourceDescriptor, LayerDescriptor } from '../../../common/descriptor_types'; -import { ILayer } from '../layer'; - -export type ImmutableSourceProperty = { - label: string; - value: string; -}; - -export type Attribution = { - url: string; - label: string; -}; - -export interface ISource { - createDefaultLayer(options?: LayerDescriptor): ILayer; - destroy(): void; - getDisplayName(): Promise; - getInspectorAdapters(): object; - isFieldAware(): boolean; - isFilterByMapBounds(): boolean; - isGeoGridPrecisionAware(): boolean; - isQueryAware(): boolean; - isRefreshTimerAware(): Promise; - isTimeAware(): Promise; - getImmutableProperties(): Promise; - getAttributions(): Promise; - getMinZoom(): number; - getMaxZoom(): number; -} - -export class AbstractSource implements ISource { - readonly _descriptor: AbstractSourceDescriptor; - constructor(sourceDescriptor: AbstractSourceDescriptor, inspectorAdapters?: object); - - destroy(): void; - createDefaultLayer(options?: LayerDescriptor, mapColors?: string[]): ILayer; - getDisplayName(): Promise; - getInspectorAdapters(): object; - isFieldAware(): boolean; - isFilterByMapBounds(): boolean; - isGeoGridPrecisionAware(): boolean; - isQueryAware(): boolean; - isRefreshTimerAware(): Promise; - isTimeAware(): Promise; - getImmutableProperties(): Promise; - getAttributions(): Promise; - getMinZoom(): number; - getMaxZoom(): number; -} diff --git a/x-pack/plugins/maps/public/layers/sources/source.js b/x-pack/plugins/maps/public/layers/sources/source.js deleted file mode 100644 index fd93daf249b26..0000000000000 --- a/x-pack/plugins/maps/public/layers/sources/source.js +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { copyPersistentState } from '../../reducers/util'; -import { MIN_ZOOM, MAX_ZOOM } from '../../../common/constants'; - -export class AbstractSource { - static isIndexingSource = false; - - static renderEditor() { - throw new Error('Must implement Source.renderEditor'); - } - - static createDescriptor() { - throw new Error('Must implement Source.createDescriptor'); - } - - constructor(descriptor, inspectorAdapters) { - this._descriptor = descriptor; - this._inspectorAdapters = inspectorAdapters; - } - - destroy() {} - - cloneDescriptor() { - return copyPersistentState(this._descriptor); - } - - async supportsFitToBounds() { - return true; - } - - /** - * return list of immutable source properties. - * Immutable source properties are properties that can not be edited by the user. - */ - async getImmutableProperties() { - return []; - } - - getInspectorAdapters() { - return this._inspectorAdapters; - } - - _createDefaultLayerDescriptor() { - throw new Error(`Source#createDefaultLayerDescriptor not implemented`); - } - - createDefaultLayer() { - throw new Error(`Source#createDefaultLayer not implemented`); - } - - async getDisplayName() { - console.warn('Source should implement Source#getDisplayName'); - return ''; - } - - /** - * return attribution for this layer as array of objects with url and label property. - * e.g. [{ url: 'example.com', label: 'foobar' }] - * @return {Promise} - */ - async getAttributions() { - return []; - } - - isFieldAware() { - return false; - } - - isRefreshTimerAware() { - return false; - } - - isGeoGridPrecisionAware() { - return false; - } - - async isTimeAware() { - return false; - } - - getFieldNames() { - return []; - } - - hasCompleteConfig() { - throw new Error(`Source#hasCompleteConfig not implemented`); - } - - renderSourceSettingsEditor() { - return null; - } - - getApplyGlobalQuery() { - return !!this._descriptor.applyGlobalQuery; - } - - getIndexPatternIds() { - return []; - } - - getQueryableIndexPatternIds() { - return []; - } - - isFilterByMapBounds() { - return false; - } - - isQueryAware() { - return false; - } - - getGeoGridPrecision() { - return 0; - } - - isJoinable() { - return false; - } - - shouldBeIndexed() { - return AbstractSource.isIndexingSource; - } - - isESSource() { - return false; - } - - // Returns geo_shape indexed_shape context for spatial quering by pre-indexed shapes - async getPreIndexedShape(/* properties */) { - return null; - } - - // Returns function used to format value - async createFieldFormatter(/* field */) { - return null; - } - - async loadStylePropsMeta() { - throw new Error(`Source#loadStylePropsMeta not implemented`); - } - - async getValueSuggestions(/* field, query */) { - return []; - } - - getMinZoom() { - return MIN_ZOOM; - } - - getMaxZoom() { - return MAX_ZOOM; - } -} diff --git a/x-pack/plugins/maps/public/layers/sources/source.ts b/x-pack/plugins/maps/public/layers/sources/source.ts new file mode 100644 index 0000000000000..1cd84010159ab --- /dev/null +++ b/x-pack/plugins/maps/public/layers/sources/source.ts @@ -0,0 +1,195 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* eslint-disable @typescript-eslint/consistent-type-definitions */ + +import { ReactElement } from 'react'; + +import { Adapters } from 'src/plugins/inspector/public'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +// @ts-ignore +import { copyPersistentState } from '../../reducers/util'; + +import { LayerDescriptor, SourceDescriptor } from '../../../common/descriptor_types'; +import { ILayer } from '../layer'; +import { IField } from '../fields/field'; +import { MAX_ZOOM, MIN_ZOOM } from '../../../common/constants'; + +export type ImmutableSourceProperty = { + label: string; + value: string; +}; + +export type Attribution = { + url: string; + label: string; +}; + +export type PreIndexedShape = { + index: string; + id: string | number; + path: string; +}; + +export type FieldFormatter = (value: string | number | null | undefined | boolean) => string; + +export interface ISource { + createDefaultLayer(options?: Partial): ILayer; + destroy(): void; + getDisplayName(): Promise; + getInspectorAdapters(): Adapters | undefined; + isFieldAware(): boolean; + isFilterByMapBounds(): boolean; + isGeoGridPrecisionAware(): boolean; + isQueryAware(): boolean; + isRefreshTimerAware(): boolean; + isTimeAware(): Promise; + getImmutableProperties(): Promise; + getAttributions(): Promise; + isESSource(): boolean; + renderSourceSettingsEditor({ onChange }: { onChange: () => void }): ReactElement | null; + supportsFitToBounds(): Promise; + isJoinable(): boolean; + cloneDescriptor(): SourceDescriptor; + getFieldNames(): string[]; + getApplyGlobalQuery(): boolean; + getIndexPatternIds(): string[]; + getQueryableIndexPatternIds(): string[]; + getGeoGridPrecision(zoom: number): number; + shouldBeIndexed(): boolean; + getPreIndexedShape(): Promise; + createFieldFormatter(field: IField): Promise; + getValueSuggestions(field: IField, query: string): Promise; + getMinZoom(): number; + getMaxZoom(): number; +} + +export class AbstractSource implements ISource { + readonly _descriptor: SourceDescriptor; + readonly _inspectorAdapters?: Adapters | undefined; + + constructor(descriptor: SourceDescriptor, inspectorAdapters?: Adapters) { + this._descriptor = descriptor; + this._inspectorAdapters = inspectorAdapters; + } + + destroy(): void {} + + cloneDescriptor(): SourceDescriptor { + // @ts-ignore + return copyPersistentState(this._descriptor); + } + + async supportsFitToBounds(): Promise { + return true; + } + + /** + * return list of immutable source properties. + * Immutable source properties are properties that can not be edited by the user. + */ + async getImmutableProperties(): Promise { + return []; + } + + getInspectorAdapters(): Adapters | undefined { + return this._inspectorAdapters; + } + + createDefaultLayer(options?: Partial): ILayer { + throw new Error(`Source#createDefaultLayer not implemented`); + } + + async getDisplayName(): Promise { + return ''; + } + + async getAttributions(): Promise { + return []; + } + + isFieldAware(): boolean { + return false; + } + + isRefreshTimerAware(): boolean { + return false; + } + + isGeoGridPrecisionAware(): boolean { + return false; + } + + isQueryAware(): boolean { + return false; + } + + getFieldNames(): string[] { + return []; + } + + renderSourceSettingsEditor() { + return null; + } + + getApplyGlobalQuery(): boolean { + return !!this._descriptor.applyGlobalQuery; + } + + getIndexPatternIds(): string[] { + return []; + } + + getQueryableIndexPatternIds(): string[] { + return []; + } + + getGeoGridPrecision(zoom: number): number { + return 0; + } + + isJoinable(): boolean { + return false; + } + + shouldBeIndexed(): boolean { + return false; + } + + isESSource(): boolean { + return false; + } + + // Returns geo_shape indexed_shape context for spatial quering by pre-indexed shapes + async getPreIndexedShape(/* properties */): Promise { + return null; + } + + // Returns function used to format value + async createFieldFormatter(field: IField): Promise { + return null; + } + + async getValueSuggestions(field: IField, query: string): Promise { + return []; + } + + async isTimeAware(): Promise { + return false; + } + + isFilterByMapBounds(): boolean { + return false; + } + + getMinZoom() { + return MIN_ZOOM; + } + + getMaxZoom() { + return MAX_ZOOM; + } +} diff --git a/x-pack/plugins/maps/public/layers/sources/xyz_tms_source/xyz_tms_source.ts b/x-pack/plugins/maps/public/layers/sources/xyz_tms_source/xyz_tms_source.ts index 8b64480f92961..77f8d88a8c0ab 100644 --- a/x-pack/plugins/maps/public/layers/sources/xyz_tms_source/xyz_tms_source.ts +++ b/x-pack/plugins/maps/public/layers/sources/xyz_tms_source/xyz_tms_source.ts @@ -13,6 +13,7 @@ import { AbstractTMSSource } from '../tms_source'; import { LayerDescriptor, XYZTMSSourceDescriptor } from '../../../../common/descriptor_types'; import { Attribution, ImmutableSourceProperty } from '../source'; import { XYZTMSSourceConfig } from './xyz_tms_editor'; +import { ILayer } from '../../layer'; export const sourceTitle = i18n.translate('xpack.maps.source.ems_xyzTitle', { defaultMessage: 'Tile Map Service', @@ -48,7 +49,7 @@ export class XYZTMSSource extends AbstractTMSSource { ]; } - createDefaultLayer(options?: LayerDescriptor): TileLayer { + createDefaultLayer(options?: LayerDescriptor): ILayer { const layerDescriptor: LayerDescriptor = TileLayer.createDescriptor({ sourceDescriptor: this._descriptor, ...options, diff --git a/x-pack/plugins/maps/public/layers/styles/abstract_style.js b/x-pack/plugins/maps/public/layers/styles/abstract_style.js deleted file mode 100644 index 3e7a3dbf7ed20..0000000000000 --- a/x-pack/plugins/maps/public/layers/styles/abstract_style.js +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export class AbstractStyle { - getDescriptorWithMissingStylePropsRemoved(/* nextOrdinalFields */) { - return { - hasChanges: false, - }; - } - - async pluckStyleMetaFromSourceDataRequest(/* sourceDataRequest */) { - return {}; - } - - getDescriptor() { - return this._descriptor; - } - - renderEditor(/* { layer, onStyleDescriptorChange } */) { - return null; - } - - getSourceFieldNames() { - return []; - } -} diff --git a/x-pack/plugins/maps/public/layers/styles/heatmap/heatmap_style.js b/x-pack/plugins/maps/public/layers/styles/heatmap/heatmap_style.js index d769fe0da9ec2..1fa24943c5e51 100644 --- a/x-pack/plugins/maps/public/layers/styles/heatmap/heatmap_style.js +++ b/x-pack/plugins/maps/public/layers/styles/heatmap/heatmap_style.js @@ -5,7 +5,7 @@ */ import React from 'react'; -import { AbstractStyle } from '../abstract_style'; +import { AbstractStyle } from '../style'; import { HeatmapStyleEditor } from './components/heatmap_style_editor'; import { HeatmapLegend } from './components/legend/heatmap_legend'; import { DEFAULT_HEATMAP_COLOR_RAMP_NAME } from './components/heatmap_constants'; diff --git a/x-pack/plugins/maps/public/layers/styles/style.ts b/x-pack/plugins/maps/public/layers/styles/style.ts new file mode 100644 index 0000000000000..38fdc36904412 --- /dev/null +++ b/x-pack/plugins/maps/public/layers/styles/style.ts @@ -0,0 +1,59 @@ +/* + * 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 { ReactElement } from 'react'; +import { StyleDescriptor, StyleMetaDescriptor } from '../../../common/descriptor_types'; +import { ILayer } from '../layer'; +import { IField } from '../fields/field'; +import { DataRequest } from '../util/data_request'; + +export interface IStyle { + getDescriptor(): StyleDescriptor | null; + getDescriptorWithMissingStylePropsRemoved( + nextFields: IField[] + ): { hasChanges: boolean; nextStyleDescriptor?: StyleDescriptor }; + pluckStyleMetaFromSourceDataRequest(sourceDataRequest: DataRequest): StyleMetaDescriptor; + renderEditor({ + layer, + onStyleDescriptorChange, + }: { + layer: ILayer; + onStyleDescriptorChange: (styleDescriptor: StyleDescriptor) => void; + }): ReactElement | null; + getSourceFieldNames(): string[]; +} + +export class AbstractStyle implements IStyle { + readonly _descriptor: StyleDescriptor | null; + + constructor(descriptor: StyleDescriptor | null) { + this._descriptor = descriptor; + } + + getDescriptorWithMissingStylePropsRemoved( + nextFields: IField[] + ): { hasChanges: boolean; nextStyleDescriptor?: StyleDescriptor } { + return { + hasChanges: false, + }; + } + + pluckStyleMetaFromSourceDataRequest(sourceDataRequest: DataRequest): StyleMetaDescriptor { + return { fieldMeta: {} }; + } + + getDescriptor(): StyleDescriptor | null { + return this._descriptor; + } + + renderEditor(/* { layer, onStyleDescriptorChange } */) { + return null; + } + + getSourceFieldNames(): string[] { + return []; + } +} diff --git a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/routes/types.ts b/x-pack/plugins/maps/public/layers/styles/tile/tile_style.ts similarity index 51% rename from x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/routes/types.ts rename to x-pack/plugins/maps/public/layers/styles/tile/tile_style.ts index 7f57c20c536e0..f658d0821edf2 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/server/np_ready/routes/types.ts +++ b/x-pack/plugins/maps/public/layers/styles/tile/tile_style.ts @@ -3,11 +3,14 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { IRouter } from 'src/core/server'; -export interface RouteDependencies { - router: IRouter; - __LEGACY: { - server: any; - }; +import { AbstractStyle } from '../style'; +import { LAYER_STYLE_TYPE } from '../../../../common/constants'; + +export class TileStyle extends AbstractStyle { + constructor() { + super({ + type: LAYER_STYLE_TYPE.TILE, + }); + } } diff --git a/x-pack/plugins/maps/public/layers/styles/vector/vector_style.d.ts b/x-pack/plugins/maps/public/layers/styles/vector/vector_style.d.ts index e010d5ac7d7a3..762322b8e09f9 100644 --- a/x-pack/plugins/maps/public/layers/styles/vector/vector_style.d.ts +++ b/x-pack/plugins/maps/public/layers/styles/vector/vector_style.d.ts @@ -7,24 +7,23 @@ import { IStyleProperty } from './properties/style_property'; import { IDynamicStyleProperty } from './properties/dynamic_style_property'; import { IVectorLayer } from '../../vector_layer'; import { IVectorSource } from '../../sources/vector_source'; +import { AbstractStyle, IStyle } from '../style'; import { VectorStyleDescriptor, VectorStylePropertiesDescriptor, } from '../../../../common/descriptor_types'; -export interface IVectorStyle { +export interface IVectorStyle extends IStyle { getAllStyleProperties(): IStyleProperty[]; - getDescriptor(): VectorStyleDescriptor; getDynamicPropertiesArray(): IDynamicStyleProperty[]; getSourceFieldNames(): string[]; } -export class VectorStyle implements IVectorStyle { +export class VectorStyle extends AbstractStyle implements IVectorStyle { static createDescriptor(properties: VectorStylePropertiesDescriptor): VectorStyleDescriptor; static createDefaultStyleProperties(mapColors: string[]): VectorStylePropertiesDescriptor; constructor(descriptor: VectorStyleDescriptor, source: IVectorSource, layer: IVectorLayer); getSourceFieldNames(): string[]; getAllStyleProperties(): IStyleProperty[]; - getDescriptor(): VectorStyleDescriptor; getDynamicPropertiesArray(): IDynamicStyleProperty[]; } diff --git a/x-pack/plugins/maps/public/layers/styles/vector/vector_style.js b/x-pack/plugins/maps/public/layers/styles/vector/vector_style.js index b044c98d44d41..5a4edd9c93a05 100644 --- a/x-pack/plugins/maps/public/layers/styles/vector/vector_style.js +++ b/x-pack/plugins/maps/public/layers/styles/vector/vector_style.js @@ -8,7 +8,7 @@ import _ from 'lodash'; import React from 'react'; import { VectorStyleEditor } from './components/vector_style_editor'; import { getDefaultProperties, LINE_STYLES, POLYGON_STYLES } from './vector_style_defaults'; -import { AbstractStyle } from '../abstract_style'; +import { AbstractStyle } from '../style'; import { GEO_JSON_TYPE, FIELD_ORIGIN, @@ -60,6 +60,7 @@ export class VectorStyle extends AbstractStyle { constructor(descriptor = {}, source, layer) { super(); + descriptor = descriptor === null ? {} : descriptor; this._source = source; this._layer = layer; this._descriptor = { diff --git a/x-pack/plugins/maps/public/layers/tile_layer.d.ts b/x-pack/plugins/maps/public/layers/tile_layer.d.ts index 53e8c388ee4c2..8a1ef0f172717 100644 --- a/x-pack/plugins/maps/public/layers/tile_layer.d.ts +++ b/x-pack/plugins/maps/public/layers/tile_layer.d.ts @@ -4,11 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AbstractLayer, ILayerArguments } from './layer'; +import { AbstractLayer } from './layer'; import { ITMSSource } from './sources/tms_source'; import { LayerDescriptor } from '../../common/descriptor_types'; -interface ITileLayerArguments extends ILayerArguments { +interface ITileLayerArguments { source: ITMSSource; layerDescriptor: LayerDescriptor; } diff --git a/x-pack/plugins/maps/public/layers/tile_layer.js b/x-pack/plugins/maps/public/layers/tile_layer.js index 2ac60e12d137a..baded3c287637 100644 --- a/x-pack/plugins/maps/public/layers/tile_layer.js +++ b/x-pack/plugins/maps/public/layers/tile_layer.js @@ -6,7 +6,8 @@ import { AbstractLayer } from './layer'; import _ from 'lodash'; -import { SOURCE_DATA_ID_ORIGIN, LAYER_TYPE } from '../../common/constants'; +import { SOURCE_DATA_ID_ORIGIN, LAYER_TYPE, LAYER_STYLE_TYPE } from '../../common/constants'; +import { TileStyle } from './styles/tile/tile_style'; export class TileLayer extends AbstractLayer { static type = LAYER_TYPE.TILE; @@ -15,9 +16,14 @@ export class TileLayer extends AbstractLayer { const tileLayerDescriptor = super.createDescriptor(options, mapColors); tileLayerDescriptor.type = TileLayer.type; tileLayerDescriptor.alpha = _.get(options, 'alpha', 1); + tileLayerDescriptor.style = { type: LAYER_STYLE_TYPE.TILE }; return tileLayerDescriptor; } + constructor({ source, layerDescriptor }) { + super({ source, layerDescriptor, style: new TileStyle() }); + } + async syncData({ startLoading, stopLoading, onLoadError, dataFilters }) { if (!this.isVisible() || !this.showAtZoomLevel(dataFilters.zoom)) { return; diff --git a/x-pack/plugins/maps/public/layers/tile_layer.test.ts b/x-pack/plugins/maps/public/layers/tile_layer.test.ts index f8c2fd9db60fa..a7e8be9fc4b46 100644 --- a/x-pack/plugins/maps/public/layers/tile_layer.test.ts +++ b/x-pack/plugins/maps/public/layers/tile_layer.test.ts @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { TileLayer } from './tile_layer'; +// eslint-disable-next-line max-classes-per-file +import { ITileLayerArguments, TileLayer } from './tile_layer'; import { SOURCE_TYPES } from '../../common/constants'; import { XYZTMSSourceDescriptor } from '../../common/descriptor_types'; import { ITMSSource, AbstractTMSSource } from './sources/tms_source'; @@ -38,10 +39,13 @@ class MockTileSource extends AbstractTMSSource implements ITMSSource { describe('TileLayer', () => { it('should use display-label from source', async () => { const source = new MockTileSource(sourceDescriptor); - const layer: ILayer = new TileLayer({ + + const args: ITileLayerArguments = { source, layerDescriptor: { id: 'layerid', sourceDescriptor }, - }); + }; + + const layer: ILayer = new TileLayer(args); expect(await source.getDisplayName()).toEqual(await layer.getDisplayName()); }); diff --git a/x-pack/plugins/maps/public/layers/tiled_vector_layer.tsx b/x-pack/plugins/maps/public/layers/tiled_vector_layer.tsx index c47cae5641e56..06c5ef579b221 100644 --- a/x-pack/plugins/maps/public/layers/tiled_vector_layer.tsx +++ b/x-pack/plugins/maps/public/layers/tiled_vector_layer.tsx @@ -20,7 +20,7 @@ export class TiledVectorLayer extends VectorLayer { static type = LAYER_TYPE.TILED_VECTOR; static createDescriptor( - descriptor: VectorLayerDescriptor, + descriptor: Partial, mapColors: string[] ): VectorLayerDescriptor { const layerDescriptor = super.createDescriptor(descriptor, mapColors); diff --git a/x-pack/plugins/maps/public/layers/vector_layer.d.ts b/x-pack/plugins/maps/public/layers/vector_layer.d.ts index 3d5b8054ff3fd..efc1f3011c687 100644 --- a/x-pack/plugins/maps/public/layers/vector_layer.d.ts +++ b/x-pack/plugins/maps/public/layers/vector_layer.d.ts @@ -19,7 +19,7 @@ import { IVectorStyle } from './styles/vector/vector_style'; import { IField } from './fields/field'; import { SyncContext } from '../actions/map_actions'; -type VectorLayerArguments = { +export type VectorLayerArguments = { source: IVectorSource; joins?: IJoin[]; layerDescriptor: VectorLayerDescriptor; @@ -33,14 +33,12 @@ export interface IVectorLayer extends ILayer { } export class VectorLayer extends AbstractLayer implements IVectorLayer { + protected readonly _style: IVectorStyle; static createDescriptor( options: Partial, mapColors?: string[] ): VectorLayerDescriptor; - protected readonly _source: IVectorSource; - protected readonly _style: IVectorStyle; - constructor(options: VectorLayerArguments); getLayerTypeIconName(): string; getFields(): Promise; diff --git a/x-pack/plugins/maps/public/layers/vector_layer.js b/x-pack/plugins/maps/public/layers/vector_layer.js index c5947a63587ea..17b7f8152d76d 100644 --- a/x-pack/plugins/maps/public/layers/vector_layer.js +++ b/x-pack/plugins/maps/public/layers/vector_layer.js @@ -484,6 +484,8 @@ export class VectorLayer extends AbstractLayer { try { startLoading(dataRequestId, requestToken, nextMeta); const layerName = await this.getDisplayName(source); + + //todo: cast source to ESSource when migrating to TS const styleMeta = await source.loadStylePropsMeta( layerName, style, diff --git a/x-pack/plugins/maps/public/layers/vector_tile_layer.js b/x-pack/plugins/maps/public/layers/vector_tile_layer.js index c620ec6c56dc3..fc7812a2c86c7 100644 --- a/x-pack/plugins/maps/public/layers/vector_tile_layer.js +++ b/x-pack/plugins/maps/public/layers/vector_tile_layer.js @@ -6,7 +6,7 @@ import { TileLayer } from './tile_layer'; import _ from 'lodash'; -import { SOURCE_DATA_ID_ORIGIN, LAYER_TYPE } from '../../common/constants'; +import { SOURCE_DATA_ID_ORIGIN, LAYER_TYPE, LAYER_STYLE_TYPE } from '../../common/constants'; import { isRetina } from '../meta'; import { addSpriteSheetToMapFromImageData, @@ -28,6 +28,7 @@ export class VectorTileLayer extends TileLayer { const tileLayerDescriptor = super.createDescriptor(options); tileLayerDescriptor.type = VectorTileLayer.type; tileLayerDescriptor.alpha = _.get(options, 'alpha', 1); + tileLayerDescriptor.style = { type: LAYER_STYLE_TYPE.TILE }; return tileLayerDescriptor; } diff --git a/x-pack/plugins/maps/public/plugin.ts b/x-pack/plugins/maps/public/plugin.ts index 0b076621326ce..74bd305bff963 100644 --- a/x-pack/plugins/maps/public/plugin.ts +++ b/x-pack/plugins/maps/public/plugin.ts @@ -31,7 +31,7 @@ import { setUiActions, setUiSettings, setVisualizations, - // @ts-ignore + setSearchService, } from './kibana_services'; import { featureCatalogueEntry } from './feature_catalogue_entry'; // @ts-ignore @@ -68,6 +68,7 @@ export const bindStartCoreAndPlugins = (core: CoreStart, plugins: any) => { setFileUpload(fileUpload); setIndexPatternSelect(data.ui.IndexPatternSelect); setTimeFilter(data.query.timefilter.timefilter); + setSearchService(data.search); setIndexPatternService(data.indexPatterns); setAutocompleteService(data.autocomplete); setCore(core); diff --git a/x-pack/plugins/maps/public/reducers/default_map_settings.ts b/x-pack/plugins/maps/public/reducers/default_map_settings.ts index 81622ea9581b0..fe21b37434edd 100644 --- a/x-pack/plugins/maps/public/reducers/default_map_settings.ts +++ b/x-pack/plugins/maps/public/reducers/default_map_settings.ts @@ -11,5 +11,9 @@ export function getDefaultMapSettings(): MapSettings { return { maxZoom: MAX_ZOOM, minZoom: MIN_ZOOM, + showSpatialFilters: true, + spatialFiltersAlpa: 0.3, + spatialFiltersFillColor: '#DA8B45', + spatialFiltersLineColor: '#DA8B45', }; } diff --git a/x-pack/plugins/maps/public/reducers/map.d.ts b/x-pack/plugins/maps/public/reducers/map.d.ts index af2d96eb75562..be0700d4bdd6d 100644 --- a/x-pack/plugins/maps/public/reducers/map.d.ts +++ b/x-pack/plugins/maps/public/reducers/map.d.ts @@ -42,6 +42,10 @@ export type MapContext = { export type MapSettings = { maxZoom: number; minZoom: number; + showSpatialFilters: boolean; + spatialFiltersAlpa: number; + spatialFiltersFillColor: string; + spatialFiltersLineColor: string; }; export type MapState = { diff --git a/x-pack/plugins/maps/public/selectors/map_selectors.d.ts b/x-pack/plugins/maps/public/selectors/map_selectors.d.ts index fed344b7744fe..4d0f652af982a 100644 --- a/x-pack/plugins/maps/public/selectors/map_selectors.d.ts +++ b/x-pack/plugins/maps/public/selectors/map_selectors.d.ts @@ -8,6 +8,7 @@ import { AnyAction } from 'redux'; import { MapCenter } from '../../common/descriptor_types'; import { MapStoreState } from '../reducers/store'; import { MapSettings } from '../reducers/map'; +import { IVectorLayer } from '../layers/vector_layer'; export function getHiddenLayerIds(state: MapStoreState): string[]; @@ -20,3 +21,5 @@ export function getQueryableUniqueIndexPatternIds(state: MapStoreState): string[ export function getMapSettings(state: MapStoreState): MapSettings; export function hasMapSettingsChanges(state: MapStoreState): boolean; + +export function getSpatialFiltersLayer(state: MapStoreState): IVectorLayer; diff --git a/x-pack/plugins/maps/public/selectors/map_selectors.js b/x-pack/plugins/maps/public/selectors/map_selectors.js index 20d1f7e08c910..f43c92d4c9945 100644 --- a/x-pack/plugins/maps/public/selectors/map_selectors.js +++ b/x-pack/plugins/maps/public/selectors/map_selectors.js @@ -6,27 +6,26 @@ import { createSelector } from 'reselect'; import _ from 'lodash'; - import { TileLayer } from '../layers/tile_layer'; - import { VectorTileLayer } from '../layers/vector_tile_layer'; - import { VectorLayer } from '../layers/vector_layer'; - import { HeatmapLayer } from '../layers/heatmap_layer'; - import { BlendedVectorLayer } from '../layers/blended_vector_layer'; - import { getTimeFilter } from '../kibana_services'; - import { getInspectorAdapters } from '../reducers/non_serializable_instances'; import { TiledVectorLayer } from '../layers/tiled_vector_layer'; - import { copyPersistentState, TRACKED_LAYER_DESCRIPTOR } from '../reducers/util'; - import { InnerJoin } from '../layers/joins/inner_join'; - import { getSourceByType } from '../layers/sources/source_registry'; +import { GeojsonFileSource } from '../layers/sources/client_file_source'; +import { + LAYER_TYPE, + SOURCE_DATA_ID_ORIGIN, + STYLE_TYPE, + VECTOR_STYLES, + SPATIAL_FILTERS_LAYER_ID, +} from '../../common/constants'; +import { extractFeaturesFromFilters } from '../elasticsearch_geo_utils'; function createLayerInstance(layerDescriptor, inspectorAdapters) { const source = createSourceInstance(layerDescriptor.sourceDescriptor, inspectorAdapters); @@ -195,6 +194,53 @@ export const getDataFilters = createSelector( } ); +export const getSpatialFiltersLayer = createSelector( + getFilters, + getMapSettings, + (filters, settings) => { + const featureCollection = { + type: 'FeatureCollection', + features: extractFeaturesFromFilters(filters), + }; + const geoJsonSourceDescriptor = GeojsonFileSource.createDescriptor( + featureCollection, + 'spatialFilters' + ); + + return new VectorLayer({ + layerDescriptor: { + id: SPATIAL_FILTERS_LAYER_ID, + visible: settings.showSpatialFilters, + alpha: settings.spatialFiltersAlpa, + type: LAYER_TYPE.VECTOR, + __dataRequests: [ + { + dataId: SOURCE_DATA_ID_ORIGIN, + data: featureCollection, + }, + ], + style: { + properties: { + [VECTOR_STYLES.FILL_COLOR]: { + type: STYLE_TYPE.STATIC, + options: { + color: settings.spatialFiltersFillColor, + }, + }, + [VECTOR_STYLES.LINE_COLOR]: { + type: STYLE_TYPE.STATIC, + options: { + color: settings.spatialFiltersLineColor, + }, + }, + }, + }, + }, + source: new GeojsonFileSource(geoJsonSourceDescriptor), + }); + } +); + export const getLayerList = createSelector( getLayerListRaw, getInspectorAdapters, diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/edit_flyout/__snapshots__/overrides.test.js.snap b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/edit_flyout/__snapshots__/overrides.test.js.snap index 46428ff9c351a..59481cb086566 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/edit_flyout/__snapshots__/overrides.test.js.snap +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/edit_flyout/__snapshots__/overrides.test.js.snap @@ -70,6 +70,7 @@ exports[`Overrides render overrides 1`] = ` "asPlainText": true, } } + sortMatchesBy="none" /> diff --git a/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/__snapshots__/editor.test.tsx.snap b/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/__snapshots__/editor.test.tsx.snap index 1b27d09965c4e..06ee16f264756 100644 --- a/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/__snapshots__/editor.test.tsx.snap +++ b/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/__snapshots__/editor.test.tsx.snap @@ -150,6 +150,7 @@ exports[`CustomUrlEditor renders the editor for a dashboard type URL with a labe placeholder="Select entities" selectedOptions={Array []} singleSelection={false} + sortMatchesBy="none" /> { +export const deprecations = ({ + rename, + renameFromRoot, +}: ConfigDeprecationFactory): ConfigDeprecation[] => { return [ + // This order matters. The "blanket rename" needs to happen at the end + renameFromRoot('xpack.monitoring.max_bucket_size', 'monitoring.ui.max_bucket_size'), + renameFromRoot('xpack.monitoring.min_interval_seconds', 'monitoring.ui.min_interval_seconds'), + renameFromRoot( + 'xpack.monitoring.show_license_expiration', + 'monitoring.ui.show_license_expiration' + ), + renameFromRoot( + 'xpack.monitoring.ui.container.elasticsearch.enabled', + 'monitoring.ui.container.elasticsearch.enabled' + ), + renameFromRoot( + 'xpack.monitoring.ui.container.logstash.enabled', + 'monitoring.ui.container.logstash.enabled' + ), + renameFromRoot('xpack.monitoring.elasticsearch', 'monitoring.ui.elasticsearch'), + renameFromRoot('xpack.monitoring.ccs.enabled', 'monitoring.ui.ccs.enabled'), + renameFromRoot( + 'xpack.monitoring.elasticsearch.logFetchCount', + 'monitoring.ui.elasticsearch.logFetchCount' + ), + renameFromRoot('xpack.monitoring', 'monitoring'), (config, fromPath, logger) => { const clusterAlertsEnabled = get(config, 'cluster_alerts.enabled'); const emailNotificationsEnabled = diff --git a/x-pack/plugins/remote_clusters/public/index.ts b/x-pack/plugins/remote_clusters/public/index.ts index 6ba021b157c3e..127ec2a670645 100644 --- a/x-pack/plugins/remote_clusters/public/index.ts +++ b/x-pack/plugins/remote_clusters/public/index.ts @@ -3,8 +3,11 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ + import { PluginInitializerContext } from 'kibana/public'; import { RemoteClustersUIPlugin } from './plugin'; +export { RemoteClustersPluginSetup } from './plugin'; + export const plugin = (initializerContext: PluginInitializerContext) => new RemoteClustersUIPlugin(initializerContext); diff --git a/x-pack/plugins/remote_clusters/public/plugin.ts b/x-pack/plugins/remote_clusters/public/plugin.ts index d110c461c1e3f..22f98e94748d8 100644 --- a/x-pack/plugins/remote_clusters/public/plugin.ts +++ b/x-pack/plugins/remote_clusters/public/plugin.ts @@ -14,7 +14,12 @@ import { init as initNotification } from './application/services/notification'; import { init as initRedirect } from './application/services/redirect'; import { Dependencies, ClientConfigType } from './types'; -export class RemoteClustersUIPlugin implements Plugin { +export interface RemoteClustersPluginSetup { + isUiEnabled: boolean; +} + +export class RemoteClustersUIPlugin + implements Plugin { constructor(private readonly initializerContext: PluginInitializerContext) {} setup( @@ -55,6 +60,10 @@ export class RemoteClustersUIPlugin implements Plugin new RemoteClustersServerPlugin(ctx); diff --git a/x-pack/plugins/remote_clusters/server/plugin.ts b/x-pack/plugins/remote_clusters/server/plugin.ts index fca4a5dbc5f94..a7ca30a6bf96d 100644 --- a/x-pack/plugins/remote_clusters/server/plugin.ts +++ b/x-pack/plugins/remote_clusters/server/plugin.ts @@ -7,6 +7,7 @@ import { i18n } from '@kbn/i18n'; import { CoreSetup, Logger, Plugin, PluginInitializerContext } from 'src/core/server'; import { Observable } from 'rxjs'; +import { first } from 'rxjs/operators'; import { PLUGIN } from '../common/constants'; import { Dependencies, LicenseStatus, RouteDependencies } from './types'; @@ -18,19 +19,26 @@ import { registerDeleteRoute, } from './routes/api'; -export class RemoteClustersServerPlugin implements Plugin { +export interface RemoteClustersPluginSetup { + isUiEnabled: boolean; +} + +export class RemoteClustersServerPlugin + implements Plugin { licenseStatus: LicenseStatus; log: Logger; - config: Observable; + config$: Observable; constructor({ logger, config }: PluginInitializerContext) { this.log = logger.get(); - this.config = config.create(); + this.config$ = config.create(); this.licenseStatus = { valid: false }; } async setup({ http }: CoreSetup, { licensing, cloud }: Dependencies) { const router = http.createRouter(); + const config = await this.config$.pipe(first()).toPromise(); + const routeDependencies: RouteDependencies = { router, getLicenseStatus: () => this.licenseStatus, @@ -64,6 +72,10 @@ export class RemoteClustersServerPlugin implements Plugin } } }); + + return { + isUiEnabled: config.ui.enabled, + }; } start() {} diff --git a/x-pack/plugins/reporting/public/components/__snapshots__/report_listing.test.tsx.snap b/x-pack/plugins/reporting/public/components/__snapshots__/report_listing.test.tsx.snap index 271d61c224210..b05e74c516cd4 100644 --- a/x-pack/plugins/reporting/public/components/__snapshots__/report_listing.test.tsx.snap +++ b/x-pack/plugins/reporting/public/components/__snapshots__/report_listing.test.tsx.snap @@ -219,11 +219,14 @@ Array [
- - Report - + + + Report + +
@@ -246,11 +249,14 @@ Array [
- - Created at - + + + Created at + +
@@ -273,11 +279,14 @@ Array [
- - Status - + + + Status + +
@@ -298,11 +307,14 @@ Array [
- - Actions - + + + Actions + +
@@ -514,11 +526,14 @@ Array [
- - Report - + + + Report + +
@@ -541,11 +556,14 @@ Array [
- - Created at - + + + Created at + +
@@ -568,11 +586,14 @@ Array [
- - Status - + + + Status + +
@@ -593,11 +614,14 @@ Array [
- - Actions - + + + Actions + +
diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/__snapshots__/cluster_privileges.test.tsx.snap b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/__snapshots__/cluster_privileges.test.tsx.snap index a52438ca93638..dd27fe13e84a3 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/__snapshots__/cluster_privileges.test.tsx.snap +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/__snapshots__/cluster_privileges.test.tsx.snap @@ -29,6 +29,7 @@ exports[`it renders without crashing 1`] = ` } selectedOptions={Array []} singleSelection={false} + sortMatchesBy="none" /> diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/__snapshots__/elasticsearch_privileges.test.tsx.snap b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/__snapshots__/elasticsearch_privileges.test.tsx.snap index b306de5b84093..46fb9b8572679 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/__snapshots__/elasticsearch_privileges.test.tsx.snap +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/__snapshots__/elasticsearch_privileges.test.tsx.snap @@ -119,6 +119,7 @@ exports[`it renders without crashing 1`] = ` placeholder="Add a user…" selectedOptions={Array []} singleSelection={false} + sortMatchesBy="none" /> diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/__snapshots__/index_privilege_form.test.tsx.snap b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/__snapshots__/index_privilege_form.test.tsx.snap index bbf90d0f64bd2..d23a6da13a3bb 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/__snapshots__/index_privilege_form.test.tsx.snap +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/__snapshots__/index_privilege_form.test.tsx.snap @@ -37,6 +37,7 @@ exports[`it renders without crashing 1`] = ` options={Array []} selectedOptions={Array []} singleSelection={false} + sortMatchesBy="none" /> @@ -82,6 +83,7 @@ exports[`it renders without crashing 1`] = ` } selectedOptions={Array []} singleSelection={false} + sortMatchesBy="none" /> diff --git a/x-pack/plugins/siem/cypress/integration/cases.spec.ts b/x-pack/plugins/siem/cypress/integration/cases.spec.ts new file mode 100644 index 0000000000000..f541555d56440 --- /dev/null +++ b/x-pack/plugins/siem/cypress/integration/cases.spec.ts @@ -0,0 +1,115 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { case1 } from '../objects/case'; + +import { + ALL_CASES_CLOSE_ACTION, + ALL_CASES_CLOSED_CASES_COUNT, + ALL_CASES_CLOSED_CASES_STATS, + ALL_CASES_COMMENTS_COUNT, + ALL_CASES_DELETE_ACTION, + ALL_CASES_NAME, + ALL_CASES_OPEN_CASES_COUNT, + ALL_CASES_OPEN_CASES_STATS, + ALL_CASES_OPENED_ON, + ALL_CASES_PAGE_TITLE, + ALL_CASES_REPORTER, + ALL_CASES_REPORTERS_COUNT, + ALL_CASES_SERVICE_NOW_INCIDENT, + ALL_CASES_TAGS, + ALL_CASES_TAGS_COUNT, +} from '../screens/all_cases'; +import { + ACTION, + CASE_DETAILS_DESCRIPTION, + CASE_DETAILS_PAGE_TITLE, + CASE_DETAILS_PUSH_AS_SERVICE_NOW_BTN, + CASE_DETAILS_STATUS, + CASE_DETAILS_TAGS, + CASE_DETAILS_TIMELINE_MARKDOWN, + CASE_DETAILS_USER_ACTION, + CASE_DETAILS_USERNAMES, + PARTICIPANTS, + REPORTER, + USER, +} from '../screens/case_details'; +import { TIMELINE_DESCRIPTION, TIMELINE_QUERY, TIMELINE_TITLE } from '../screens/timeline'; + +import { goToCaseDetails, goToCreateNewCase } from '../tasks/all_cases'; +import { openCaseTimeline } from '../tasks/case_details'; +import { backToCases, createNewCase } from '../tasks/create_new_case'; +import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; +import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; + +import { CASES } from '../urls/navigation'; + +describe('Cases', () => { + before(() => { + esArchiverLoad('timeline'); + }); + + after(() => { + esArchiverUnload('timeline'); + }); + + it('Creates a new case with timeline and opens the timeline', () => { + loginAndWaitForPageWithoutDateRange(CASES); + goToCreateNewCase(); + createNewCase(case1); + backToCases(); + + cy.get(ALL_CASES_PAGE_TITLE).should('have.text', 'Cases Beta'); + cy.get(ALL_CASES_OPEN_CASES_STATS).should('have.text', 'Open cases1'); + cy.get(ALL_CASES_CLOSED_CASES_STATS).should('have.text', 'Closed cases0'); + cy.get(ALL_CASES_OPEN_CASES_COUNT).should('have.text', 'Open cases (1)'); + cy.get(ALL_CASES_CLOSED_CASES_COUNT).should('have.text', 'Closed cases (0)'); + cy.get(ALL_CASES_REPORTERS_COUNT).should('have.text', 'Reporter1'); + cy.get(ALL_CASES_TAGS_COUNT).should('have.text', 'Tags2'); + cy.get(ALL_CASES_NAME).should('have.text', case1.name); + cy.get(ALL_CASES_REPORTER).should('have.text', case1.reporter); + case1.tags.forEach((tag, index) => { + cy.get(ALL_CASES_TAGS(index)).should('have.text', tag); + }); + cy.get(ALL_CASES_COMMENTS_COUNT).should('have.text', '0'); + cy.get(ALL_CASES_OPENED_ON).should('include.text', 'ago'); + cy.get(ALL_CASES_SERVICE_NOW_INCIDENT).should('have.text', 'Not pushed'); + cy.get(ALL_CASES_DELETE_ACTION).should('exist'); + cy.get(ALL_CASES_CLOSE_ACTION).should('exist'); + + goToCaseDetails(); + + const expectedTags = case1.tags.join(''); + cy.get(CASE_DETAILS_PAGE_TITLE).should('have.text', case1.name); + cy.get(CASE_DETAILS_STATUS).should('have.text', 'open'); + cy.get(CASE_DETAILS_USER_ACTION) + .eq(USER) + .should('have.text', case1.reporter); + cy.get(CASE_DETAILS_USER_ACTION) + .eq(ACTION) + .should('have.text', 'added description'); + cy.get(CASE_DETAILS_DESCRIPTION).should( + 'have.text', + `${case1.description} ${case1.timeline.title}` + ); + cy.get(CASE_DETAILS_USERNAMES) + .eq(REPORTER) + .should('have.text', case1.reporter); + cy.get(CASE_DETAILS_USERNAMES) + .eq(PARTICIPANTS) + .should('have.text', case1.reporter); + cy.get(CASE_DETAILS_TAGS).should('have.text', expectedTags); + cy.get(CASE_DETAILS_PUSH_AS_SERVICE_NOW_BTN).should('have.attr', 'disabled'); + cy.get(CASE_DETAILS_TIMELINE_MARKDOWN).then($element => { + const timelineLink = $element.prop('href').match(/http(s?):\/\/\w*:\w*(\S*)/)[0]; + openCaseTimeline(timelineLink); + + cy.get(TIMELINE_TITLE).should('have.attr', 'value', case1.timeline.title); + cy.get(TIMELINE_DESCRIPTION).should('have.attr', 'value', case1.timeline.description); + cy.get(TIMELINE_QUERY).should('have.attr', 'value', case1.timeline.query); + }); + }); +}); diff --git a/x-pack/plugins/siem/cypress/objects/case.ts b/x-pack/plugins/siem/cypress/objects/case.ts new file mode 100644 index 0000000000000..1c7bc34bca417 --- /dev/null +++ b/x-pack/plugins/siem/cypress/objects/case.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Timeline } from './timeline'; + +export interface TestCase { + name: string; + tags: string[]; + description: string; + timeline: Timeline; + reporter: string; +} + +const caseTimeline: Timeline = { + title: 'SIEM test', + description: 'description', + query: 'host.name:*', +}; + +export const case1: TestCase = { + name: 'This is the title of the case', + tags: ['Tag1', 'Tag2'], + description: 'This is the case description', + timeline: caseTimeline, + reporter: 'elastic', +}; diff --git a/x-pack/plugins/siem/cypress/objects/timeline.ts b/x-pack/plugins/siem/cypress/objects/timeline.ts index bca99bfa9266a..060a1376b46ce 100644 --- a/x-pack/plugins/siem/cypress/objects/timeline.ts +++ b/x-pack/plugins/siem/cypress/objects/timeline.ts @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -interface Timeline { +export interface Timeline { title: string; + description: string; query: string; } diff --git a/x-pack/plugins/siem/cypress/screens/all_cases.ts b/x-pack/plugins/siem/cypress/screens/all_cases.ts new file mode 100644 index 0000000000000..b1e4c66515352 --- /dev/null +++ b/x-pack/plugins/siem/cypress/screens/all_cases.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const ALL_CASES_CLOSE_ACTION = '[data-test-subj="action-close"]'; + +export const ALL_CASES_CLOSED_CASES_COUNT = '[data-test-subj="closed-case-count"]'; + +export const ALL_CASES_CLOSED_CASES_STATS = '[data-test-subj="closedStatsHeader"]'; + +export const ALL_CASES_COMMENTS_COUNT = '[data-test-subj="case-table-column-commentCount"]'; + +export const ALL_CASES_CREATE_NEW_CASE_BTN = '[data-test-subj="createNewCaseBtn"]'; + +export const ALL_CASES_DELETE_ACTION = '[data-test-subj="action-delete"]'; + +export const ALL_CASES_NAME = '[data-test-subj="case-details-link"]'; + +export const ALL_CASES_OPEN_CASES_COUNT = '[data-test-subj="open-case-count"]'; + +export const ALL_CASES_OPEN_CASES_STATS = '[data-test-subj="openStatsHeader"]'; + +export const ALL_CASES_OPENED_ON = '[data-test-subj="case-table-column-createdAt"]'; + +export const ALL_CASES_PAGE_TITLE = '[data-test-subj="header-page-title"]'; + +export const ALL_CASES_REPORTER = '[data-test-subj="case-table-column-createdBy"]'; + +export const ALL_CASES_REPORTERS_COUNT = + '[data-test-subj="options-filter-popover-button-Reporter"]'; + +export const ALL_CASES_SERVICE_NOW_INCIDENT = + '[data-test-subj="case-table-column-external-notPushed"]'; + +export const ALL_CASES_TAGS = (index: number) => { + return `[data-test-subj="case-table-column-tags-${index}"]`; +}; + +export const ALL_CASES_TAGS_COUNT = '[data-test-subj="options-filter-popover-button-Tags"]'; diff --git a/x-pack/plugins/siem/cypress/screens/case_details.ts b/x-pack/plugins/siem/cypress/screens/case_details.ts new file mode 100644 index 0000000000000..3bd180b1d588f --- /dev/null +++ b/x-pack/plugins/siem/cypress/screens/case_details.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const ACTION = 2; + +export const CASE_DETAILS_DESCRIPTION = '[data-test-subj="markdown-root"]'; + +export const CASE_DETAILS_PAGE_TITLE = '[data-test-subj="header-page-title"]'; + +export const CASE_DETAILS_PUSH_AS_SERVICE_NOW_BTN = '[data-test-subj="push-to-service-now"]'; + +export const CASE_DETAILS_STATUS = '[data-test-subj="case-view-status"]'; + +export const CASE_DETAILS_TAGS = '[data-test-subj="case-tags"]'; + +export const CASE_DETAILS_TIMELINE_MARKDOWN = '[data-test-subj="markdown-link"]'; + +export const CASE_DETAILS_USER_ACTION = '[data-test-subj="user-action-title"] .euiFlexItem'; + +export const CASE_DETAILS_USERNAMES = '[data-test-subj="case-view-username"]'; + +export const PARTICIPANTS = 1; + +export const REPORTER = 0; + +export const USER = 1; diff --git a/x-pack/plugins/siem/cypress/screens/create_new_case.ts b/x-pack/plugins/siem/cypress/screens/create_new_case.ts new file mode 100644 index 0000000000000..6e2beb78fff19 --- /dev/null +++ b/x-pack/plugins/siem/cypress/screens/create_new_case.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const BACK_TO_CASES_BTN = '[data-test-subj="backToCases"]'; + +export const DESCRIPTION_INPUT = + '[data-test-subj="caseDescription"] [data-test-subj="textAreaInput"]'; + +export const INSERT_TIMELINE_BTN = '[data-test-subj="insert-timeline-button"]'; + +export const LOADING_SPINNER = '[data-test-subj="create-case-loading-spinner"]'; + +export const SUBMIT_BTN = '[data-test-subj="create-case-submit"]'; + +export const TAGS_INPUT = '[data-test-subj="caseTags"] [data-test-subj="comboBoxSearchInput"]'; + +export const TIMELINE = '[data-test-subj="timeline"]'; + +export const TIMELINE_SEARCHBOX = '[data-test-subj="timeline-super-select-search-box"]'; + +export const TITLE_INPUT = '[data-test-subj="caseTitle"] [data-test-subj="input"]'; diff --git a/x-pack/plugins/siem/cypress/screens/timeline.ts b/x-pack/plugins/siem/cypress/screens/timeline.ts index 53d8273d9ce6b..58d2568084f7c 100644 --- a/x-pack/plugins/siem/cypress/screens/timeline.ts +++ b/x-pack/plugins/siem/cypress/screens/timeline.ts @@ -42,6 +42,8 @@ export const TIMELINE_INSPECT_BUTTON = '[data-test-subj="inspect-empty-button"]' export const TIMELINE_NOT_READY_TO_DROP_BUTTON = '[data-test-subj="flyout-button-not-ready-to-drop"]'; +export const TIMELINE_QUERY = '[data-test-subj="timelineQueryInput"]'; + export const TIMELINE_SETTINGS_ICON = '[data-test-subj="settings-gear"]'; export const TIMELINE_TITLE = '[data-test-subj="timeline-title"]'; diff --git a/x-pack/plugins/siem/cypress/tasks/all_cases.ts b/x-pack/plugins/siem/cypress/tasks/all_cases.ts new file mode 100644 index 0000000000000..f374532201324 --- /dev/null +++ b/x-pack/plugins/siem/cypress/tasks/all_cases.ts @@ -0,0 +1,15 @@ +/* + * 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 { ALL_CASES_NAME, ALL_CASES_CREATE_NEW_CASE_BTN } from '../screens/all_cases'; + +export const goToCreateNewCase = () => { + cy.get(ALL_CASES_CREATE_NEW_CASE_BTN).click({ force: true }); +}; + +export const goToCaseDetails = () => { + cy.get(ALL_CASES_NAME).click({ force: true }); +}; diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/tags/proportion.js b/x-pack/plugins/siem/cypress/tasks/case_details.ts similarity index 52% rename from x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/tags/proportion.js rename to x-pack/plugins/siem/cypress/tasks/case_details.ts index 6a59a6795d45a..a28f8b8010adb 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/tags/proportion.js +++ b/x-pack/plugins/siem/cypress/tasks/case_details.ts @@ -4,7 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { euiPaletteColorBlind } from '@elastic/eui'; -const euiVisPalette = euiPaletteColorBlind(); +import { TIMELINE_TITLE } from '../screens/timeline'; -export const proportion = () => ({ name: 'proportion', color: euiVisPalette[3] }); +export const openCaseTimeline = (link: string) => { + cy.visit('/app/kibana'); + cy.visit(link); + cy.contains('a', 'SIEM'); + cy.get(TIMELINE_TITLE).should('exist'); +}; diff --git a/x-pack/plugins/siem/cypress/tasks/create_new_case.ts b/x-pack/plugins/siem/cypress/tasks/create_new_case.ts new file mode 100644 index 0000000000000..b7078a1033de8 --- /dev/null +++ b/x-pack/plugins/siem/cypress/tasks/create_new_case.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { TestCase } from '../objects/case'; + +import { + BACK_TO_CASES_BTN, + DESCRIPTION_INPUT, + SUBMIT_BTN, + INSERT_TIMELINE_BTN, + LOADING_SPINNER, + TAGS_INPUT, + TIMELINE, + TIMELINE_SEARCHBOX, + TITLE_INPUT, +} from '../screens/create_new_case'; + +export const backToCases = () => { + cy.get(BACK_TO_CASES_BTN).click({ force: true }); +}; + +export const createNewCase = (newCase: TestCase) => { + cy.get(TITLE_INPUT).type(newCase.name, { force: true }); + newCase.tags.forEach(tag => { + cy.get(TAGS_INPUT).type(`${tag}{enter}`, { force: true }); + }); + cy.get(DESCRIPTION_INPUT).type(`${newCase.description} `, { force: true }); + + cy.get(INSERT_TIMELINE_BTN).click({ force: true }); + cy.get(TIMELINE_SEARCHBOX).type(`${newCase.timeline.title}{enter}`); + cy.get(TIMELINE).should('be.visible'); + cy.get(TIMELINE) + .eq(1) + .click({ force: true }); + + cy.get(SUBMIT_BTN).click({ force: true }); + cy.get(LOADING_SPINNER).should('exist'); + cy.get(LOADING_SPINNER).should('not.exist'); +}; diff --git a/x-pack/plugins/siem/cypress/urls/navigation.ts b/x-pack/plugins/siem/cypress/urls/navigation.ts index 5e65e5aa34c18..263469a4dbaed 100644 --- a/x-pack/plugins/siem/cypress/urls/navigation.ts +++ b/x-pack/plugins/siem/cypress/urls/navigation.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +export const CASES = '/app/siem#/case'; export const DETECTIONS = 'app/siem#/detections'; export const HOSTS_PAGE = '/app/siem#/hosts/allHosts'; export const HOSTS_PAGE_TAB_URLS = { diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 8a606e230dc36..1b77dfb168e88 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -548,6 +548,8 @@ "dashboard.dashboardGrid.toast.unableToLoadDashboardDangerMessage": "ダッシュボードが読み込めません。", "dashboard.factory.displayName": "ダッシュボード", "dashboard.panel.removePanel.replacePanel": "パネルの交換", + "dashboard.panel.unableToMigratePanelDataForSixOneZeroErrorMessage": "「6.1.0」のダッシュボードの互換性のため、パネルデータを移行できませんでした。パネルには想定された列または行フィールドがありません", + "dashboard.panel.unableToMigratePanelDataForSixThreeZeroErrorMessage": "「6.3.0」のダッシュボードの互換性のため、パネルデータを移行できませんでした。パネルに必要なフィールドがありません: {key}", "data.aggTypes.buckets.ranges.rangesFormatMessage": "{gte} {from} と {lt} {to}", "data.common.kql.errors.endOfInputText": "インプットの終わり", "data.common.kql.errors.fieldNameText": "フィールド名", @@ -2002,8 +2004,6 @@ "kbn.context.reloadPageDescription.selectValidAnchorDocumentTextMessage": "にアクセスして有効な別のドキュメントを選択してください。", "kbn.context.unableToLoadAnchorDocumentDescription": "別のドキュメントが読み込めません", "kbn.context.unableToLoadDocumentDescription": "ドキュメントが読み込めません", - "kbn.dashboard.panel.unableToMigratePanelDataForSixOneZeroErrorMessage": "「6.1.0」のダッシュボードの互換性のため、パネルデータを移行できませんでした。パネルには想定された列または行フィールドがありません", - "kbn.dashboard.panel.unableToMigratePanelDataForSixThreeZeroErrorMessage": "「6.3.0」のダッシュボードの互換性のため、パネルデータを移行できませんでした。パネルに必要なフィールドがありません: {key}", "kbn.dashboardTitle": "ダッシュボード", "kbn.devToolsTitle": "開発ツール", "kbn.discover.backToTopLinkText": "最上部へ戻る。", @@ -2295,6 +2295,8 @@ "kbn.management.landing.header": "Kibana {version} 管理", "kbn.management.landing.subhead": "インデックス、インデックスパターン、保存されたオブジェクト、Kibana の設定、その他を管理します。", "kbn.management.landing.text": "すべてのツールの一覧は、左のメニューにあります。", + "kbn.managementTitle": "管理", + "kbn.visualizeTitle": "可視化", "savedObjectsManagement.indexPattern.confirmOverwriteButton": "上書き", "savedObjectsManagement.indexPattern.confirmOverwriteLabel": "「{title}」に上書きしてよろしいですか?", "savedObjectsManagement.indexPattern.confirmOverwriteTitle": "{type} を上書きしますか?", @@ -2416,8 +2418,6 @@ "savedObjectsManagement.breadcrumb.index": "保存されたオブジェクト", "savedObjectsManagement.field.offLabel": "オフ", "savedObjectsManagement.field.onLabel": "オン", - "kbn.managementTitle": "管理", - "kbn.visualizeTitle": "可視化", "kibana_legacy.bigUrlWarningNotificationMessage": "{advancedSettingsLink}で{storeInSessionStorageParam}オプションを有効にするか、オンスクリーンビジュアルを簡素化してください。", "kibana_legacy.bigUrlWarningNotificationMessage.advancedSettingsLinkText": "高度な設定", "kibana_legacy.bigUrlWarningNotificationTitle": "URLが大きく、Kibanaの動作が停止する可能性があります", @@ -5021,8 +5021,6 @@ "xpack.canvas.elements.bubbleChartHelpText": "カスタマイズ可能なバブルチャートです", "xpack.canvas.elements.debugDisplayName": "デバッグ", "xpack.canvas.elements.debugHelpText": "エレメントの構成をダンプします", - "xpack.canvas.elements.donutChartDisplayName": "ドーナッツチャート", - "xpack.canvas.elements.donutChartHelpText": "カスタマイズ可能なドーナッツチャートです", "xpack.canvas.elements.dropdownFilterDisplayName": "ドロップダウンフィルター", "xpack.canvas.elements.dropdownFilterHelpText": "「exactly」フィルターの値を選択できるドロップダウンです", "xpack.canvas.elements.horizontalBarChartDisplayName": "水平棒グラフ", @@ -5057,8 +5055,6 @@ "xpack.canvas.elements.shapeHelpText": "カスタマイズ可能な図形です", "xpack.canvas.elements.tableDisplayName": "データテーブル", "xpack.canvas.elements.tableHelpText": "データをチューブ形式で表示する、スクロール可能なグリッドです", - "xpack.canvas.elements.tiltedPieDisplayName": "傾き円グラフ", - "xpack.canvas.elements.tiltedPieHelpText": "カスタマイズ可能な傾き円グラフです", "xpack.canvas.elements.timeFilterDisplayName": "時間フィルター", "xpack.canvas.elements.timeFilterHelpText": "期間を設定します", "xpack.canvas.elements.verticalBarChartDisplayName": "垂直棒グラフ", @@ -5069,16 +5065,16 @@ "xpack.canvas.elements.verticalProgressPillHelpText": "進捗状況を垂直のピルで表示します", "xpack.canvas.elementSettings.dataTabLabel": "データ", "xpack.canvas.elementSettings.displayTabLabel": "表示", - "xpack.canvas.elementTypes.addNewElementDescription": "ワークパッドのエレメントをグループ化して保存し、新規エレメントを作成します", - "xpack.canvas.elementTypes.addNewElementTitle": "新規エレメントの作成", - "xpack.canvas.elementTypes.cancelButtonLabel": "キャンセル", - "xpack.canvas.elementTypes.deleteButtonLabel": "削除", - "xpack.canvas.elementTypes.deleteElementDescription": "このエレメントを削除してよろしいですか?", - "xpack.canvas.elementTypes.deleteElementTitle": "エレメント「{elementName}」を削除しますか?", - "xpack.canvas.elementTypes.editElementTitle": "エレメントを編集", - "xpack.canvas.elementTypes.elementsTitle": "エレメント", - "xpack.canvas.elementTypes.findElementPlaceholder": "エレメントを検索", - "xpack.canvas.elementTypes.myElementsTitle": "マイエレメント", + "xpack.canvas.savedElementsModal.addNewElementDescription": "ワークパッドのエレメントをグループ化して保存し、新規エレメントを作成します", + "xpack.canvas.savedElementsModal.addNewElementTitle": "新規エレメントの作成", + "xpack.canvas.savedElementsModal.cancelButtonLabel": "キャンセル", + "xpack.canvas.savedElementsModal.deleteButtonLabel": "削除", + "xpack.canvas.savedElementsModal.deleteElementDescription": "このエレメントを削除してよろしいですか?", + "xpack.canvas.savedElementsModal.deleteElementTitle": "エレメント「{elementName}」を削除しますか?", + "xpack.canvas.savedElementsModal.editElementTitle": "エレメントを編集", + "xpack.canvas.savedElementsModal.elementsTitle": "エレメント", + "xpack.canvas.savedElementsModal.findElementPlaceholder": "エレメントを検索", + "xpack.canvas.savedElementsModal.myElementsTitle": "マイエレメント", "xpack.canvas.embedObject.noMatchingObjectsMessage": "一致するオブジェクトが見つかりませんでした。", "xpack.canvas.embedObject.titleText": "オブジェクトの埋め込み", "xpack.canvas.error.actionsElements.invaludArgIndexErrorMessage": "無効な引数インデックス: {index}", @@ -5591,13 +5587,8 @@ "xpack.canvas.sidebarHeader.topAlignMenuItemLabel": "一番上", "xpack.canvas.sidebarHeader.ungroupMenuItemLabel": "グループ解除", "xpack.canvas.sidebarHeader.verticalDistributionMenutItemLabel": "縦", - "xpack.canvas.tags.chartTag": "チャート", - "xpack.canvas.tags.filterTag": "フィルター", - "xpack.canvas.tags.graphicTag": "グラフィック", "xpack.canvas.tags.presentationTag": "プレゼンテーション", - "xpack.canvas.tags.proportionTag": "比率", "xpack.canvas.tags.reportTag": "レポート", - "xpack.canvas.tags.textTag": "テキスト", "xpack.canvas.templates.darkHelp": "ダークカラーテーマのプレゼンテーションデッキです", "xpack.canvas.templates.darkName": "ダーク", "xpack.canvas.templates.lightHelp": "ライトカラーテーマのプレゼンテーションデッキです", @@ -5897,7 +5888,6 @@ "xpack.canvas.workpadHeader.cycleIntervalHoursText": "{hours} {hours, plural, one {時間} other {時間}}ごと", "xpack.canvas.workpadHeader.cycleIntervalMinutesText": "{minutes} {minutes, plural, one {分} other {分}}ごと", "xpack.canvas.workpadHeader.cycleIntervalSecondsText": "{seconds} {seconds, plural, one {秒} other {秒}}ごと", - "xpack.canvas.workpadHeader.embedObjectButtonLabel": "オブジェクトを埋め込む", "xpack.canvas.workpadHeader.fullscreenButtonAriaLabel": "全画面表示", "xpack.canvas.workpadHeader.fullscreenTooltip": "全画面モードを開始します", "xpack.canvas.workpadHeader.hideEditControlTooltip": "編集コントロールを非表示にします", @@ -5907,7 +5897,6 @@ "xpack.canvas.workpadHeaderAutoRefreshControls.intervalFormLabel": "自動更新間隔を変更します", "xpack.canvas.workpadHeaderAutoRefreshControls.refreshListDurationManualText": "手動で", "xpack.canvas.workpadHeaderAutoRefreshControls.refreshListTitle": "エレメントを更新", - "xpack.canvas.workpadHeaderControlSettings.settingsTooltip": "設定をコントロールします", "xpack.canvas.workpadHeaderCustomInterval.confirmButtonLabel": "設定", "xpack.canvas.workpadHeaderCustomInterval.formDescription": "{secondsExample}、{minutesExample}、{hoursExample} のような短い表記を使用します", "xpack.canvas.workpadHeaderCustomInterval.formLabel": "カスタム間隔を設定", @@ -5916,32 +5905,32 @@ "xpack.canvas.workpadHeaderKioskControl.cycleToggleSwitch": "スライドを自動的にサイクル", "xpack.canvas.workpadHeaderRefreshControlSettings.refreshAriaLabel": "エレメントを更新", "xpack.canvas.workpadHeaderRefreshControlSettings.refreshTooltip": "データを更新", - "xpack.canvas.workpadHeaderWorkpadExport.copyPDFMessage": "{PDF} 生成 {URL} がクリップボードにコピーされました。", - "xpack.canvas.workpadHeaderWorkpadExport.copyReportingConfigMessage": "レポート構成がクリップボードにコピーされました", - "xpack.canvas.workpadHeaderWorkpadExport.copyShareConfigMessage": "共有マークアップがクリップボードにコピーされました", - "xpack.canvas.workpadHeaderWorkpadExport.exportPDFErrorMessage": "「{workpadName}」の {PDF} の作成に失敗しました", - "xpack.canvas.workpadHeaderWorkpadExport.exportPDFMessage": "{PDF} をエクスポート中です。管理で進捗を確認できます。", - "xpack.canvas.workpadHeaderWorkpadExport.exportPDFTitle": "ワークパッド「{workpadName}」の {PDF} エクスポート", - "xpack.canvas.workpadHeaderWorkpadExport.pdfPanelCopyAriaLabel": "この {URL} を使用してスクリプトから、または Watcher で {PDF} を生成することもできます。{URL} をクリップボードにコピーするにはエンターキーを押してください。", - "xpack.canvas.workpadHeaderWorkpadExport.pdfPanelCopyButtonLabel": "{POST} {URL} をコピー", - "xpack.canvas.workpadHeaderWorkpadExport.pdfPanelCopyDescription": "{POST} {URL} をコピーして {KIBANA} 外または ウォッチャー から生成することもできます。", - "xpack.canvas.workpadHeaderWorkpadExport.pdfPanelGenerateButtonLabel": "{PDF} を生成", - "xpack.canvas.workpadHeaderWorkpadExport.pdfPanelGenerateDescription": "ワークパッドのサイズによって、{PDF} の生成には数分かかる場合があります。", - "xpack.canvas.workpadHeaderWorkpadExport.shareDownloadJSONTitle": "{JSON} をダウンロード", - "xpack.canvas.workpadHeaderWorkpadExport.shareDownloadPDFTitle": "{PDF} レポート", - "xpack.canvas.workpadHeaderWorkpadExport.shareWebsiteErrorTitle": "「{workpadName}」の {ZIP} ファイルの作成に失敗しました。ワークパッドが大きすぎる可能性があります。ファイルを別々にダウンロードする必要があります。", - "xpack.canvas.workpadHeaderWorkpadExport.shareWebsiteTitle": "Web サイトで共有", - "xpack.canvas.workpadHeaderWorkpadExport.shareWorkpadMessage": "このワークパッドを共有", - "xpack.canvas.workpadHeaderWorkpadExport.unknownExportErrorMessage": "未知のエクスポートタイプ: {type}", - "xpack.canvas.workpadHeaderWorkpadExport.unsupportedRendererWarning": "このワークパッドには {CANVAS} シェアラブルワークパッドランタイムがサポートしていないレンダリング関数が含まれています。これらのエレメントはレンダリングされません:", - "xpack.canvas.workpadHeaderWorkpadZoom.zoomControlsAriaLabel": "ズームコントロール", - "xpack.canvas.workpadHeaderWorkpadZoom.zoomControlsTooltip": "ズームコントロール", - "xpack.canvas.workpadHeaderWorkpadZoom.zoomFitToWindowText": "ウィンドウに合わせる", - "xpack.canvas.workpadHeaderWorkpadZoom.zoomInText": "ズームイン", - "xpack.canvas.workpadHeaderWorkpadZoom.zoomOutText": "ズームアウト", - "xpack.canvas.workpadHeaderWorkpadZoom.zoomPanelTitle": "ズーム:", - "xpack.canvas.workpadHeaderWorkpadZoom.zoomPrecentageValue": "リセット", - "xpack.canvas.workpadHeaderWorkpadZoom.zoomResetText": "{scalePercentage}%", + "xpack.canvas.workpadHeaderShareMenu.copyPDFMessage": "{PDF} 生成 {URL} がクリップボードにコピーされました。", + "xpack.canvas.workpadHeaderShareMenu.copyReportingConfigMessage": "レポート構成がクリップボードにコピーされました", + "xpack.canvas.workpadHeaderShareMenu.copyShareConfigMessage": "共有マークアップがクリップボードにコピーされました", + "xpack.canvas.workpadHeaderShareMenu.exportPDFErrorMessage": "「{workpadName}」の {PDF} の作成に失敗しました", + "xpack.canvas.workpadHeaderShareMenu.exportPDFMessage": "{PDF} をエクスポート中です。管理で進捗を確認できます。", + "xpack.canvas.workpadHeaderShareMenu.exportPDFTitle": "ワークパッド「{workpadName}」の {PDF} エクスポート", + "xpack.canvas.workpadHeaderShareMenu.pdfPanelCopyAriaLabel": "この {URL} を使用してスクリプトから、または Watcher で {PDF} を生成することもできます。{URL} をクリップボードにコピーするにはエンターキーを押してください。", + "xpack.canvas.workpadHeaderShareMenu.pdfPanelCopyButtonLabel": "{POST} {URL} をコピー", + "xpack.canvas.workpadHeaderShareMenu.pdfPanelCopyDescription": "{POST} {URL} をコピーして {KIBANA} 外または ウォッチャー から生成することもできます。", + "xpack.canvas.workpadHeaderShareMenu.pdfPanelGenerateButtonLabel": "{PDF} を生成", + "xpack.canvas.workpadHeaderShareMenu.pdfPanelGenerateDescription": "ワークパッドのサイズによって、{PDF} の生成には数分かかる場合があります。", + "xpack.canvas.workpadHeaderShareMenu.shareDownloadJSONTitle": "{JSON} をダウンロード", + "xpack.canvas.workpadHeaderShareMenu.shareDownloadPDFTitle": "{PDF} レポート", + "xpack.canvas.workpadHeaderShareMenu.shareWebsiteErrorTitle": "「{workpadName}」の {ZIP} ファイルの作成に失敗しました。ワークパッドが大きすぎる可能性があります。ファイルを別々にダウンロードする必要があります。", + "xpack.canvas.workpadHeaderShareMenu.shareWebsiteTitle": "Web サイトで共有", + "xpack.canvas.workpadHeaderShareMenu.shareWorkpadMessage": "このワークパッドを共有", + "xpack.canvas.workpadHeaderShareMenu.unknownExportErrorMessage": "未知のエクスポートタイプ: {type}", + "xpack.canvas.workpadHeaderShareMenu.unsupportedRendererWarning": "このワークパッドには {CANVAS} シェアラブルワークパッドランタイムがサポートしていないレンダリング関数が含まれています。これらのエレメントはレンダリングされません:", + "xpack.canvas.workpadHeaderViewMenu.zoomControlsAriaLabel": "ズームコントロール", + "xpack.canvas.workpadHeaderViewMenu.zoomControlsTooltip": "ズームコントロール", + "xpack.canvas.workpadHeaderViewMenu.zoomFitToWindowText": "ウィンドウに合わせる", + "xpack.canvas.workpadHeaderViewMenu.zoomInText": "ズームイン", + "xpack.canvas.workpadHeaderViewMenu.zoomOutText": "ズームアウト", + "xpack.canvas.workpadHeaderViewMenu.zoomPanelTitle": "ズーム:", + "xpack.canvas.workpadHeaderViewMenu.zoomPrecentageValue": "リセット", + "xpack.canvas.workpadHeaderViewMenu.zoomResetText": "{scalePercentage}%", "xpack.canvas.workpadLoader.clonedWorkpadName": "{workpadName} のコピー", "xpack.canvas.workpadLoader.cloneTooltip": "ワークパッドのクローンを作成します", "xpack.canvas.workpadLoader.createWorkpadLoadingDescription": "ワークパッドを作成中...", @@ -6098,9 +6087,6 @@ "xpack.crossClusterReplication.autoFollowPatternList.table.statusTextPaused": "一時停止中", "xpack.crossClusterReplication.autoFollowPatternList.table.statusTitle": "ステータス", "xpack.crossClusterReplication.autoFollowPatternList.table.suffixColumnTitle": "フォロワーインデックスの接尾辞", - "xpack.crossClusterReplication.checkLicense.errorExpiredMessage": "{licenseType} ライセンスが期限切れのため {pluginName} を使用できません", - "xpack.crossClusterReplication.checkLicense.errorUnavailableMessage": "現在ライセンス情報が利用できないため {pluginName} を使用できません。", - "xpack.crossClusterReplication.checkLicense.errorUnsupportedMessage": "ご使用の {licenseType} ライセンスは {pluginName} をサポートしていません。ライセンスをアップグレードしてください。", "xpack.crossClusterReplication.deleteAutoFollowPattern.confirmModal.cancelButtonText": "キャンセル", "xpack.crossClusterReplication.deleteAutoFollowPattern.confirmModal.confirmButtonText": "削除", "xpack.crossClusterReplication.deleteAutoFollowPattern.confirmModal.deleteMultipleTitle": "{count} 個の自動フォローパターンを削除しますか?", @@ -8304,7 +8290,6 @@ "xpack.ingestManager.agentDetails.statusLabel": "ステータス", "xpack.ingestManager.agentDetails.typeLabel": "タイプ", "xpack.ingestManager.agentDetails.unavailableConfigTooltipText": "この構成は利用できなくなりました", - "xpack.ingestManager.agentDetails.unenrollButtonText": "登録解除", "xpack.ingestManager.agentDetails.unexceptedErrorTitle": "エージェントを読み込む間にエラーが発生しました", "xpack.ingestManager.agentDetails.userProvidedMetadataSectionSubtitle": "ユーザー提供メタデータ", "xpack.ingestManager.agentEnrollment.apiKeySelectionDescription": "ご希望のエージェント構成とプラットフォームをすばやく選択できます。次いで、以下の手順に従ってエージェントをセットアップして登録します。", @@ -8336,8 +8321,6 @@ "xpack.ingestManager.agentList.actionsColumnTitle": "アクション", "xpack.ingestManager.agentList.actionsMenuText": "開く", "xpack.ingestManager.agentList.addButton": "新しいエージェントをインストール", - "xpack.ingestManager.agentList.agentsOnPageSelectedMessage": "このページで {count, plural, one {# エージェント} other {# エージェント}}が選択されます。{selectAllLink}", - "xpack.ingestManager.agentList.allAgentsSelectedMessage": "{count} エージェントすべてが選択されます。{clearSelectionLink}", "xpack.ingestManager.agentList.clearFiltersLinkText": "フィルターを消去", "xpack.ingestManager.agentList.configColumnTitle": "構成", "xpack.ingestManager.agentList.configFilterText": "構成", @@ -8349,15 +8332,12 @@ "xpack.ingestManager.agentList.noFilteredAgentsPrompt": "エージェントが見つかりません。{clearFiltersLink}", "xpack.ingestManager.agentList.outOfDateLabel": "最新ではありません", "xpack.ingestManager.agentList.revisionNumber": "rev. {revNumber}", - "xpack.ingestManager.agentList.selectAllAgentsLinkText": "{count} エージェントすべてを選択", - "xpack.ingestManager.agentList.selectPageAgentsLinkText": "このページのみを選択", "xpack.ingestManager.agentList.showInactiveSwitchLabel": "非アクティブエージェントを表示", "xpack.ingestManager.agentList.statusColumnTitle": "ステータス", "xpack.ingestManager.agentList.statusErrorFilterText": "エラー", "xpack.ingestManager.agentList.statusFilterText": "ステータス", "xpack.ingestManager.agentList.statusOfflineFilterText": "オフライン", "xpack.ingestManager.agentList.statusOnlineFilterText": "オンライン", - "xpack.ingestManager.agentList.unenrollButton": "{count, plural, one {# エージェント} other {# エージェント}} の登録を解除", "xpack.ingestManager.agentList.unenrollOneButton": "登録解除", "xpack.ingestManager.agentList.versionTitle": "バージョン", "xpack.ingestManager.agentList.viewActionText": "エージェントの表示", @@ -8505,10 +8485,7 @@ "xpack.ingestManager.unenrollAgents.confirmModal.deleteMultipleTitle": "{count, plural, one {# エージェント} other {# エージェント}}の登録を解除しますか?", "xpack.ingestManager.unenrollAgents.confirmModal.deleteSingleTitle": "エージェント「{id}」の登録を解除しますか?", "xpack.ingestManager.unenrollAgents.confirmModal.loadingButtonLabel": "読み込み中...", - "xpack.ingestManager.unenrollAgents.failureMultipleNotificationTitle": "{count} 件のエージェントの登録解除エラー", - "xpack.ingestManager.unenrollAgents.failureSingleNotificationTitle": "エージェント「{id}」の登録解除エラー", "xpack.ingestManager.unenrollAgents.fatalErrorNotificationTitle": "エージェントの登録解除エラー", - "xpack.ingestManager.unenrollAgents.successMultipleNotificationTitle": "{count} 件のエージェントの登録を解除しました", "xpack.ingestManager.unenrollAgents.successSingleNotificationTitle": "エージェント「{id}」の登録を解除しました", "xpack.ingestManager.yamlConfig.instructionDescription": "この構成でエージェントを登録するには、ホストで次のコマンドをコピーして実行します。", "xpack.ingestManager.yamlConfig.instructionTittle": "フリートに登録", @@ -10922,7 +10899,6 @@ "xpack.monitoring.alerts.licenseExpiration.actionGroups.default": "デフォルト", "xpack.monitoring.alerts.licenseExpiration.newSubject": "NEW X-Pack 監視:ライセンス期限", "xpack.monitoring.alerts.licenseExpiration.resolvedSubject": "RESOLVED X-Pack 監視:ライセンス期限", - "xpack.monitoring.alerts.licenseExpiration.ui.firingMessage": "このクラスターのライセンスは、#relative で #absolute に期限が切れます。", "xpack.monitoring.alerts.licenseExpiration.ui.resolvedMessage": "このクラスターのライセンスはアクティブです。", "xpack.monitoring.alerts.lowSeverityName": "低", "xpack.monitoring.alerts.mediumSeverityName": "中", @@ -16738,4 +16714,4 @@ "xpack.watcher.watchEdit.thresholdWatchExpression.aggType.fieldIsRequiredValidationMessage": "フィールドを選択してください。", "xpack.watcher.watcherDescription": "アラートの作成、管理、監視によりデータへの変更を検知します。" } -} \ No newline at end of file +} diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index faee95b8172b7..ce2469f29b883 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -548,6 +548,8 @@ "dashboard.dashboardGrid.toast.unableToLoadDashboardDangerMessage": "无法加载仪表板。", "dashboard.factory.displayName": "仪表板", "dashboard.panel.removePanel.replacePanel": "替换面板", + "dashboard.panel.unableToMigratePanelDataForSixOneZeroErrorMessage": "无法迁移用于“6.1.0”向后兼容的面板数据,面板不包含所需的列和/或行字段", + "dashboard.panel.unableToMigratePanelDataForSixThreeZeroErrorMessage": "无法迁移用于“6.3.0”向后兼容的面板数据,面板不包含预期字段:{key}", "data.aggTypes.buckets.ranges.rangesFormatMessage": "{gte} {from} 和 {lt} {to}", "data.common.kql.errors.endOfInputText": "输入结束", "data.common.kql.errors.fieldNameText": "字段名称", @@ -2003,8 +2005,6 @@ "kbn.context.reloadPageDescription.selectValidAnchorDocumentTextMessage": "以选择有效地定位点文档。", "kbn.context.unableToLoadAnchorDocumentDescription": "无法加载该定位点文档", "kbn.context.unableToLoadDocumentDescription": "无法加载文档", - "kbn.dashboard.panel.unableToMigratePanelDataForSixOneZeroErrorMessage": "无法迁移用于“6.1.0”向后兼容的面板数据,面板不包含所需的列和/或行字段", - "kbn.dashboard.panel.unableToMigratePanelDataForSixThreeZeroErrorMessage": "无法迁移用于“6.3.0”向后兼容的面板数据,面板不包含预期字段:{key}", "kbn.dashboardTitle": "仪表板", "kbn.devToolsTitle": "开发工具", "kbn.discover.backToTopLinkText": "返至顶部。", @@ -2296,6 +2296,8 @@ "kbn.management.landing.header": "Kibana {version} 管理", "kbn.management.landing.subhead": "管理您的索引、索引模式、已保存对象、Kibana 设置等等。", "kbn.management.landing.text": "应用的完整列表位于左侧菜单中。", + "kbn.managementTitle": "管理", + "kbn.visualizeTitle": "可视化", "savedObjectsManagement.indexPattern.confirmOverwriteButton": "覆盖", "savedObjectsManagement.indexPattern.confirmOverwriteLabel": "确定要覆盖 “{title}”?", "savedObjectsManagement.indexPattern.confirmOverwriteTitle": "覆盖“{type}”?", @@ -2417,8 +2419,6 @@ "savedObjectsManagement.view.viewItemTitle": "查看“{title}”", "savedObjectsManagement.breadcrumb.edit": "编辑 {savedObjectType}", "savedObjectsManagement.breadcrumb.index": "已保存对象", - "kbn.managementTitle": "管理", - "kbn.visualizeTitle": "可视化", "kibana_legacy.bigUrlWarningNotificationMessage": "在{advancedSettingsLink}中启用“{storeInSessionStorageParam}”选项或简化屏幕视觉效果。", "kibana_legacy.bigUrlWarningNotificationMessage.advancedSettingsLinkText": "高级设置", "kibana_legacy.bigUrlWarningNotificationTitle": "URL 过长,Kibana 可能无法工作", @@ -5022,8 +5022,6 @@ "xpack.canvas.elements.bubbleChartHelpText": "可定制的气泡图", "xpack.canvas.elements.debugDisplayName": "“Debug”(故障排查)", "xpack.canvas.elements.debugHelpText": "只需丢弃元素的配置", - "xpack.canvas.elements.donutChartDisplayName": "圆环图", - "xpack.canvas.elements.donutChartHelpText": "可定制的圆环图", "xpack.canvas.elements.dropdownFilterDisplayName": "下拉列表筛选", "xpack.canvas.elements.dropdownFilterHelpText": "可以从其中为“完全”筛选选择值的下拉列表", "xpack.canvas.elements.horizontalBarChartDisplayName": "水平条形图", @@ -5058,8 +5056,6 @@ "xpack.canvas.elements.shapeHelpText": "可定制的形状", "xpack.canvas.elements.tableDisplayName": "数据表", "xpack.canvas.elements.tableHelpText": "用于以表格形式显示数据的可滚动网格", - "xpack.canvas.elements.tiltedPieDisplayName": "斜饼图", - "xpack.canvas.elements.tiltedPieHelpText": "可定制的斜饼图", "xpack.canvas.elements.timeFilterDisplayName": "时间筛选", "xpack.canvas.elements.timeFilterHelpText": "设置时间窗口", "xpack.canvas.elements.verticalBarChartDisplayName": "垂直条形图", @@ -5070,16 +5066,16 @@ "xpack.canvas.elements.verticalProgressPillHelpText": "将进度显示为垂直胶囊的一部分", "xpack.canvas.elementSettings.dataTabLabel": "数据", "xpack.canvas.elementSettings.displayTabLabel": "显示", - "xpack.canvas.elementTypes.addNewElementDescription": "分组并保存 Workpad 元素以创建新元素", - "xpack.canvas.elementTypes.addNewElementTitle": "添加新元素", - "xpack.canvas.elementTypes.cancelButtonLabel": "取消", - "xpack.canvas.elementTypes.deleteButtonLabel": "删除", - "xpack.canvas.elementTypes.deleteElementDescription": "确定要删除此元素?", - "xpack.canvas.elementTypes.deleteElementTitle": "删除元素“{elementName}”?", - "xpack.canvas.elementTypes.editElementTitle": "编辑元素", - "xpack.canvas.elementTypes.elementsTitle": "元素", - "xpack.canvas.elementTypes.findElementPlaceholder": "查找元素", - "xpack.canvas.elementTypes.myElementsTitle": "我的元素", + "xpack.canvas.savedElementsModal.addNewElementDescription": "分组并保存 Workpad 元素以创建新元素", + "xpack.canvas.savedElementsModal.addNewElementTitle": "添加新元素", + "xpack.canvas.savedElementsModal.cancelButtonLabel": "取消", + "xpack.canvas.savedElementsModal.deleteButtonLabel": "删除", + "xpack.canvas.savedElementsModal.deleteElementDescription": "确定要删除此元素?", + "xpack.canvas.savedElementsModal.deleteElementTitle": "删除元素“{elementName}”?", + "xpack.canvas.savedElementsModal.editElementTitle": "编辑元素", + "xpack.canvas.savedElementsModal.elementsTitle": "元素", + "xpack.canvas.savedElementsModal.findElementPlaceholder": "查找元素", + "xpack.canvas.savedElementsModal.myElementsTitle": "我的元素", "xpack.canvas.embedObject.noMatchingObjectsMessage": "未找到任何匹配对象。", "xpack.canvas.embedObject.titleText": "嵌入对象", "xpack.canvas.error.actionsElements.invaludArgIndexErrorMessage": "无效的参数索引:{index}", @@ -5592,13 +5588,8 @@ "xpack.canvas.sidebarHeader.topAlignMenuItemLabel": "上", "xpack.canvas.sidebarHeader.ungroupMenuItemLabel": "取消分组", "xpack.canvas.sidebarHeader.verticalDistributionMenutItemLabel": "垂直", - "xpack.canvas.tags.chartTag": "图表", - "xpack.canvas.tags.filterTag": "筛选", - "xpack.canvas.tags.graphicTag": "图形", "xpack.canvas.tags.presentationTag": "演示", - "xpack.canvas.tags.proportionTag": "比例", "xpack.canvas.tags.reportTag": "报告", - "xpack.canvas.tags.textTag": "文本", "xpack.canvas.templates.darkHelp": "深色主题的演示幻灯片", "xpack.canvas.templates.darkName": "深色", "xpack.canvas.templates.lightHelp": "浅色主题的演示幻灯片", @@ -5899,7 +5890,6 @@ "xpack.canvas.workpadHeader.cycleIntervalHoursText": "每 {hours} {hours, plural, one {小时} other {小时}}", "xpack.canvas.workpadHeader.cycleIntervalMinutesText": "每 {minutes} {minutes, plural, one {分钟} other {分钟}}", "xpack.canvas.workpadHeader.cycleIntervalSecondsText": "每 {seconds} {seconds, plural, one {秒} other {秒}}", - "xpack.canvas.workpadHeader.embedObjectButtonLabel": "嵌入对象", "xpack.canvas.workpadHeader.fullscreenButtonAriaLabel": "全屏查看", "xpack.canvas.workpadHeader.fullscreenTooltip": "进入全屏模式", "xpack.canvas.workpadHeader.hideEditControlTooltip": "隐藏编辑控件", @@ -5909,7 +5899,6 @@ "xpack.canvas.workpadHeaderAutoRefreshControls.intervalFormLabel": "更改自动刷新时间间隔", "xpack.canvas.workpadHeaderAutoRefreshControls.refreshListDurationManualText": "手动", "xpack.canvas.workpadHeaderAutoRefreshControls.refreshListTitle": "刷新元素", - "xpack.canvas.workpadHeaderControlSettings.settingsTooltip": "控制设置", "xpack.canvas.workpadHeaderCustomInterval.confirmButtonLabel": "设置", "xpack.canvas.workpadHeaderCustomInterval.formDescription": "使用速记表示法,如 {secondsExample}、{minutesExample} 或 {hoursExample}", "xpack.canvas.workpadHeaderCustomInterval.formLabel": "设置定制时间间隔", @@ -5918,32 +5907,32 @@ "xpack.canvas.workpadHeaderKioskControl.cycleToggleSwitch": "自动循环播放幻灯片", "xpack.canvas.workpadHeaderRefreshControlSettings.refreshAriaLabel": "刷新元素", "xpack.canvas.workpadHeaderRefreshControlSettings.refreshTooltip": "刷新数据", - "xpack.canvas.workpadHeaderWorkpadExport.copyPDFMessage": "{PDF} 生成 {URL} 已复制到剪贴板", - "xpack.canvas.workpadHeaderWorkpadExport.copyReportingConfigMessage": "已将报告配置复制到剪贴板", - "xpack.canvas.workpadHeaderWorkpadExport.copyShareConfigMessage": "已将共享标记复制到剪贴板", - "xpack.canvas.workpadHeaderWorkpadExport.exportPDFErrorMessage": "无法为“{workpadName}”创建 {PDF}", - "xpack.canvas.workpadHeaderWorkpadExport.exportPDFMessage": "正在导出 {PDF}。可以在“管理”中跟踪进度。", - "xpack.canvas.workpadHeaderWorkpadExport.exportPDFTitle": "Workpad“{workpadName}”的 {PDF} 导出", - "xpack.canvas.workpadHeaderWorkpadExport.pdfPanelCopyAriaLabel": "或者,也可以从脚本或使用 {URL} 通过 Watcher 生成 {PDF}。按 Enter 键可将 {URL} 复制到剪贴板。", - "xpack.canvas.workpadHeaderWorkpadExport.pdfPanelCopyButtonLabel": "复制 {POST} {URL}", - "xpack.canvas.workpadHeaderWorkpadExport.pdfPanelCopyDescription": "或者,复制此 {POST} {URL} 以从 {KIBANA} 外部或从 Watcher 调用生成。", - "xpack.canvas.workpadHeaderWorkpadExport.pdfPanelGenerateButtonLabel": "生成 {PDF}", - "xpack.canvas.workpadHeaderWorkpadExport.pdfPanelGenerateDescription": "{PDF} 可能会花费 1 或 2 分钟生成,取决于 Workpad 的大小。", - "xpack.canvas.workpadHeaderWorkpadExport.shareDownloadJSONTitle": "下载为 {JSON}", - "xpack.canvas.workpadHeaderWorkpadExport.shareDownloadPDFTitle": "{PDF} 报告", - "xpack.canvas.workpadHeaderWorkpadExport.shareWebsiteErrorTitle": "无法为“{workpadName}”创建 {ZIP} 文件。Workpad 可能过大。您将需要分别下载文件。", - "xpack.canvas.workpadHeaderWorkpadExport.shareWebsiteTitle": "在网站上共享", - "xpack.canvas.workpadHeaderWorkpadExport.shareWorkpadMessage": "共享此 Workpad", - "xpack.canvas.workpadHeaderWorkpadExport.unknownExportErrorMessage": "未知导出类型:{type}", - "xpack.canvas.workpadHeaderWorkpadExport.unsupportedRendererWarning": "此 Workpad 包含 {CANVAS} Shareable Workpad Runtime 不支持的呈现函数。将不会呈现以下元素:", - "xpack.canvas.workpadHeaderWorkpadZoom.zoomControlsAriaLabel": "缩放控制", - "xpack.canvas.workpadHeaderWorkpadZoom.zoomControlsTooltip": "缩放控制", - "xpack.canvas.workpadHeaderWorkpadZoom.zoomFitToWindowText": "适应窗口大小", - "xpack.canvas.workpadHeaderWorkpadZoom.zoomInText": "放大", - "xpack.canvas.workpadHeaderWorkpadZoom.zoomOutText": "缩小", - "xpack.canvas.workpadHeaderWorkpadZoom.zoomPanelTitle": "缩放", - "xpack.canvas.workpadHeaderWorkpadZoom.zoomPrecentageValue": "重置", - "xpack.canvas.workpadHeaderWorkpadZoom.zoomResetText": "{scalePercentage}%", + "xpack.canvas.workpadHeaderShareMenu.copyPDFMessage": "{PDF} 生成 {URL} 已复制到剪贴板", + "xpack.canvas.workpadHeaderShareMenu.copyReportingConfigMessage": "已将报告配置复制到剪贴板", + "xpack.canvas.workpadHeaderShareMenu.copyShareConfigMessage": "已将共享标记复制到剪贴板", + "xpack.canvas.workpadHeaderShareMenu.exportPDFErrorMessage": "无法为“{workpadName}”创建 {PDF}", + "xpack.canvas.workpadHeaderShareMenu.exportPDFMessage": "正在导出 {PDF}。可以在“管理”中跟踪进度。", + "xpack.canvas.workpadHeaderShareMenu.exportPDFTitle": "Workpad“{workpadName}”的 {PDF} 导出", + "xpack.canvas.workpadHeaderShareMenu.pdfPanelCopyAriaLabel": "或者,也可以从脚本或使用 {URL} 通过 Watcher 生成 {PDF}。按 Enter 键可将 {URL} 复制到剪贴板。", + "xpack.canvas.workpadHeaderShareMenu.pdfPanelCopyButtonLabel": "复制 {POST} {URL}", + "xpack.canvas.workpadHeaderShareMenu.pdfPanelCopyDescription": "或者,复制此 {POST} {URL} 以从 {KIBANA} 外部或从 Watcher 调用生成。", + "xpack.canvas.workpadHeaderShareMenu.pdfPanelGenerateButtonLabel": "生成 {PDF}", + "xpack.canvas.workpadHeaderShareMenu.pdfPanelGenerateDescription": "{PDF} 可能会花费 1 或 2 分钟生成,取决于 Workpad 的大小。", + "xpack.canvas.workpadHeaderShareMenu.shareDownloadJSONTitle": "下载为 {JSON}", + "xpack.canvas.workpadHeaderShareMenu.shareDownloadPDFTitle": "{PDF} 报告", + "xpack.canvas.workpadHeaderShareMenu.shareWebsiteErrorTitle": "无法为“{workpadName}”创建 {ZIP} 文件。Workpad 可能过大。您将需要分别下载文件。", + "xpack.canvas.workpadHeaderShareMenu.shareWebsiteTitle": "在网站上共享", + "xpack.canvas.workpadHeaderShareMenu.shareWorkpadMessage": "共享此 Workpad", + "xpack.canvas.workpadHeaderShareMenu.unknownExportErrorMessage": "未知导出类型:{type}", + "xpack.canvas.workpadHeaderShareMenu.unsupportedRendererWarning": "此 Workpad 包含 {CANVAS} Shareable Workpad Runtime 不支持的呈现函数。将不会呈现以下元素:", + "xpack.canvas.workpadHeaderViewMenu.zoomControlsAriaLabel": "缩放控制", + "xpack.canvas.workpadHeaderViewMenu.zoomControlsTooltip": "缩放控制", + "xpack.canvas.workpadHeaderViewMenu.zoomFitToWindowText": "适应窗口大小", + "xpack.canvas.workpadHeaderViewMenu.zoomInText": "放大", + "xpack.canvas.workpadHeaderViewMenu.zoomOutText": "缩小", + "xpack.canvas.workpadHeaderViewMenu.zoomPanelTitle": "缩放", + "xpack.canvas.workpadHeaderViewMenu.zoomPrecentageValue": "重置", + "xpack.canvas.workpadHeaderViewMenu.zoomResetText": "{scalePercentage}%", "xpack.canvas.workpadLoader.clonedWorkpadName": "{workpadName} 的副本", "xpack.canvas.workpadLoader.cloneTooltip": "克隆 Workpad", "xpack.canvas.workpadLoader.createWorkpadLoadingDescription": "正在创建 Workpad......", @@ -6100,9 +6089,6 @@ "xpack.crossClusterReplication.autoFollowPatternList.table.statusTextPaused": "已暂停", "xpack.crossClusterReplication.autoFollowPatternList.table.statusTitle": "状态", "xpack.crossClusterReplication.autoFollowPatternList.table.suffixColumnTitle": "Follower 索引后缀", - "xpack.crossClusterReplication.checkLicense.errorExpiredMessage": "您不能使用 {pluginName},因为您的 {licenseType} 许可证已过期", - "xpack.crossClusterReplication.checkLicense.errorUnavailableMessage": "您不能使用 {pluginName},因为许可证信息当前不可用。", - "xpack.crossClusterReplication.checkLicense.errorUnsupportedMessage": "您的 {licenseType} 许可证不支持 {pluginName}。请升级您的许可。", "xpack.crossClusterReplication.deleteAutoFollowPattern.confirmModal.cancelButtonText": "取消", "xpack.crossClusterReplication.deleteAutoFollowPattern.confirmModal.confirmButtonText": "删除", "xpack.crossClusterReplication.deleteAutoFollowPattern.confirmModal.deleteMultipleTitle": "是否删除 {count} 个自动跟随模式?", @@ -8307,7 +8293,6 @@ "xpack.ingestManager.agentDetails.statusLabel": "状态", "xpack.ingestManager.agentDetails.typeLabel": "类型", "xpack.ingestManager.agentDetails.unavailableConfigTooltipText": "此配置不再可用", - "xpack.ingestManager.agentDetails.unenrollButtonText": "取消注册", "xpack.ingestManager.agentDetails.unexceptedErrorTitle": "加载代理时发生错误", "xpack.ingestManager.agentDetails.userProvidedMetadataSectionSubtitle": "用户提供的元数据", "xpack.ingestManager.agentEnrollment.apiKeySelectionDescription": "快速选择所需的代理配置和平台。然后,根据下面的说明设置和注册代理。", @@ -8339,8 +8324,6 @@ "xpack.ingestManager.agentList.actionsColumnTitle": "操作", "xpack.ingestManager.agentList.actionsMenuText": "打开", "xpack.ingestManager.agentList.addButton": "安装新代理", - "xpack.ingestManager.agentList.agentsOnPageSelectedMessage": "已选择此页面上的 {count, plural, one {# 个代理} other {# 个代理}}。{selectAllLink}", - "xpack.ingestManager.agentList.allAgentsSelectedMessage": "已选择所有 {count} 个代理。{clearSelectionLink}", "xpack.ingestManager.agentList.clearFiltersLinkText": "清除筛选", "xpack.ingestManager.agentList.configColumnTitle": "配置", "xpack.ingestManager.agentList.configFilterText": "配置", @@ -8352,15 +8335,12 @@ "xpack.ingestManager.agentList.noFilteredAgentsPrompt": "未找到任何代理。{clearFiltersLink}", "xpack.ingestManager.agentList.outOfDateLabel": "过时", "xpack.ingestManager.agentList.revisionNumber": "修订 {revNumber}", - "xpack.ingestManager.agentList.selectAllAgentsLinkText": "选择所有 {count} 个代理", - "xpack.ingestManager.agentList.selectPageAgentsLinkText": "仅选择此页面", "xpack.ingestManager.agentList.showInactiveSwitchLabel": "显示非活动代理", "xpack.ingestManager.agentList.statusColumnTitle": "状态", "xpack.ingestManager.agentList.statusErrorFilterText": "错误", "xpack.ingestManager.agentList.statusFilterText": "状态", "xpack.ingestManager.agentList.statusOfflineFilterText": "脱机", "xpack.ingestManager.agentList.statusOnlineFilterText": "联机", - "xpack.ingestManager.agentList.unenrollButton": "取消注册 {count, plural, one {# 个代理} other {# 个代理}}", "xpack.ingestManager.agentList.unenrollOneButton": "取消注册", "xpack.ingestManager.agentList.versionTitle": "版本", "xpack.ingestManager.agentList.viewActionText": "查看代理", @@ -8508,10 +8488,7 @@ "xpack.ingestManager.unenrollAgents.confirmModal.deleteMultipleTitle": "取消注册 {count, plural, one {# 个代理} other {# 个代理}}?", "xpack.ingestManager.unenrollAgents.confirmModal.deleteSingleTitle": "取消注册“{id}”?", "xpack.ingestManager.unenrollAgents.confirmModal.loadingButtonLabel": "正在加载……", - "xpack.ingestManager.unenrollAgents.failureMultipleNotificationTitle": "取消注册 {count} 个代理时出错", - "xpack.ingestManager.unenrollAgents.failureSingleNotificationTitle": "取消注册代理“{id}”时出错", "xpack.ingestManager.unenrollAgents.fatalErrorNotificationTitle": "取消注册代理时出错", - "xpack.ingestManager.unenrollAgents.successMultipleNotificationTitle": "已取消注册 {count} 个代理", "xpack.ingestManager.unenrollAgents.successSingleNotificationTitle": "已取消注册代理“{id}”", "xpack.ingestManager.yamlConfig.instructionDescription": "要将代理注册到此配置,请在您的主机上复制并运行以下命令。", "xpack.ingestManager.yamlConfig.instructionTittle": "注册到 fleet", @@ -10926,7 +10903,6 @@ "xpack.monitoring.alerts.licenseExpiration.actionGroups.default": "默认值", "xpack.monitoring.alerts.licenseExpiration.newSubject": "新 X-Pack Monitoring:许可证到期", "xpack.monitoring.alerts.licenseExpiration.resolvedSubject": "已解决 X-Pack Monitoring:许可证到期", - "xpack.monitoring.alerts.licenseExpiration.ui.firingMessage": "此集群的许可证将在 #relative 后,即 #absolute到期", "xpack.monitoring.alerts.licenseExpiration.ui.resolvedMessage": "此集群的许可证处于活动状态。", "xpack.monitoring.alerts.lowSeverityName": "低", "xpack.monitoring.alerts.mediumSeverityName": "中", @@ -16743,4 +16719,4 @@ "xpack.watcher.watchEdit.thresholdWatchExpression.aggType.fieldIsRequiredValidationMessage": "此字段必填。", "xpack.watcher.watcherDescription": "通过创建、管理和监测警报来检测数据中的更改。" } -} \ No newline at end of file +} diff --git a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/of.test.tsx b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/of.test.tsx index 2f3c0000ef96b..b01104a8d5cf7 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/of.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/of.test.tsx @@ -45,6 +45,7 @@ describe('of expression', () => { "asPlainText": true, } } + sortMatchesBy="none" /> `); }); @@ -105,6 +106,7 @@ describe('of expression', () => { "asPlainText": true, } } + sortMatchesBy="none" /> `); }); diff --git a/x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.test.ts b/x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.test.ts index 2af1a9ac38e44..24347b7799871 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.test.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.test.ts @@ -51,6 +51,7 @@ describe('Upgrade Assistant Usage Collector', () => { 'ui_reindex.open': 4, 'ui_reindex.start': 2, 'ui_reindex.stop': 1, + 'ui_reindex.not_defined': 1, }, }; }, diff --git a/x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.ts b/x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.ts index 9c2946db7f084..0c2e3a1e43f4a 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { set } from 'lodash'; +import { get } from 'lodash'; import { APICaller, ElasticsearchServiceStart, @@ -84,16 +84,19 @@ export async function fetchUpgradeAssistantMetrics( return defaultTelemetrySavedObject; } - const upgradeAssistantTelemetrySOAttrsKeys = Object.keys( - upgradeAssistantTelemetrySavedObjectAttrs - ); - const telemetryObj = defaultTelemetrySavedObject; - - upgradeAssistantTelemetrySOAttrsKeys.forEach((key: string) => { - set(telemetryObj, key, upgradeAssistantTelemetrySavedObjectAttrs[key]); - }); - - return telemetryObj as UpgradeAssistantTelemetrySavedObject; + return { + ui_open: { + overview: get(upgradeAssistantTelemetrySavedObjectAttrs, 'ui_open.overview', 0), + cluster: get(upgradeAssistantTelemetrySavedObjectAttrs, 'ui_open.cluster', 0), + indices: get(upgradeAssistantTelemetrySavedObjectAttrs, 'ui_open.indices', 0), + }, + ui_reindex: { + close: get(upgradeAssistantTelemetrySavedObjectAttrs, 'ui_reindex.close', 0), + open: get(upgradeAssistantTelemetrySavedObjectAttrs, 'ui_reindex.open', 0), + start: get(upgradeAssistantTelemetrySavedObjectAttrs, 'ui_reindex.start', 0), + stop: get(upgradeAssistantTelemetrySavedObjectAttrs, 'ui_reindex.stop', 0), + }, + } as UpgradeAssistantTelemetrySavedObject; }; return { diff --git a/x-pack/test/api_integration/apis/fleet/unenroll_agent.ts b/x-pack/test/api_integration/apis/fleet/unenroll_agent.ts index b484f1f5a8ed2..2acfca63995f1 100644 --- a/x-pack/test/api_integration/apis/fleet/unenroll_agent.ts +++ b/x-pack/test/api_integration/apis/fleet/unenroll_agent.ts @@ -61,50 +61,29 @@ export default function(providerContext: FtrProviderContext) { await esArchiver.unload('fleet/agents'); }); - it('should not allow both ids and kuery in the payload', async () => { - await supertest - .post(`/api/ingest_manager/fleet/agents/unenroll`) - .set('kbn-xsrf', 'xxx') - .send({ - ids: ['agent:1'], - kuery: ['agents.id:1'], - }) - .expect(400); - }); - - it('should not allow no ids or kuery in the payload', async () => { - await supertest - .post(`/api/ingest_manager/fleet/agents/unenroll`) - .set('kbn-xsrf', 'xxx') - .send({}) - .expect(400); - }); - it('allow to unenroll using a list of ids', async () => { const { body } = await supertest - .post(`/api/ingest_manager/fleet/agents/unenroll`) + .post(`/api/ingest_manager/fleet/agents/agent1/unenroll`) .set('kbn-xsrf', 'xxx') .send({ ids: ['agent1'], }) .expect(200); - expect(body).to.have.keys('results', 'success'); + expect(body).to.have.keys('success'); expect(body.success).to.be(true); - expect(body.results).to.have.length(1); - expect(body.results[0].success).to.be(true); }); it('should invalidate related API keys', async () => { const { body } = await supertest - .post(`/api/ingest_manager/fleet/agents/unenroll`) + .post(`/api/ingest_manager/fleet/agents/agent1/unenroll`) .set('kbn-xsrf', 'xxx') .send({ ids: ['agent1'], }) .expect(200); - expect(body).to.have.keys('results', 'success'); + expect(body).to.have.keys('success'); expect(body.success).to.be(true); const { @@ -119,25 +98,5 @@ export default function(providerContext: FtrProviderContext) { expect(outputAPIKeys).length(1); expect(outputAPIKeys[0].invalidated).eql(true); }); - - it('allow to unenroll using a kibana query', async () => { - const { body } = await supertest - .post(`/api/ingest_manager/fleet/agents/unenroll`) - .set('kbn-xsrf', 'xxx') - .send({ - kuery: 'agents.shared_id:agent2_filebeat OR agents.shared_id:agent3_metricbeat', - }) - .expect(200); - - expect(body).to.have.keys('results', 'success'); - expect(body.success).to.be(true); - expect(body.results).to.have.length(2); - expect(body.results[0].success).to.be(true); - - const agentsUnenrolledIds = body.results.map((r: { id: string }) => r.id); - - expect(agentsUnenrolledIds).to.contain('agent2'); - expect(agentsUnenrolledIds).to.contain('agent3'); - }); }); } diff --git a/x-pack/test/api_integration/apis/management/cross_cluster_replication/auto_follow_pattern.helpers.js b/x-pack/test/api_integration/apis/management/cross_cluster_replication/auto_follow_pattern.helpers.js index 22f0bde50b073..b9a0bfd40a8d6 100644 --- a/x-pack/test/api_integration/apis/management/cross_cluster_replication/auto_follow_pattern.helpers.js +++ b/x-pack/test/api_integration/apis/management/cross_cluster_replication/auto_follow_pattern.helpers.js @@ -5,33 +5,27 @@ */ import { API_BASE_PATH } from './constants'; -import { getRandomString } from './lib'; -import { getAutoFollowIndexPayload } from './fixtures'; export const registerHelpers = supertest => { let autoFollowPatternsCreated = []; const loadAutoFollowPatterns = () => supertest.get(`${API_BASE_PATH}/auto_follow_patterns`); - const getAutoFollowPattern = name => - supertest.get(`${API_BASE_PATH}/auto_follow_patterns/${name}`); + const getAutoFollowPattern = id => supertest.get(`${API_BASE_PATH}/auto_follow_patterns/${id}`); - const createAutoFollowPattern = ( - name = getRandomString(), - payload = getAutoFollowIndexPayload() - ) => { - autoFollowPatternsCreated.push(name); + const createAutoFollowPattern = payload => { + autoFollowPatternsCreated.push(payload.id); return supertest .post(`${API_BASE_PATH}/auto_follow_patterns`) .set('kbn-xsrf', 'xxx') - .send({ ...payload, id: name }); + .send(payload); }; - const deleteAutoFollowPattern = name => { - autoFollowPatternsCreated = autoFollowPatternsCreated.filter(c => c !== name); + const deleteAutoFollowPattern = id => { + autoFollowPatternsCreated = autoFollowPatternsCreated.filter(c => c !== id); - return supertest.delete(`${API_BASE_PATH}/auto_follow_patterns/${name}`).set('kbn-xsrf', 'xxx'); + return supertest.delete(`${API_BASE_PATH}/auto_follow_patterns/${id}`).set('kbn-xsrf', 'xxx'); }; const deleteAllAutoFollowPatterns = () => diff --git a/x-pack/test/api_integration/apis/management/cross_cluster_replication/auto_follow_pattern.js b/x-pack/test/api_integration/apis/management/cross_cluster_replication/auto_follow_pattern.js index 3efb4d6600f7f..7a95ba7fcd981 100644 --- a/x-pack/test/api_integration/apis/management/cross_cluster_replication/auto_follow_pattern.js +++ b/x-pack/test/api_integration/apis/management/cross_cluster_replication/auto_follow_pattern.js @@ -6,8 +6,7 @@ import expect from '@kbn/expect'; -import { getRandomString } from './lib'; -import { getAutoFollowIndexPayload } from './fixtures'; +import { REMOTE_CLUSTER_NAME } from './constants'; import { registerHelpers as registerRemoteClustersHelpers } from './remote_clusters.helpers'; import { registerHelpers as registerAutoFollowPatternHelpers } from './auto_follow_pattern.helpers'; @@ -37,44 +36,60 @@ export default function({ getService }) { describe('when remote cluster does not exist', () => { it('should throw a 404 error when cluster is unknown', async () => { - const payload = getAutoFollowIndexPayload(); - payload.remoteCluster = 'unknown-cluster'; + const { body } = await createAutoFollowPattern({ + id: 'pattern0', + remoteCluster: 'unknown-cluster', + leaderIndexPatterns: ['leader-*'], + followIndexPattern: '{{leader_index}}_follower', + }); - const { body } = await createAutoFollowPattern(undefined, payload).expect(404); + expect(body.statusCode).to.be(404); expect(body.attributes.cause[0]).to.contain('no such remote cluster'); }); }); describe('when remote cluster exists', () => { - before(() => addCluster()); + before(async () => addCluster()); describe('create()', () => { it('should create an auto-follow pattern when cluster is known', async () => { - const name = getRandomString(); - const { body } = await createAutoFollowPattern(name).expect(200); - console.log(body); - + const { body, statusCode } = await createAutoFollowPattern({ + id: 'pattern1', + remoteCluster: REMOTE_CLUSTER_NAME, + leaderIndexPatterns: ['leader-*'], + followIndexPattern: '{{leader_index}}_follower', + }); + + expect(statusCode).to.be(200); expect(body.acknowledged).to.eql(true); }); }); describe('get()', () => { it('should return a 404 when the auto-follow pattern is not found', async () => { - const name = getRandomString(); - const { body } = await getAutoFollowPattern(name).expect(404); - + const { body } = await getAutoFollowPattern('missing-pattern'); + expect(body.statusCode).to.be(404); expect(body.attributes.cause).not.to.be(undefined); }); it('should return an auto-follow pattern that was created', async () => { - const name = getRandomString(); - const autoFollowPattern = getAutoFollowIndexPayload(); - - await createAutoFollowPattern(name, autoFollowPattern); - - const { body } = await getAutoFollowPattern(name).expect(200); - - expect(body).to.eql({ ...autoFollowPattern, name }); + await createAutoFollowPattern({ + id: 'pattern2', + remoteCluster: REMOTE_CLUSTER_NAME, + leaderIndexPatterns: ['leader-*'], + followIndexPattern: '{{leader_index}}_follower', + }); + + const { body, statusCode } = await getAutoFollowPattern('pattern2'); + + expect(statusCode).to.be(200); + expect(body).to.eql({ + name: 'pattern2', + remoteCluster: REMOTE_CLUSTER_NAME, + active: true, + leaderIndexPatterns: ['leader-*'], + followIndexPattern: '{{leader_index}}_follower', + }); }); }); }); diff --git a/x-pack/test/api_integration/apis/management/cross_cluster_replication/fixtures.js b/x-pack/test/api_integration/apis/management/cross_cluster_replication/fixtures.js index de47f5d9ea85e..6e254b27356f2 100644 --- a/x-pack/test/api_integration/apis/management/cross_cluster_replication/fixtures.js +++ b/x-pack/test/api_integration/apis/management/cross_cluster_replication/fixtures.js @@ -7,13 +7,6 @@ import { REMOTE_CLUSTER_NAME } from './constants'; import { getRandomString } from './lib'; -export const getAutoFollowIndexPayload = (remoteCluster = REMOTE_CLUSTER_NAME, active = true) => ({ - active, - remoteCluster, - leaderIndexPatterns: ['leader-*'], - followIndexPattern: '{{leader_index}}_follower', -}); - export const getFollowerIndexPayload = ( leaderIndexName = getRandomString(), remoteCluster = REMOTE_CLUSTER_NAME, diff --git a/x-pack/test/api_integration/apis/management/cross_cluster_replication/follower_indices.js b/x-pack/test/api_integration/apis/management/cross_cluster_replication/follower_indices.js index eabf474120f2b..d03b1f83fb404 100644 --- a/x-pack/test/api_integration/apis/management/cross_cluster_replication/follower_indices.js +++ b/x-pack/test/api_integration/apis/management/cross_cluster_replication/follower_indices.js @@ -6,7 +6,7 @@ import expect from '@kbn/expect'; -import { FOLLOWER_INDEX_ADVANCED_SETTINGS } from '../../../../../legacy/plugins/cross_cluster_replication/common/constants'; +import { FOLLOWER_INDEX_ADVANCED_SETTINGS } from '../../../../../plugins/cross_cluster_replication/common/constants'; import { getFollowerIndexPayload } from './fixtures'; import { registerHelpers as registerElasticSearchHelpers, getRandomString } from './lib'; import { registerHelpers as registerRemoteClustersHelpers } from './remote_clusters.helpers'; @@ -57,7 +57,8 @@ export default function({ getService }) { expect(body.attributes.cause[0]).to.contain('no such index'); }); - it('should create a follower index that follows an existing remote index', async () => { + // NOTE: If this test fails locally it's probably because you have another cluster running. + it('should create a follower index that follows an existing leader index', async () => { // First let's create an index to follow const leaderIndex = await createIndex(); @@ -65,7 +66,7 @@ export default function({ getService }) { const { body } = await createFollowerIndex(undefined, payload).expect(200); // There is a race condition in which Elasticsearch can respond without acknowledging, - // i.e. `body .follow_index_shards_acked` is sometimes true and sometimes false. + // i.e. `body.follow_index_shards_acked` is sometimes true and sometimes false. // By only asserting that `follow_index_created` is true, we eliminate this flakiness. expect(body.follow_index_created).to.eql(true); }); @@ -79,6 +80,7 @@ export default function({ getService }) { expect(body.attributes.cause[0]).to.contain('no such index'); }); + // NOTE: If this test fails locally it's probably because you have another cluster running. it('should return a follower index that was created', async () => { const leaderIndex = await createIndex(); diff --git a/x-pack/test/api_integration/apis/management/index_management/indices.js b/x-pack/test/api_integration/apis/management/index_management/indices.js index 7195b8680a286..d2d07eca475e7 100644 --- a/x-pack/test/api_integration/apis/management/index_management/indices.js +++ b/x-pack/test/api_integration/apis/management/index_management/indices.js @@ -193,10 +193,10 @@ export default function({ getService }) { 'size', 'isFrozen', 'aliases', - 'ilm', // data enricher - 'isRollupIndex', // data enricher // Cloud disables CCR, so wouldn't expect follower indices. 'isFollowerIndex', // data enricher + 'ilm', // data enricher + 'isRollupIndex', // data enricher ]; expect(Object.keys(body[0])).to.eql(expectedKeys); }); @@ -219,10 +219,10 @@ export default function({ getService }) { 'size', 'isFrozen', 'aliases', - 'ilm', // data enricher - 'isRollupIndex', // data enricher // Cloud disables CCR, so wouldn't expect follower indices. 'isFollowerIndex', // data enricher + 'ilm', // data enricher + 'isRollupIndex', // data enricher ]; expect(Object.keys(body[0])).to.eql(expectedKeys); expect(body.length > 1).to.be(true); // to contrast it with the next test diff --git a/x-pack/test/functional/apps/canvas/custom_elements.ts b/x-pack/test/functional/apps/canvas/custom_elements.ts index de3976509be1f..d4e1702368879 100644 --- a/x-pack/test/functional/apps/canvas/custom_elements.ts +++ b/x-pack/test/functional/apps/canvas/custom_elements.ts @@ -19,8 +19,7 @@ export default function canvasCustomElementTest({ const PageObjects = getPageObjects(['canvas', 'common']); const find = getService('find'); - // FLAKY: https://github.com/elastic/kibana/issues/62927 - describe.skip('custom elements', function() { + describe('custom elements', function() { this.tags('skipFirefox'); before(async () => { @@ -42,7 +41,7 @@ export default function canvasCustomElementTest({ await testSubjects.click('canvasWorkpadPage > canvasWorkpadPageElementContent', 20000); // click the "Save as new element" button - await find.clickByCssSelector('[aria-label="Save as new element"]', 20000); + await testSubjects.click('canvasSidebarHeader__saveElementButton', 20000); // fill out the custom element form and submit it await PageObjects.canvas.fillOutCustomElementForm( @@ -57,15 +56,11 @@ export default function canvasCustomElementTest({ }); it('adds the custom element to the workpad when prompted', async () => { - await PageObjects.canvas.openAddElementModal(); - - // open the custom elements tab - await find.clickByCssSelector('#customElements', 20000); + // open the saved elements modal + await PageObjects.canvas.openSavedElementsModal(); // ensure the custom element is the one expected and click it to add to the workpad - const customElement = await find.byCssSelector( - '[aria-labelledby="customElements"] .canvasElementCard__wrapper' - ); + const customElement = await find.byCssSelector('.canvasElementCard__wrapper'); const elementName = await customElement.findByCssSelector('.euiCard__title'); expect(await elementName.getVisibleText()).to.contain('My New Element'); customElement.click(); @@ -95,14 +90,11 @@ export default function canvasCustomElementTest({ }); it('saves custom element modifications', async () => { - await PageObjects.canvas.openAddElementModal(); - - // open the custom elements tab - await find.clickByCssSelector('#customElements', 20000); + // open the saved elements modal + await PageObjects.canvas.openSavedElementsModal(); // ensure the correct amount of custom elements exist - const container = await find.byCssSelector('[aria-labelledby="customElements"]'); - const customElements = await container.findAllByCssSelector('.canvasElementCard__wrapper'); + const customElements = await find.allByCssSelector('.canvasElementCard__wrapper'); expect(customElements).to.have.length(1); // hover over the custom element to bring up the edit and delete icons @@ -110,8 +102,7 @@ export default function canvasCustomElementTest({ await customElement.moveMouseTo(); // click the edit element button - const editBtn = await customElement.findByCssSelector('[aria-label="Edit element"]'); - await editBtn.click(); + await testSubjects.click('canvasElementCard__editButton', 20000); // fill out the custom element form and submit it await PageObjects.canvas.fillOutCustomElementForm( @@ -121,22 +112,21 @@ export default function canvasCustomElementTest({ // ensure the custom element in the modal shows the updated text await retry.try(async () => { - const elementName = await find.byCssSelector( - '[aria-labelledby="customElements"] .canvasElementCard__wrapper .euiCard__title' - ); + const elementName = await find.byCssSelector('.canvasElementCard__wrapper .euiCard__title'); expect(await elementName.getVisibleText()).to.contain('My Edited New Element'); }); + + // Close the modal + await PageObjects.canvas.closeSavedElementsModal(); }); it('deletes custom element when prompted', async () => { - // open the custom elements tab - await find.clickByCssSelector('#customElements', 20000); + // open the saved elements modal + await PageObjects.canvas.openSavedElementsModal(); // ensure the correct amount of custom elements exist - const customElements = await find.allByCssSelector( - '[aria-labelledby="customElements"] .canvasElementCard__wrapper' - ); + const customElements = await find.allByCssSelector('.canvasElementCard__wrapper'); expect(customElements).to.have.length(1); // hover over the custom element to bring up the edit and delete icons @@ -144,22 +134,18 @@ export default function canvasCustomElementTest({ await customElement.moveMouseTo(); // click the delete element button - const editBtn = await customElement.findByCssSelector('[aria-label="Delete element"]'); - await editBtn.click(); + await testSubjects.click('canvasElementCard__deleteButton', 20000); - await testSubjects.click('confirmModalConfirmButton'); + await testSubjects.click('confirmModalConfirmButton', 20000); // ensure the custom element was deleted await retry.try(async () => { - const containerAgain = await find.byCssSelector('[aria-labelledby="customElements"]'); - const customElementsAgain = await containerAgain.findAllByCssSelector( - '.canvasElementCard__wrapper' - ); + const customElementsAgain = await find.allByCssSelector('.canvasElementCard__wrapper'); expect(customElementsAgain).to.have.length(0); }); // Close the modal - await browser.pressKeys(browser.keys.ESCAPE); + await PageObjects.canvas.closeSavedElementsModal(); }); }); } diff --git a/x-pack/test/functional/apps/discover/feature_controls/discover_security.ts b/x-pack/test/functional/apps/discover/feature_controls/discover_security.ts index dc8c488460100..76ca613af4b55 100644 --- a/x-pack/test/functional/apps/discover/feature_controls/discover_security.ts +++ b/x-pack/test/functional/apps/discover/feature_controls/discover_security.ts @@ -28,8 +28,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.timePicker.setDefaultAbsoluteRange(); } - // FLAKY: https://github.com/elastic/kibana/issues/60535 - describe.skip('security', () => { + describe('security', () => { before(async () => { await esArchiver.load('discover/feature_controls/security'); await esArchiver.loadIfNeeded('logstash_functional'); diff --git a/x-pack/test/functional/apps/discover/feature_controls/discover_spaces.ts b/x-pack/test/functional/apps/discover/feature_controls/discover_spaces.ts index f33b8b4899d16..4bedc757f0b57 100644 --- a/x-pack/test/functional/apps/discover/feature_controls/discover_spaces.ts +++ b/x-pack/test/functional/apps/discover/feature_controls/discover_spaces.ts @@ -24,8 +24,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.timePicker.setDefaultAbsoluteRange(); } - // FLAKY: https://github.com/elastic/kibana/issues/60559 - describe.skip('spaces', () => { + describe('spaces', () => { before(async () => { await esArchiver.loadIfNeeded('logstash_functional'); }); diff --git a/x-pack/test/functional/page_objects/canvas_page.ts b/x-pack/test/functional/page_objects/canvas_page.ts index de826097a5be6..94ad393ead3a3 100644 --- a/x-pack/test/functional/page_objects/canvas_page.ts +++ b/x-pack/test/functional/page_objects/canvas_page.ts @@ -51,8 +51,12 @@ export function CanvasPageProvider({ getService }: FtrProviderContext) { expect(disabledAttr).to.be('true'); }, - async openAddElementModal() { + async openSavedElementsModal() { await testSubjects.click('add-element-button'); + await testSubjects.click('saved-elements-menu-option'); + }, + async closeSavedElementsModal() { + await testSubjects.click('saved-elements-modal-close-button'); }, async expectAddElementButton() { diff --git a/x-pack/test/functional/page_objects/security_page.js b/x-pack/test/functional/page_objects/security_page.js index b399327012a77..84eb0cc378771 100644 --- a/x-pack/test/functional/page_objects/security_page.js +++ b/x-pack/test/functional/page_objects/security_page.js @@ -385,7 +385,7 @@ export function SecurityPageProvider({ getService, getPageObjects }) { // have to remove the '*' return find .clickByCssSelector( - 'div[data-test-subj="fieldInput0"] .euiBadge[title="*"] svg.euiIcon' + 'div[data-test-subj="fieldInput0"] [title="Remove * from selection in this group"] svg.euiIcon' ) .then(function() { return addGrantedField(userObj.elasticsearch.indices[0].field_security.grant); diff --git a/x-pack/test/siem_cypress/es_archives/timeline/data.json.gz b/x-pack/test/siem_cypress/es_archives/timeline/data.json.gz new file mode 100644 index 0000000000000..c7acb36992af3 Binary files /dev/null and b/x-pack/test/siem_cypress/es_archives/timeline/data.json.gz differ diff --git a/x-pack/test/siem_cypress/es_archives/timeline/mappings.json b/x-pack/test/siem_cypress/es_archives/timeline/mappings.json new file mode 100644 index 0000000000000..d3412f9d43b57 --- /dev/null +++ b/x-pack/test/siem_cypress/es_archives/timeline/mappings.json @@ -0,0 +1,2976 @@ +{ + "type": "index", + "value": { + "aliases": { + ".kibana": { + } + }, + "index": ".kibana_1", + "mappings": { + "_meta": { + "migrationMappingPropertyHashes": { + "action": "6e96ac5e648f57523879661ea72525b7", + "action_task_params": "a9d49f184ee89641044be0ca2950fa3a", + "agent_actions": "ed270b46812f0fa1439366c428a2cf17", + "agent_configs": "38abaf89513877745c359e7700c0c66a", + "agent_events": "3231653fafe4ef3196fe3b32ab774bf2", + "agents": "c3eeb7b9d97176f15f6d126370ab23c7", + "alert": "7b44fba6773e37c806ce290ea9b7024e", + "apm-indices": "9bb9b2bf1fa636ed8619cbab5ce6a1dd", + "apm-telemetry": "3525d7c22c42bc80f5e6e9cb3f2b26a2", + "application_usage_totals": "c897e4310c5f24b07caaff3db53ae2c1", + "application_usage_transactional": "965839e75f809fefe04f92dc4d99722a", + "canvas-element": "7390014e1091044523666d97247392fc", + "canvas-workpad": "b0a1706d356228dbdcb4a17e6b9eb231", + "cases": "08b8b110dbca273d37e8aef131ecab61", + "cases-comments": "c2061fb929f585df57425102fa928b4b", + "cases-configure": "42711cbb311976c0687853f4c1354572", + "cases-user-actions": "32277330ec6b721abe3b846cfd939a71", + "config": "ae24d22d5986d04124cc6568f771066f", + "dashboard": "d00f614b29a80360e1190193fd333bab", + "datasources": "d4bc0c252b2b5683ff21ea32d00acffc", + "enrollment_api_keys": "28b91e20b105b6f928e2012600085d8f", + "epm-package": "0be91c6758421dd5d0f1a58e9e5bc7c3", + "file-upload-telemetry": "0ed4d3e1983d1217a30982630897092e", + "graph-workspace": "cd7ba1330e6682e9cc00b78850874be1", + "index-pattern": "66eccb05066c5a89924f48a9e9736499", + "infrastructure-ui-source": "ddc0ecb18383f6b26101a2fadb2dab0c", + "inventory-view": "9ecce5b58867403613d82fe496470b34", + "kql-telemetry": "d12a98a6f19a2d273696597547e064ee", + "lens": "21c3ea0763beb1ecb0162529706b88c5", + "lens-ui-telemetry": "509bfa5978586998e05f9e303c07a327", + "map": "23d7aa4a720d4938ccde3983f87bd58d", + "maps-telemetry": "268da3a48066123fc5baf35abaa55014", + "metrics-explorer-view": "53c5365793677328df0ccb6138bf3cdd", + "migrationVersion": "4a1746014a75ade3a714e1db5763276f", + "ml-telemetry": "257fd1d4b4fdbb9cb4b8a3b27da201e9", + "namespace": "2f4316de49999235636386fe51dc06c1", + "outputs": "aee9782e0d500b867859650a36280165", + "query": "11aaeb7f5f7fa5bb43f25e18ce26e7d9", + "references": "7997cf5a56cc02bdc9c93361bde732b0", + "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4", + "search": "181661168bbadd1eff5902361e2a0d5c", + "server": "ec97f1c5da1a19609a60874e5af1100c", + "siem-detection-engine-rule-actions": "90eee2e4635260f4be0a1da8f5bc0aa0", + "siem-detection-engine-rule-status": "ae783f41c6937db6b7a2ef5c93a9e9b0", + "siem-ui-timeline": "ac8020190f5950dd3250b6499144e7fb", + "siem-ui-timeline-note": "8874706eedc49059d4cf0f5094559084", + "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", + "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", + "telemetry": "36a616f7026dfa617d6655df850fe16d", + "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", + "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", + "type": "2f4316de49999235636386fe51dc06c1", + "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", + "updated_at": "00da57df13e94e9d98437d13ace4bfe0", + "upgrade-assistant-reindex-operation": "a53a20fe086b72c9a86da3cc12dad8a6", + "upgrade-assistant-telemetry": "56702cec857e0a9dacfb696655b4ff7b", + "uptime-dynamic-settings": "b6289473c8985c79b6c47eebc19a0ca5", + "url": "c7f66a0df8b1b52f17c28c4adb111105", + "visualization": "52d7a13ad68a150c4525b292d23e12cc" + } + }, + "dynamic": "strict", + "properties": { + "action": { + "properties": { + "actionTypeId": { + "type": "keyword" + }, + "config": { + "enabled": false, + "type": "object" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "secrets": { + "type": "binary" + } + } + }, + "action_task_params": { + "properties": { + "actionId": { + "type": "keyword" + }, + "apiKey": { + "type": "binary" + }, + "params": { + "enabled": false, + "type": "object" + } + } + }, + "agent_actions": { + "properties": { + "agent_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "data": { + "type": "flattened" + }, + "sent_at": { + "type": "date" + }, + "type": { + "type": "keyword" + } + } + }, + "agent_configs": { + "properties": { + "datasources": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "id": { + "type": "keyword" + }, + "is_default": { + "type": "boolean" + }, + "name": { + "type": "text" + }, + "namespace": { + "type": "keyword" + }, + "revision": { + "type": "integer" + }, + "status": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + }, + "updated_on": { + "type": "keyword" + } + } + }, + "agent_events": { + "properties": { + "action_id": { + "type": "keyword" + }, + "agent_id": { + "type": "keyword" + }, + "config_id": { + "type": "keyword" + }, + "data": { + "type": "text" + }, + "message": { + "type": "text" + }, + "payload": { + "type": "text" + }, + "stream_id": { + "type": "keyword" + }, + "subtype": { + "type": "keyword" + }, + "timestamp": { + "type": "date" + }, + "type": { + "type": "keyword" + } + } + }, + "agents": { + "properties": { + "access_api_key_id": { + "type": "keyword" + }, + "active": { + "type": "boolean" + }, + "config_id": { + "type": "keyword" + }, + "config_newest_revision": { + "type": "integer" + }, + "config_revision": { + "type": "integer" + }, + "current_error_events": { + "type": "text" + }, + "default_api_key": { + "type": "keyword" + }, + "enrolled_at": { + "type": "date" + }, + "last_checkin": { + "type": "date" + }, + "last_updated": { + "type": "date" + }, + "local_metadata": { + "type": "text" + }, + "shared_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "user_provided_metadata": { + "type": "text" + }, + "version": { + "type": "keyword" + } + } + }, + "alert": { + "properties": { + "actions": { + "properties": { + "actionRef": { + "type": "keyword" + }, + "actionTypeId": { + "type": "keyword" + }, + "group": { + "type": "keyword" + }, + "params": { + "enabled": false, + "type": "object" + } + }, + "type": "nested" + }, + "alertTypeId": { + "type": "keyword" + }, + "apiKey": { + "type": "binary" + }, + "apiKeyOwner": { + "type": "keyword" + }, + "consumer": { + "type": "keyword" + }, + "createdAt": { + "type": "date" + }, + "createdBy": { + "type": "keyword" + }, + "enabled": { + "type": "boolean" + }, + "muteAll": { + "type": "boolean" + }, + "mutedInstanceIds": { + "type": "keyword" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "params": { + "enabled": false, + "type": "object" + }, + "schedule": { + "properties": { + "interval": { + "type": "keyword" + } + } + }, + "scheduledTaskId": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "throttle": { + "type": "keyword" + }, + "updatedBy": { + "type": "keyword" + } + } + }, + "apm-indices": { + "properties": { + "apm_oss": { + "properties": { + "errorIndices": { + "type": "keyword" + }, + "metricsIndices": { + "type": "keyword" + }, + "onboardingIndices": { + "type": "keyword" + }, + "sourcemapIndices": { + "type": "keyword" + }, + "spanIndices": { + "type": "keyword" + }, + "transactionIndices": { + "type": "keyword" + } + } + } + } + }, + "apm-telemetry": { + "properties": { + "agents": { + "properties": { + "dotnet": { + "properties": { + "agent": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "service": { + "properties": { + "framework": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "language": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "runtime": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "go": { + "properties": { + "agent": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "service": { + "properties": { + "framework": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "language": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "runtime": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "java": { + "properties": { + "agent": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "service": { + "properties": { + "framework": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "language": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "runtime": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "js-base": { + "properties": { + "agent": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "service": { + "properties": { + "framework": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "language": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "runtime": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "nodejs": { + "properties": { + "agent": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "service": { + "properties": { + "framework": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "language": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "runtime": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "python": { + "properties": { + "agent": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "service": { + "properties": { + "framework": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "language": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "runtime": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "ruby": { + "properties": { + "agent": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "service": { + "properties": { + "framework": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "language": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "runtime": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "rum-js": { + "properties": { + "agent": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "service": { + "properties": { + "framework": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "language": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "runtime": { + "properties": { + "composite": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + } + } + }, + "cardinality": { + "properties": { + "transaction": { + "properties": { + "name": { + "properties": { + "all_agents": { + "properties": { + "1d": { + "type": "long" + } + } + }, + "rum": { + "properties": { + "1d": { + "type": "long" + } + } + } + } + } + } + }, + "user_agent": { + "properties": { + "original": { + "properties": { + "all_agents": { + "properties": { + "1d": { + "type": "long" + } + } + }, + "rum": { + "properties": { + "1d": { + "type": "long" + } + } + } + } + } + } + } + } + }, + "counts": { + "properties": { + "agent_configuration": { + "properties": { + "all": { + "type": "long" + } + } + }, + "error": { + "properties": { + "1d": { + "type": "long" + }, + "all": { + "type": "long" + } + } + }, + "max_error_groups_per_service": { + "properties": { + "1d": { + "type": "long" + } + } + }, + "max_transaction_groups_per_service": { + "properties": { + "1d": { + "type": "long" + } + } + }, + "metric": { + "properties": { + "1d": { + "type": "long" + }, + "all": { + "type": "long" + } + } + }, + "onboarding": { + "properties": { + "1d": { + "type": "long" + }, + "all": { + "type": "long" + } + } + }, + "services": { + "properties": { + "1d": { + "type": "long" + } + } + }, + "sourcemap": { + "properties": { + "1d": { + "type": "long" + }, + "all": { + "type": "long" + } + } + }, + "span": { + "properties": { + "1d": { + "type": "long" + }, + "all": { + "type": "long" + } + } + }, + "traces": { + "properties": { + "1d": { + "type": "long" + } + } + }, + "transaction": { + "properties": { + "1d": { + "type": "long" + }, + "all": { + "type": "long" + } + } + } + } + }, + "has_any_services": { + "type": "boolean" + }, + "indices": { + "properties": { + "all": { + "properties": { + "total": { + "properties": { + "docs": { + "properties": { + "count": { + "type": "long" + } + } + }, + "store": { + "properties": { + "size_in_bytes": { + "type": "long" + } + } + } + } + } + } + }, + "shards": { + "properties": { + "total": { + "type": "long" + } + } + } + } + }, + "integrations": { + "properties": { + "ml": { + "properties": { + "all_jobs_count": { + "type": "long" + } + } + } + } + }, + "retainment": { + "properties": { + "error": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "metric": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "onboarding": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "span": { + "properties": { + "ms": { + "type": "long" + } + } + }, + "transaction": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "services_per_agent": { + "properties": { + "dotnet": { + "null_value": 0, + "type": "long" + }, + "go": { + "null_value": 0, + "type": "long" + }, + "java": { + "null_value": 0, + "type": "long" + }, + "js-base": { + "null_value": 0, + "type": "long" + }, + "nodejs": { + "null_value": 0, + "type": "long" + }, + "python": { + "null_value": 0, + "type": "long" + }, + "ruby": { + "null_value": 0, + "type": "long" + }, + "rum-js": { + "null_value": 0, + "type": "long" + } + } + }, + "tasks": { + "properties": { + "agent_configuration": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "agents": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "cardinality": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "groupings": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "indices_stats": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "integrations": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "processor_events": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "services": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + }, + "versions": { + "properties": { + "took": { + "properties": { + "ms": { + "type": "long" + } + } + } + } + } + } + }, + "version": { + "properties": { + "apm_server": { + "properties": { + "major": { + "type": "long" + }, + "minor": { + "type": "long" + }, + "patch": { + "type": "long" + } + } + } + } + } + } + }, + "application_usage_totals": { + "properties": { + "appId": { + "type": "keyword" + }, + "minutesOnScreen": { + "type": "float" + }, + "numberOfClicks": { + "type": "long" + } + } + }, + "application_usage_transactional": { + "properties": { + "appId": { + "type": "keyword" + }, + "minutesOnScreen": { + "type": "float" + }, + "numberOfClicks": { + "type": "long" + }, + "timestamp": { + "type": "date" + } + } + }, + "canvas-element": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "content": { + "type": "text" + }, + "help": { + "type": "text" + }, + "image": { + "type": "text" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "canvas-workpad": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "cases": { + "properties": { + "closed_at": { + "type": "date" + }, + "closed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "description": { + "type": "text" + }, + "external_service": { + "properties": { + "connector_id": { + "type": "keyword" + }, + "connector_name": { + "type": "keyword" + }, + "external_id": { + "type": "keyword" + }, + "external_title": { + "type": "text" + }, + "external_url": { + "type": "text" + }, + "pushed_at": { + "type": "date" + }, + "pushed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "status": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "title": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-comments": { + "properties": { + "comment": { + "type": "text" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "pushed_at": { + "type": "date" + }, + "pushed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-configure": { + "properties": { + "closure_type": { + "type": "keyword" + }, + "connector_id": { + "type": "keyword" + }, + "connector_name": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-user-actions": { + "properties": { + "action": { + "type": "keyword" + }, + "action_at": { + "type": "date" + }, + "action_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "action_field": { + "type": "keyword" + }, + "new_value": { + "type": "text" + }, + "old_value": { + "type": "text" + } + } + }, + "config": { + "dynamic": "true", + "properties": { + "buildNum": { + "type": "keyword" + }, + "dateFormat:tz": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "dashboard": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "optionsJSON": { + "type": "text" + }, + "panelsJSON": { + "type": "text" + }, + "refreshInterval": { + "properties": { + "display": { + "type": "keyword" + }, + "pause": { + "type": "boolean" + }, + "section": { + "type": "integer" + }, + "value": { + "type": "integer" + } + } + }, + "timeFrom": { + "type": "keyword" + }, + "timeRestore": { + "type": "boolean" + }, + "timeTo": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "datasources": { + "properties": { + "config_id": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "enabled": { + "type": "boolean" + }, + "inputs": { + "properties": { + "config": { + "type": "flattened" + }, + "enabled": { + "type": "boolean" + }, + "processors": { + "type": "keyword" + }, + "streams": { + "properties": { + "config": { + "type": "flattened" + }, + "dataset": { + "type": "keyword" + }, + "enabled": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "processors": { + "type": "keyword" + } + }, + "type": "nested" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "name": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "output_id": { + "type": "keyword" + }, + "package": { + "properties": { + "name": { + "type": "keyword" + }, + "title": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "revision": { + "type": "integer" + } + } + }, + "enrollment_api_keys": { + "properties": { + "active": { + "type": "boolean" + }, + "api_key": { + "type": "binary" + }, + "api_key_id": { + "type": "keyword" + }, + "config_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "expire_at": { + "type": "date" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + } + } + }, + "epm-package": { + "properties": { + "installed": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "internal": { + "type": "boolean" + }, + "name": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "file-upload-telemetry": { + "properties": { + "filesUploadedTotalCount": { + "type": "long" + } + } + }, + "graph-workspace": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "numLinks": { + "type": "integer" + }, + "numVertices": { + "type": "integer" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "wsState": { + "type": "text" + } + } + }, + "index-pattern": { + "properties": { + "fieldFormatMap": { + "type": "text" + }, + "fields": { + "type": "text" + }, + "intervalName": { + "type": "keyword" + }, + "notExpandable": { + "type": "boolean" + }, + "sourceFilters": { + "type": "text" + }, + "timeFieldName": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "type": { + "type": "keyword" + }, + "typeMeta": { + "type": "keyword" + } + } + }, + "infrastructure-ui-source": { + "properties": { + "description": { + "type": "text" + }, + "fields": { + "properties": { + "container": { + "type": "keyword" + }, + "host": { + "type": "keyword" + }, + "pod": { + "type": "keyword" + }, + "tiebreaker": { + "type": "keyword" + }, + "timestamp": { + "type": "keyword" + } + } + }, + "logAlias": { + "type": "keyword" + }, + "logColumns": { + "properties": { + "fieldColumn": { + "properties": { + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + } + } + }, + "messageColumn": { + "properties": { + "id": { + "type": "keyword" + } + } + }, + "timestampColumn": { + "properties": { + "id": { + "type": "keyword" + } + } + } + }, + "type": "nested" + }, + "metricAlias": { + "type": "keyword" + }, + "name": { + "type": "text" + } + } + }, + "inventory-view": { + "properties": { + "autoBounds": { + "type": "boolean" + }, + "autoReload": { + "type": "boolean" + }, + "boundsOverride": { + "properties": { + "max": { + "type": "integer" + }, + "min": { + "type": "integer" + } + } + }, + "customMetrics": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "label": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "customOptions": { + "properties": { + "field": { + "type": "keyword" + }, + "text": { + "type": "keyword" + } + }, + "type": "nested" + }, + "filterQuery": { + "properties": { + "expression": { + "type": "keyword" + }, + "kind": { + "type": "keyword" + } + } + }, + "groupBy": { + "properties": { + "field": { + "type": "keyword" + }, + "label": { + "type": "keyword" + } + }, + "type": "nested" + }, + "metric": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "label": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "name": { + "type": "keyword" + }, + "nodeType": { + "type": "keyword" + }, + "time": { + "type": "integer" + }, + "view": { + "type": "keyword" + } + } + }, + "kql-telemetry": { + "properties": { + "optInCount": { + "type": "long" + }, + "optOutCount": { + "type": "long" + } + } + }, + "lens": { + "properties": { + "expression": { + "index": false, + "type": "keyword" + }, + "state": { + "type": "flattened" + }, + "title": { + "type": "text" + }, + "visualizationType": { + "type": "keyword" + } + } + }, + "lens-ui-telemetry": { + "properties": { + "count": { + "type": "integer" + }, + "date": { + "type": "date" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "map": { + "properties": { + "bounds": { + "type": "geo_shape" + }, + "description": { + "type": "text" + }, + "layerListJSON": { + "type": "text" + }, + "mapStateJSON": { + "type": "text" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "maps-telemetry": { + "properties": { + "attributesPerMap": { + "properties": { + "dataSourcesCount": { + "properties": { + "avg": { + "type": "long" + }, + "max": { + "type": "long" + }, + "min": { + "type": "long" + } + } + }, + "emsVectorLayersCount": { + "dynamic": "true", + "type": "object" + }, + "layerTypesCount": { + "dynamic": "true", + "type": "object" + }, + "layersCount": { + "properties": { + "avg": { + "type": "long" + }, + "max": { + "type": "long" + }, + "min": { + "type": "long" + } + } + } + } + }, + "indexPatternsWithGeoFieldCount": { + "type": "long" + }, + "mapsTotalCount": { + "type": "long" + }, + "settings": { + "properties": { + "showMapVisualizationTypes": { + "type": "boolean" + } + } + }, + "timeCaptured": { + "type": "date" + } + } + }, + "metrics-explorer-view": { + "properties": { + "chartOptions": { + "properties": { + "stack": { + "type": "boolean" + }, + "type": { + "type": "keyword" + }, + "yAxisMode": { + "type": "keyword" + } + } + }, + "currentTimerange": { + "properties": { + "from": { + "type": "keyword" + }, + "interval": { + "type": "keyword" + }, + "to": { + "type": "keyword" + } + } + }, + "name": { + "type": "keyword" + }, + "options": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "filterQuery": { + "type": "keyword" + }, + "groupBy": { + "type": "keyword" + }, + "limit": { + "type": "integer" + }, + "metrics": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "color": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "label": { + "type": "keyword" + } + }, + "type": "nested" + } + } + } + } + }, + "migrationVersion": { + "dynamic": "true", + "properties": { + "space": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "ml-telemetry": { + "properties": { + "file_data_visualizer": { + "properties": { + "index_creation_count": { + "type": "long" + } + } + } + } + }, + "namespace": { + "type": "keyword" + }, + "outputs": { + "properties": { + "api_key": { + "type": "keyword" + }, + "ca_sha256": { + "type": "keyword" + }, + "config": { + "type": "flattened" + }, + "fleet_enroll_password": { + "type": "binary" + }, + "fleet_enroll_username": { + "type": "binary" + }, + "hosts": { + "type": "keyword" + }, + "is_default": { + "type": "boolean" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "query": { + "properties": { + "description": { + "type": "text" + }, + "filters": { + "enabled": false, + "type": "object" + }, + "query": { + "properties": { + "language": { + "type": "keyword" + }, + "query": { + "index": false, + "type": "keyword" + } + } + }, + "timefilter": { + "enabled": false, + "type": "object" + }, + "title": { + "type": "text" + } + } + }, + "references": { + "properties": { + "id": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "sample-data-telemetry": { + "properties": { + "installCount": { + "type": "long" + }, + "unInstallCount": { + "type": "long" + } + } + }, + "search": { + "properties": { + "columns": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "sort": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "server": { + "properties": { + "uuid": { + "type": "keyword" + } + } + }, + "siem-detection-engine-rule-actions": { + "properties": { + "actions": { + "properties": { + "action_type_id": { + "type": "keyword" + }, + "group": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "params": { + "dynamic": "true", + "type": "object" + } + } + }, + "alertThrottle": { + "type": "keyword" + }, + "ruleAlertId": { + "type": "keyword" + }, + "ruleThrottle": { + "type": "keyword" + } + } + }, + "siem-detection-engine-rule-status": { + "properties": { + "alertId": { + "type": "keyword" + }, + "bulkCreateTimeDurations": { + "type": "float" + }, + "gap": { + "type": "text" + }, + "lastFailureAt": { + "type": "date" + }, + "lastFailureMessage": { + "type": "text" + }, + "lastLookBackDate": { + "type": "date" + }, + "lastSuccessAt": { + "type": "date" + }, + "lastSuccessMessage": { + "type": "text" + }, + "searchAfterTimeDurations": { + "type": "float" + }, + "status": { + "type": "keyword" + }, + "statusDate": { + "type": "date" + } + } + }, + "siem-ui-timeline": { + "properties": { + "columns": { + "properties": { + "aggregatable": { + "type": "boolean" + }, + "category": { + "type": "keyword" + }, + "columnHeaderType": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "example": { + "type": "text" + }, + "id": { + "type": "keyword" + }, + "indexes": { + "type": "keyword" + }, + "name": { + "type": "text" + }, + "placeholder": { + "type": "text" + }, + "searchable": { + "type": "boolean" + }, + "type": { + "type": "keyword" + } + } + }, + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "dataProviders": { + "properties": { + "and": { + "properties": { + "enabled": { + "type": "boolean" + }, + "excluded": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "kqlQuery": { + "type": "text" + }, + "name": { + "type": "text" + }, + "queryMatch": { + "properties": { + "displayField": { + "type": "text" + }, + "displayValue": { + "type": "text" + }, + "field": { + "type": "text" + }, + "operator": { + "type": "text" + }, + "value": { + "type": "text" + } + } + } + } + }, + "enabled": { + "type": "boolean" + }, + "excluded": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "kqlQuery": { + "type": "text" + }, + "name": { + "type": "text" + }, + "queryMatch": { + "properties": { + "displayField": { + "type": "text" + }, + "displayValue": { + "type": "text" + }, + "field": { + "type": "text" + }, + "operator": { + "type": "text" + }, + "value": { + "type": "text" + } + } + } + } + }, + "dateRange": { + "properties": { + "end": { + "type": "date" + }, + "start": { + "type": "date" + } + } + }, + "description": { + "type": "text" + }, + "eventType": { + "type": "keyword" + }, + "favorite": { + "properties": { + "favoriteDate": { + "type": "date" + }, + "fullName": { + "type": "text" + }, + "keySearch": { + "type": "text" + }, + "userName": { + "type": "text" + } + } + }, + "filters": { + "properties": { + "exists": { + "type": "text" + }, + "match_all": { + "type": "text" + }, + "meta": { + "properties": { + "alias": { + "type": "text" + }, + "controlledBy": { + "type": "text" + }, + "disabled": { + "type": "boolean" + }, + "field": { + "type": "text" + }, + "formattedValue": { + "type": "text" + }, + "index": { + "type": "keyword" + }, + "key": { + "type": "keyword" + }, + "negate": { + "type": "boolean" + }, + "params": { + "type": "text" + }, + "type": { + "type": "keyword" + }, + "value": { + "type": "text" + } + } + }, + "missing": { + "type": "text" + }, + "query": { + "type": "text" + }, + "range": { + "type": "text" + }, + "script": { + "type": "text" + } + } + }, + "kqlMode": { + "type": "keyword" + }, + "kqlQuery": { + "properties": { + "filterQuery": { + "properties": { + "kuery": { + "properties": { + "expression": { + "type": "text" + }, + "kind": { + "type": "keyword" + } + } + }, + "serializedQuery": { + "type": "text" + } + } + } + } + }, + "savedQueryId": { + "type": "keyword" + }, + "sort": { + "properties": { + "columnId": { + "type": "keyword" + }, + "sortDirection": { + "type": "keyword" + } + } + }, + "title": { + "type": "text" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "siem-ui-timeline-note": { + "properties": { + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "eventId": { + "type": "keyword" + }, + "note": { + "type": "text" + }, + "timelineId": { + "type": "keyword" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "siem-ui-timeline-pinned-event": { + "properties": { + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "eventId": { + "type": "keyword" + }, + "timelineId": { + "type": "keyword" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "space": { + "properties": { + "_reserved": { + "type": "boolean" + }, + "color": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "disabledFeatures": { + "type": "keyword" + }, + "imageUrl": { + "index": false, + "type": "text" + }, + "initials": { + "type": "keyword" + }, + "name": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "spaceId": { + "type": "keyword" + }, + "telemetry": { + "properties": { + "allowChangingOptInStatus": { + "type": "boolean" + }, + "enabled": { + "type": "boolean" + }, + "lastReported": { + "type": "date" + }, + "lastVersionChecked": { + "type": "keyword" + }, + "reportFailureCount": { + "type": "integer" + }, + "reportFailureVersion": { + "type": "keyword" + }, + "sendUsageFrom": { + "type": "keyword" + }, + "userHasSeenNotice": { + "type": "boolean" + } + } + }, + "timelion-sheet": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "timelion_chart_height": { + "type": "integer" + }, + "timelion_columns": { + "type": "integer" + }, + "timelion_interval": { + "type": "keyword" + }, + "timelion_other_interval": { + "type": "keyword" + }, + "timelion_rows": { + "type": "integer" + }, + "timelion_sheet": { + "type": "text" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "tsvb-validation-telemetry": { + "properties": { + "failedRequests": { + "type": "long" + } + } + }, + "type": { + "type": "keyword" + }, + "ui-metric": { + "properties": { + "count": { + "type": "integer" + } + } + }, + "updated_at": { + "type": "date" + }, + "upgrade-assistant-reindex-operation": { + "dynamic": "true", + "properties": { + "indexName": { + "type": "keyword" + }, + "status": { + "type": "integer" + } + } + }, + "upgrade-assistant-telemetry": { + "properties": { + "features": { + "properties": { + "deprecation_logging": { + "properties": { + "enabled": { + "null_value": true, + "type": "boolean" + } + } + } + } + }, + "ui_open": { + "properties": { + "cluster": { + "null_value": 0, + "type": "long" + }, + "indices": { + "null_value": 0, + "type": "long" + }, + "overview": { + "null_value": 0, + "type": "long" + } + } + }, + "ui_reindex": { + "properties": { + "close": { + "null_value": 0, + "type": "long" + }, + "open": { + "null_value": 0, + "type": "long" + }, + "start": { + "null_value": 0, + "type": "long" + }, + "stop": { + "null_value": 0, + "type": "long" + } + } + } + } + }, + "uptime-dynamic-settings": { + "properties": { + "heartbeatIndices": { + "type": "keyword" + } + } + }, + "url": { + "properties": { + "accessCount": { + "type": "long" + }, + "accessDate": { + "type": "date" + }, + "createDate": { + "type": "date" + }, + "url": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "visualization": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "savedSearchRefName": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "visState": { + "type": "text" + } + } + } + } + }, + "settings": { + "index": { + "auto_expand_replicas": "0-1", + "number_of_replicas": "0", + "number_of_shards": "1" + } + } + } +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index b47befbf9057b..30747ee555fe2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1088,7 +1088,7 @@ pirates "^4.0.0" source-map-support "^0.5.16" -"@babel/runtime-corejs2@^7.2.0", "@babel/runtime-corejs2@^7.4.2", "@babel/runtime-corejs2@^7.6.3": +"@babel/runtime-corejs2@^7.2.0", "@babel/runtime-corejs2@^7.6.3": version "7.9.2" resolved "https://registry.yarnpkg.com/@babel/runtime-corejs2/-/runtime-corejs2-7.9.2.tgz#f11d074ff99b9b4319b5ecf0501f12202bf2bf4d" integrity sha512-ayjSOxuK2GaSDJFCtLgHnYjuMyIpViNujWrZo8GUpN60/n7juzJKK5yOo6RFVb0zdU9ACJFK+MsZrUnj3OmXMw== @@ -1096,14 +1096,6 @@ core-js "^2.6.5" regenerator-runtime "^0.13.4" -"@babel/runtime@7.0.0-beta.54": - version "7.0.0-beta.54" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.0.0-beta.54.tgz#39ebb42723fe7ca4b3e1b00e967e80138d47cadf" - integrity sha1-Oeu0JyP+fKSz4bAOln6AE41Hyt8= - dependencies: - core-js "^2.5.7" - regenerator-runtime "^0.12.0" - "@babel/runtime@7.3.4": version "7.3.4" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.3.4.tgz#73d12ba819e365fcf7fd152aed56d6df97d21c83" @@ -1314,16 +1306,16 @@ tabbable "^1.1.0" uuid "^3.1.0" -"@elastic/eui@21.0.1": - version "21.0.1" - resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-21.0.1.tgz#7cf6846ed88032aebd72f75255298df2fbe26554" - integrity sha512-Hf8ZGRI265qpOKwnnqhZkaMQvali+Xg6FAaNZSskkpXvdLhwGtUGC4YU7HW2vb7svq6IpNUuz+5XWrMLLzVY9w== +"@elastic/eui@22.3.0": + version "22.3.0" + resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-22.3.0.tgz#31a7a0aaf69b329acff2791fca677a824b63539d" + integrity sha512-LoQd11RoD6cbDuVQhwTr3lR4Jga8D5cBGaKFPzaS8MoxW5vCu0gcosjI6O5SqmCwifyfU4JP5zeOjy8TawJFxw== dependencies: - "@types/chroma-js" "^1.4.3" + "@types/chroma-js" "^2.0.0" "@types/enzyme" "^3.1.13" "@types/lodash" "^4.14.116" "@types/numeral" "^0.0.25" - "@types/react-beautiful-dnd" "^10.1.0" + "@types/react-beautiful-dnd" "^12.1.2" "@types/react-input-autosize" "^2.0.2" "@types/react-virtualized" "^9.18.7" chroma-js "^2.0.4" @@ -1335,7 +1327,7 @@ numeral "^2.0.6" prop-types "^15.6.0" react-ace "^7.0.5" - react-beautiful-dnd "^10.1.0" + react-beautiful-dnd "^13.0.0" react-focus-lock "^1.17.7" react-input-autosize "^2.2.2" react-is "~16.3.0" @@ -3767,10 +3759,10 @@ resolved "https://registry.yarnpkg.com/@types/chroma-js/-/chroma-js-1.4.2.tgz#3152c8dedfa8621f1ccaaabb40722a8aca808bcf" integrity sha512-Ni8yCN1vF0yfnfKf5bNrBm+92EdZIX2sUk+A4t4QvO1x/9G04rGyC0nik4i5UcNfx8Q7MhX4XUDcy2nrkKQLFg== -"@types/chroma-js@^1.4.3": - version "1.4.3" - resolved "https://registry.yarnpkg.com/@types/chroma-js/-/chroma-js-1.4.3.tgz#4456e5cb46885a4952324e55a4b6d4064904790c" - integrity sha512-m33zg9cRLtuaUSzlbMrr7iLIKNzrD4+M6Unt5+9mCu4BhR5NwnRjVKblINCwzcBXooukIgld8DtEncP8qpvbNg== +"@types/chroma-js@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@types/chroma-js/-/chroma-js-2.0.0.tgz#b0fc98c8625d963f14e8138e0a7961103303ab22" + integrity sha512-iomunXsXjDxhm2y1OeJt8NwmgC7RyNkPAOddlYVGsbGoX8+1jYt84SG4/tf6RWcwzROLx1kPXPE95by1s+ebIg== "@types/chromedriver@^2.38.0": version "2.38.0" @@ -4546,13 +4538,6 @@ "@types/history" "*" "@types/react" "*" -"@types/react-beautiful-dnd@^10.1.0": - version "10.1.1" - resolved "https://registry.yarnpkg.com/@types/react-beautiful-dnd/-/react-beautiful-dnd-10.1.1.tgz#7afae39a4247f30c13b8bbb726ccd1b8cda9d4a5" - integrity sha512-75XELhEIWKTkyd1GdVZFvS1MtJwDs9tM37BbIat8mevcw+uH5dcJzZiwESHIWAzySHawS48nkKCQk/bEDp13Mw== - dependencies: - "@types/react" "*" - "@types/react-beautiful-dnd@^12.1.1": version "12.1.1" resolved "https://registry.yarnpkg.com/@types/react-beautiful-dnd/-/react-beautiful-dnd-12.1.1.tgz#149e638c0f912eee6b74ea419b26bb43d0b1da60" @@ -4560,6 +4545,13 @@ dependencies: "@types/react" "*" +"@types/react-beautiful-dnd@^12.1.2": + version "12.1.2" + resolved "https://registry.yarnpkg.com/@types/react-beautiful-dnd/-/react-beautiful-dnd-12.1.2.tgz#dfd1bdb072e92c1363e5f7a4c1842eaf95f77b21" + integrity sha512-h+0mA4cHmzL4BhyCniB6ZSSZhfO9LpXXbnhdAfa2k7klS03woiOT+Dh5AchY6eoQXk3vQVtqn40YY3u+MwFs8A== + dependencies: + "@types/react" "*" + "@types/react-color@^3.0.1": version "3.0.1" resolved "https://registry.yarnpkg.com/@types/react-color/-/react-color-3.0.1.tgz#5433e2f503ea0e0831cbc6fd0c20f8157d93add0" @@ -9873,7 +9865,7 @@ core-js@^1.0.0: resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" integrity sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY= -core-js@^2.2.0, core-js@^2.4.0, core-js@^2.5.0, core-js@^2.5.1, core-js@^2.5.3, core-js@^2.5.7, core-js@^2.6.5, core-js@^2.6.9: +core-js@^2.2.0, core-js@^2.4.0, core-js@^2.5.0, core-js@^2.5.1, core-js@^2.5.3, core-js@^2.6.5, core-js@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.9.tgz#6b4b214620c834152e179323727fc19741b084f2" integrity sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A== @@ -10206,18 +10198,6 @@ cson@5.1.0: requirefresh "^2.1.0" safefs "^4.1.0" -css-box-model@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/css-box-model/-/css-box-model-1.0.0.tgz#60142814f2b25be00c4aac65ea1a55a531b18922" - integrity sha512-MGipbCM6/HGmsOwN6Enq1OvNKy8H5Q1XKoyBszxwv2efly7ZVg+HcFILX8O6S0xfj27l1+6P7FyCjcQ90m5HBQ== - -css-box-model@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/css-box-model/-/css-box-model-1.1.1.tgz#c9fd8e7a8b1d59d41d6812fd1765433f671b2ee0" - integrity sha512-ZxbuLFeAPEDb0wPbGfT7783Vb00MVAkvOlMKwr0kA2PD5EGxk6P3MAhedvVuyVJCWb54bb+6HQ7pdPYENf8AZw== - dependencies: - tiny-invariant "^1.0.3" - css-box-model@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/css-box-model/-/css-box-model-1.2.0.tgz#3a26377b4162b3200d2ede4b064ec5b6a75186d0" @@ -20305,21 +20285,11 @@ mem@^4.0.0: mimic-fn "^1.0.0" p-is-promise "^1.1.0" -memoize-one@^4.0.0: - version "4.0.2" - resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-4.0.2.tgz#3fb8db695aa14ab9c0f1644e1585a8806adc1aee" - integrity sha512-ucx2DmXTeZTsS4GPPUZCbULAN7kdPT1G+H49Y34JjbQ5ESc6OGhVxKvb1iKhr9v19ZB9OtnHwNnhUnNR/7Wteg== - memoize-one@^5.0.0: version "5.0.1" resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.0.1.tgz#35a709ffb6e5f0cb79f9679a96f09ec3a35addfa" integrity sha512-S3plzyksLOSF4pkf1Xlb7mA8ZRKZlgp3ebg7rULbfwPT8Ww7uZz5CbLgRKaR92GeXpsNiFbfCRWf/uOrCYIbRg== -memoize-one@^5.0.1: - version "5.0.2" - resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.0.2.tgz#6aba5276856d72fb44ead3efab86432f94ba203d" - integrity sha512-o7lldN4fs/axqctc03NF+PMhd2veRrWeJ2n2GjEzUPBD4F9rmNg4A+bQCACIzwjHJEXuYv4aFFMaH35KZfHUrw== - memoize-one@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.1.1.tgz#047b6e3199b508eaec03504de71229b8eb1d75c0" @@ -23618,15 +23588,6 @@ prop-types@15.6.0: loose-envify "^1.3.1" object-assign "^4.1.1" -prop-types@15.6.1: - version "15.6.1" - resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.1.tgz#36644453564255ddda391191fb3a125cbdf654ca" - integrity sha512-4ec7bY1Y66LymSUOH/zARVYObB23AT2h8cf6e/O6ZALB/N0sqZFEx7rq6EYPX2MkOdKORuooI/H5k9TlR4q7kQ== - dependencies: - fbjs "^0.8.16" - loose-envify "^1.3.1" - object-assign "^4.1.1" - prop-types@15.7.2, prop-types@15.x, prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.6, prop-types@^15.5.7, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2: version "15.7.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" @@ -24251,20 +24212,6 @@ react-apollo@^2.1.4: lodash "^4.17.10" prop-types "^15.6.0" -react-beautiful-dnd@^10.1.0: - version "10.1.1" - resolved "https://registry.yarnpkg.com/react-beautiful-dnd/-/react-beautiful-dnd-10.1.1.tgz#d753088d77d7632e77cf8a8935fafcffa38f574b" - integrity sha512-TdE06Shfp56wm28EzjgC56EEMgGI5PDHejJ2bxuAZvZr8CVsbksklsJC06Hxf0MSL7FHbflL/RpkJck9isuxHg== - dependencies: - "@babel/runtime-corejs2" "^7.4.2" - css-box-model "^1.1.1" - memoize-one "^5.0.1" - prop-types "^15.6.1" - raf-schd "^4.0.0" - react-redux "^5.0.7" - redux "^4.0.1" - tiny-invariant "^1.0.4" - react-beautiful-dnd@^12.2.0: version "12.2.0" resolved "https://registry.yarnpkg.com/react-beautiful-dnd/-/react-beautiful-dnd-12.2.0.tgz#e5f6222f9e7934c6ed4ee09024547f9e353ae423" @@ -24278,20 +24225,18 @@ react-beautiful-dnd@^12.2.0: redux "^4.0.4" use-memo-one "^1.1.1" -react-beautiful-dnd@^8.0.7: - version "8.0.7" - resolved "https://registry.yarnpkg.com/react-beautiful-dnd/-/react-beautiful-dnd-8.0.7.tgz#2cc7ba62bffe08d3dad862fd8f48204440901b43" - integrity sha512-j2cClhKuACXp/KcG+YXSrVxZ7AQl13dG9X+ojstR6H2G0yoA+1GZn/O147PWVVScmfk/mSt60GNseH7vjae7vQ== +react-beautiful-dnd@^13.0.0: + version "13.0.0" + resolved "https://registry.yarnpkg.com/react-beautiful-dnd/-/react-beautiful-dnd-13.0.0.tgz#f70cc8ff82b84bc718f8af157c9f95757a6c3b40" + integrity sha512-87It8sN0ineoC3nBW0SbQuTFXM6bUqM62uJGY4BtTf0yzPl8/3+bHMWkgIe0Z6m8e+gJgjWxefGRVfpE3VcdEg== dependencies: - "@babel/runtime" "7.0.0-beta.54" - css-box-model "^1.0.0" - memoize-one "^4.0.0" - prop-types "15.6.1" - raf-schd "^4.0.0" - react-motion "^0.5.2" - react-redux "^5.0.7" - redux "^4.0.0" - tiny-invariant "^0.0.3" + "@babel/runtime" "^7.8.4" + css-box-model "^1.2.0" + memoize-one "^5.1.1" + raf-schd "^4.0.2" + react-redux "^7.1.1" + redux "^4.0.4" + use-memo-one "^1.1.1" react-clientside-effect@^1.2.0: version "1.2.0" @@ -24658,15 +24603,6 @@ react-motion@^0.4.8: prop-types "^15.5.8" raf "^3.1.0" -react-motion@^0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/react-motion/-/react-motion-0.5.2.tgz#0dd3a69e411316567927917c6626551ba0607316" - integrity sha512-9q3YAvHoUiWlP3cK0v+w1N5Z23HXMj4IF4YuvjvWegWqNPfLXsOBE/V7UvQGpXxHFKRQQcNcVQE31g9SB/6qgQ== - dependencies: - performance-now "^0.2.0" - prop-types "^15.5.8" - raf "^3.1.0" - react-onclickoutside@^6.5.0: version "6.7.1" resolved "https://registry.yarnpkg.com/react-onclickoutside/-/react-onclickoutside-6.7.1.tgz#6a5b5b8b4eae6b776259712c89c8a2b36b17be93" @@ -24733,7 +24669,7 @@ react-portal@^3.2.0: dependencies: prop-types "^15.5.8" -react-redux@^5.0.7, react-redux@^5.1.2: +react-redux@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-5.1.2.tgz#b19cf9e21d694422727bf798e934a916c4080f57" integrity sha512-Ns1G0XXc8hDyH/OcBHOxNgQx9ayH3SPxBnFCOidGKSle8pKihysQw2rG/PmciUQRoclhVBO8HMhiRmGXnDja9Q== @@ -25456,14 +25392,6 @@ redux@^4.0.0: loose-envify "^1.1.0" symbol-observable "^1.2.0" -redux@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.1.tgz#436cae6cc40fbe4727689d7c8fae44808f1bfef5" - integrity sha512-R7bAtSkk7nY6O/OYMVR9RiBI+XghjF9rlbl5806HJbQph0LJVHZrU5oaO4q70eUKiqMRqm4y07KLTlMZ2BlVmg== - dependencies: - loose-envify "^1.4.0" - symbol-observable "^1.2.0" - redux@^4.0.4: version "4.0.4" resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.4.tgz#4ee1aeb164b63d6a1bcc57ae4aa0b6e6fa7a3796" @@ -29092,12 +29020,7 @@ tiny-inflate@^1.0.0, tiny-inflate@^1.0.2: resolved "https://registry.yarnpkg.com/tiny-inflate/-/tiny-inflate-1.0.2.tgz#93d9decffc8805bd57eae4310f0b745e9b6fb3a7" integrity sha1-k9nez/yIBb1X6uQxDwt0Xptvs6c= -tiny-invariant@^0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-0.0.3.tgz#4c7283c950e290889e9e94f64d3586ec9156cf44" - integrity sha512-SA2YwvDrCITM9fTvHTHRpq9W6L2fBsClbqm3maT5PZux4Z73SPPDYwJMtnoWh6WMgmCkJij/LaOlWiqJqFMK8g== - -tiny-invariant@^1.0.2, tiny-invariant@^1.0.3, tiny-invariant@^1.0.4: +tiny-invariant@^1.0.2: version "1.0.4" resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.0.4.tgz#346b5415fd93cb696b0c4e8a96697ff590f92463" integrity sha512-lMhRd/djQJ3MoaHEBrw8e2/uM4rs9YMNk0iOr8rHQ0QdbM7D4l0gFl3szKdeixrlyfm9Zqi4dxHCM2qVG8ND5g==