diff --git a/docs/development/add-data-guide.asciidoc b/docs/developer/add-data-guide.asciidoc similarity index 100% rename from docs/development/add-data-guide.asciidoc rename to docs/developer/add-data-guide.asciidoc diff --git a/docs/development/core-development.asciidoc b/docs/developer/core-development.asciidoc similarity index 100% rename from docs/development/core-development.asciidoc rename to docs/developer/core-development.asciidoc diff --git a/docs/development/core/development-basepath.asciidoc b/docs/developer/core/development-basepath.asciidoc similarity index 100% rename from docs/development/core/development-basepath.asciidoc rename to docs/developer/core/development-basepath.asciidoc diff --git a/docs/development/core/development-dependencies.asciidoc b/docs/developer/core/development-dependencies.asciidoc similarity index 100% rename from docs/development/core/development-dependencies.asciidoc rename to docs/developer/core/development-dependencies.asciidoc diff --git a/docs/development/core/development-elasticsearch.asciidoc b/docs/developer/core/development-elasticsearch.asciidoc similarity index 100% rename from docs/development/core/development-elasticsearch.asciidoc rename to docs/developer/core/development-elasticsearch.asciidoc diff --git a/docs/development/core/development-functional-tests.asciidoc b/docs/developer/core/development-functional-tests.asciidoc similarity index 100% rename from docs/development/core/development-functional-tests.asciidoc rename to docs/developer/core/development-functional-tests.asciidoc diff --git a/docs/development/core/development-modules.asciidoc b/docs/developer/core/development-modules.asciidoc similarity index 100% rename from docs/development/core/development-modules.asciidoc rename to docs/developer/core/development-modules.asciidoc diff --git a/docs/development/core/development-unit-tests.asciidoc b/docs/developer/core/development-unit-tests.asciidoc similarity index 100% rename from docs/development/core/development-unit-tests.asciidoc rename to docs/developer/core/development-unit-tests.asciidoc diff --git a/docs/development/index.asciidoc b/docs/developer/index.asciidoc similarity index 100% rename from docs/development/index.asciidoc rename to docs/developer/index.asciidoc diff --git a/docs/development/plugin-development.asciidoc b/docs/developer/plugin-development.asciidoc similarity index 100% rename from docs/development/plugin-development.asciidoc rename to docs/developer/plugin-development.asciidoc diff --git a/docs/development/plugin/development-plugin-feature-registration.asciidoc b/docs/developer/plugin/development-plugin-feature-registration.asciidoc similarity index 100% rename from docs/development/plugin/development-plugin-feature-registration.asciidoc rename to docs/developer/plugin/development-plugin-feature-registration.asciidoc diff --git a/docs/development/plugin/development-plugin-functional-tests.asciidoc b/docs/developer/plugin/development-plugin-functional-tests.asciidoc similarity index 100% rename from docs/development/plugin/development-plugin-functional-tests.asciidoc rename to docs/developer/plugin/development-plugin-functional-tests.asciidoc diff --git a/docs/development/plugin/development-plugin-localization.asciidoc b/docs/developer/plugin/development-plugin-localization.asciidoc similarity index 100% rename from docs/development/plugin/development-plugin-localization.asciidoc rename to docs/developer/plugin/development-plugin-localization.asciidoc diff --git a/docs/development/plugin/development-plugin-resources.asciidoc b/docs/developer/plugin/development-plugin-resources.asciidoc similarity index 100% rename from docs/development/plugin/development-plugin-resources.asciidoc rename to docs/developer/plugin/development-plugin-resources.asciidoc diff --git a/docs/development/plugin/development-uiexports.asciidoc b/docs/developer/plugin/development-uiexports.asciidoc similarity index 100% rename from docs/development/plugin/development-uiexports.asciidoc rename to docs/developer/plugin/development-uiexports.asciidoc diff --git a/docs/development/pr-review.asciidoc b/docs/developer/pr-review.asciidoc similarity index 100% rename from docs/development/pr-review.asciidoc rename to docs/developer/pr-review.asciidoc diff --git a/docs/development/security/index.asciidoc b/docs/developer/security/index.asciidoc similarity index 100% rename from docs/development/security/index.asciidoc rename to docs/developer/security/index.asciidoc diff --git a/docs/development/security/rbac.asciidoc b/docs/developer/security/rbac.asciidoc similarity index 100% rename from docs/development/security/rbac.asciidoc rename to docs/developer/security/rbac.asciidoc diff --git a/docs/development/visualize/development-create-visualization.asciidoc b/docs/developer/visualize/development-create-visualization.asciidoc similarity index 100% rename from docs/development/visualize/development-create-visualization.asciidoc rename to docs/developer/visualize/development-create-visualization.asciidoc diff --git a/docs/development/visualize/development-embedding-visualizations.asciidoc b/docs/developer/visualize/development-embedding-visualizations.asciidoc similarity index 100% rename from docs/development/visualize/development-embedding-visualizations.asciidoc rename to docs/developer/visualize/development-embedding-visualizations.asciidoc diff --git a/docs/development/visualize/development-visualize-index.asciidoc b/docs/developer/visualize/development-visualize-index.asciidoc similarity index 100% rename from docs/development/visualize/development-visualize-index.asciidoc rename to docs/developer/visualize/development-visualize-index.asciidoc diff --git a/docs/development.asciidoc b/docs/development.asciidoc deleted file mode 100644 index 55c2beff074d5c..00000000000000 --- a/docs/development.asciidoc +++ /dev/null @@ -1,25 +0,0 @@ -[[development]] -= Contributing to Kibana - -[partintro] --- -Contributing to Kibana can be daunting at first, but it doesn't have to be. If -you're planning a pull request to the Kibana repository, you may want to start -with <>. - -If you'd prefer to use Kibana's internal plugin API, then check out -<>. --- - -include::development/core-development.asciidoc[] - -include::development/plugin-development.asciidoc[] - -include::development/visualize/development-visualize-index.asciidoc[] - -include::development/add-data-guide.asciidoc[] - -include::development/security/index.asciidoc[] - -include::development/pr-review.asciidoc[] - diff --git a/docs/index.asciidoc b/docs/index.asciidoc index a4972c0aa21d2c..52dffb696554f2 100644 --- a/docs/index.asciidoc +++ b/docs/index.asciidoc @@ -84,6 +84,6 @@ include::migration.asciidoc[] include::CHANGELOG.asciidoc[] -include::development/index.asciidoc[] +include::developer/index.asciidoc[] include::redirects.asciidoc[] diff --git a/docs/maps/images/create_phrase_filter.png b/docs/maps/images/create_phrase_filter.png new file mode 100644 index 00000000000000..720aecf44d9faf Binary files /dev/null and b/docs/maps/images/create_phrase_filter.png differ diff --git a/docs/maps/images/create_spatial_filter.png b/docs/maps/images/create_spatial_filter.png new file mode 100644 index 00000000000000..abb52bd0b5b0a6 Binary files /dev/null and b/docs/maps/images/create_spatial_filter.png differ diff --git a/docs/maps/images/filter_icon.png b/docs/maps/images/filter_icon.png new file mode 100644 index 00000000000000..08fd9c6b10a32b Binary files /dev/null and b/docs/maps/images/filter_icon.png differ diff --git a/docs/maps/images/global_search_bar.png b/docs/maps/images/global_search_bar.png index 42445f82db65ad..c1bb1e7835e365 100644 Binary files a/docs/maps/images/global_search_bar.png and b/docs/maps/images/global_search_bar.png differ diff --git a/docs/maps/images/global_search_multiple_indices_query1.png b/docs/maps/images/global_search_multiple_indices_query1.png index 7007db3fa0a806..f019f2c4e39d64 100644 Binary files a/docs/maps/images/global_search_multiple_indices_query1.png and b/docs/maps/images/global_search_multiple_indices_query1.png differ diff --git a/docs/maps/images/global_search_multiple_indices_query2.png b/docs/maps/images/global_search_multiple_indices_query2.png index 2a97311114feb7..51017039178f19 100644 Binary files a/docs/maps/images/global_search_multiple_indices_query2.png and b/docs/maps/images/global_search_multiple_indices_query2.png differ diff --git a/docs/maps/images/tools_icon.png b/docs/maps/images/tools_icon.png new file mode 100644 index 00000000000000..677b16eb8e484d Binary files /dev/null and b/docs/maps/images/tools_icon.png differ diff --git a/docs/maps/search.asciidoc b/docs/maps/search.asciidoc index 3830ecdcbc1f00..98b8c6ae59c753 100644 --- a/docs/maps/search.asciidoc +++ b/docs/maps/search.asciidoc @@ -2,8 +2,9 @@ [[maps-search]] == Searching your data -**Elastic Maps** embeds the query bar for real-time ad hoc search. +**Elastic Maps** embeds the search bar for real-time search. Only layers requesting data from {es} are filtered when you submit a search request. +Layers narrowed by the search context contain the filter icon image:maps/images/filter_icon.png[] to the right of layer name in the legend. You can create a layer that requests data from {es} from the following: @@ -13,13 +14,51 @@ You can create a layer that requests data from {es} from the following: ** Grid aggregation source -** <> +** <>. The search context is applied to both the terms join and the vector source when the vector source is provided by Elasticsearch documents. * <> with Grid aggregation source [role="screenshot"] image::maps/images/global_search_bar.png[] +[role="xpack"] +[[maps-create-filter-from-map]] +=== Creating filters from your map + +You can create two types of filters by interacting with your map: + +* <> +* <> + +[float] +[[maps-spatial-filters]] +==== Spatial filters + +A spatial filter narrow searchs results to documents that either intersect with, are within, or do not intersect with the specified geometry. + +You can create spatial filters in two ways: + +* Click the tool icon image:maps/images/tools_icon.png[], and then draw a polygon or bounding box on the map to define the spatial filter. +* Click *Filter by geometry* in a tooltip, and then use the feature's geometry for the spatial filter. ++ +[role="screenshot"] +image::maps/images/create_spatial_filter.png[] + +Spatial filters have the following properties: + +* *Geometry label* enables you to provide a meaningful name for your spatial filter. +* *Spatial field* specifies the geo_point or geo_shape field used to determine if a document matches the spatial relation with the specified geometry. +* *Spatial relation* determines the {ref}/query-dsl-geo-shape-query.html#_spatial_relations[spatial relation operator] to use at search time. Only available when *Spatial field* is set to geo_shape. + +[float] +[[maps-phrase-filter]] +==== Phrase filters + +A phrase filter narrows search results to documents that contain the specified text. +You can create a phrase filter by clicking the plus icon image:maps/images/gs_plus_icon.png[] in a feature tooltip. + +[role="screenshot"] +image::maps/images/create_phrase_filter.png[] [role="xpack"] [[maps-layer-based-filtering]] @@ -43,6 +82,18 @@ This can also occur with a single layer with an {es} source and a <> Searching across multiple indices might sometimes result in empty layers. The most common cause for empty layers are searches for a field that exists in one index, but does not exist in other indices. + +[float] +[[maps-disable-search-for-layer]] +==== Disable search for layer + +To prevent the global search bar from applying search context to a layer, clear the *Apply global filter to layer* checkbox in Layer settings. +Disabling the search context applies to the layer source and all <> configured for the layer. + +[float] +[[maps-add-index-search]] +==== Use _index in your search + Add {ref}/mapping-index-field.html[_index] to your search to include documents from indices that do not contain a search field. For example, suppose you have a vector layer showing the `kibana_sample_data_logs` documents diff --git a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete.js b/src/legacy/core_plugins/console/public/quarantined/src/autocomplete.js index d8cd5d868e9ae3..cec82e9690eadf 100644 --- a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete.js +++ b/src/legacy/core_plugins/console/public/quarantined/src/autocomplete.js @@ -344,7 +344,14 @@ export default function (editor) { let valueToInsert = termAsString; let templateInserted = false; if (context.addTemplate && !_.isUndefined(term.template) && !_.isNull(term.template)) { - const indentedTemplateLines = utils.jsonToString(term.template, true).split('\n'); + let indentedTemplateLines; + // In order to allow triple quoted strings in template completion we check the `__raw_` + // attribute to determine whether this template should go through JSON formatting. + if (term.template.__raw && term.template.value) { + indentedTemplateLines = term.template.value.split('\n'); + } else { + indentedTemplateLines = utils.jsonToString(term.template, true).split('\n'); + } let currentIndentation = session.getLine(context.rangeToReplace.start.row); currentIndentation = currentIndentation.match(/^\s*/)[0]; for (let i = 1; i < indentedTemplateLines.length; i++) // skip first line diff --git a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/body_completer.js b/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/body_completer.js index 68e0889f9c6182..e377a749aaf785 100644 --- a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/body_completer.js +++ b/src/legacy/core_plugins/console/public/quarantined/src/autocomplete/body_completer.js @@ -127,6 +127,18 @@ class ScopeResolver extends SharedComponent { } function getTemplate(description) { if (description.__template) { + if (description.__raw && _.isString(description.__template)) { + return { + // This is a special secret attribute that gets passed through to indicate that + // the raw value should be passed through to the console without JSON.stringifying it + // first. + // + // Primary use case is to allow __templates to contain extended JSON special values like + // triple quotes. + __raw: true, + value: description.__template, + }; + } return description.__template; } else if (description.__one_of) { return getTemplate(description.__one_of[0]); diff --git a/src/legacy/core_plugins/console/public/quarantined/src/utils.js b/src/legacy/core_plugins/console/public/quarantined/src/utils.js index 1f2c3b9267b748..5b6bd1646c3006 100644 --- a/src/legacy/core_plugins/console/public/quarantined/src/utils.js +++ b/src/legacy/core_plugins/console/public/quarantined/src/utils.js @@ -59,18 +59,39 @@ utils.reformatData = function (data, indent) { }; utils.collapseLiteralStrings = function (data) { - return data.replace(/"""(?:\s*\r?\n)?((?:.|\r?\n)*?)(?:\r?\n\s*)?"""/g, function (match, literal) { - return JSON.stringify(literal); - }); + const splitData = data.split(`"""`); + for (let idx = 1; idx < splitData.length - 1; idx += 2) { + splitData[idx] = JSON.stringify(splitData[idx]); + } + return splitData.join(''); }; +/* + The following regex describes global match on: + 1. one colon followed by any number of space characters + 2. one double quote (not escaped, special case for JSON in JSON). + 3. greedily match any non double quote and non newline char OR any escaped double quote char (non-capturing). + 4. handle a special case where an escaped slash may be the last character + 5. one double quote + + For instance: `: "some characters \" here"` + Will match and be expanded to: `"""some characters " here"""` + + */ + +const LITERAL_STRING_CANDIDATES = /((:[\s\r\n]*)([^\\])"(\\"|[^"\n])*\\?")/g; + utils.expandLiteralStrings = function (data) { - return data.replace(/("(?:\\"|[^"])*?")/g, function (match, string) { - // expand things with two slashes or more - if (string.split(/\\./).length > 2) { - string = JSON.parse(string).replace('^\s*\n', '').replace('\n\s*^', ''); - const append = string.includes('\n') ? '\n' : ''; // only go multi line if the string has multiline - return '"""' + append + string + append + '"""'; + return data.replace(LITERAL_STRING_CANDIDATES, function (match, string) { + // Expand to triple quotes if there are _any_ slashes + if (string.match(/\\./)) { + const firstDoubleQuoteIdx = string.indexOf('"'); + const colonAndAnySpacing = string.slice(0, firstDoubleQuoteIdx); + const rawStringifiedValue = string.slice(firstDoubleQuoteIdx, string.length); + const jsonValue = JSON.parse(rawStringifiedValue) + .replace('^\s*\n', '') + .replace('\n\s*^', ''); + return `${colonAndAnySpacing}"""${jsonValue}"""`; } else { return string; } diff --git a/src/legacy/core_plugins/console/public/quarantined/tests/src/utils_string_collapsing.txt b/src/legacy/core_plugins/console/public/quarantined/tests/src/utils_string_collapsing.txt index 235151317377fd..5ddd1b5a376fe1 100644 --- a/src/legacy/core_plugins/console/public/quarantined/tests/src/utils_string_collapsing.txt +++ b/src/legacy/core_plugins/console/public/quarantined/tests/src/utils_string_collapsing.txt @@ -8,11 +8,11 @@ to you """ ========== String only 2 ------------------------------------- -""" +""" starting with new lines and ending as well - """ +""" ------------------------------------- -"starting with new lines and ending as well" +"\nstarting with new lines and ending as well\n" ========== Strings in requests ------------------------------------- @@ -27,8 +27,8 @@ test2 } ------------------------------------- { - "f": { "somefield" : "test\ntest2" }, + "f": { "somefield" : "\ntest\ntest2\n" }, "g": { "script" : "second + \"\\\";" }, "h": 1, "script": "a + 2" -} \ No newline at end of file +} diff --git a/src/legacy/core_plugins/console/public/quarantined/tests/src/utils_string_expanding.txt b/src/legacy/core_plugins/console/public/quarantined/tests/src/utils_string_expanding.txt index 762e0ad1646344..34bf0f3bc20e72 100644 --- a/src/legacy/core_plugins/console/public/quarantined/tests/src/utils_string_expanding.txt +++ b/src/legacy/core_plugins/console/public/quarantined/tests/src/utils_string_expanding.txt @@ -2,20 +2,41 @@ Scripts in requests ------------------------------------- { - "f": { "script" : { "source": "test\ntest\\2" } }, - "g": { "script" : "second + \"\\\";" }, - "f": "short with \\", - "h": 1, + "f": { "script": { "source": "\ntest\ntest\\\\\\\\\\\\\\\\2\n" } }, + "g": { "script": "second + \"\\\";" }, + "a": "short with \\", + "\\\\h": 1, "script": "a + 2" } ------------------------------------- { - "f": { "script" : { "source": """ + "f": { "script": { "source": """ test -test\2 +test\\\\\\\\2 """ } }, - "g": { "script" : """second + "\";""" }, - "f": "short with \\", - "h": 1, + "g": { "script": """second + "\";""" }, + "a": """short with \""", + "\\\\h": 1, "script": "a + 2" } +========== +Preserve triple quotes +------------------------------------- +{ + "content\\\": "tri\"ple", +} +------------------------------------- +{ + "content\\\": """tri"ple""", +} +========== +Correctly parse with JSON embedded inside values +------------------------------------- +{ + "content\\\\": " { \"json\": \"inside\\\\\" ok! 1\n \" } " +} +------------------------------------- +{ + "content\\\\": """ { "json": "inside\\" ok! 1 + " } """ +} diff --git a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/phrase_value_input.tsx b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/phrase_value_input.tsx index f33ce3d7486fc0..0696bacc568b55 100644 --- a/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/phrase_value_input.tsx +++ b/src/legacy/core_plugins/data/public/filter/filter_bar/filter_editor/phrase_value_input.tsx @@ -60,7 +60,9 @@ class PhraseValueInputUI extends PhraseSuggestor { private renderWithSuggestions() { const { suggestions } = this.state; const { value, intl, onChange } = this.props; - const options = value ? uniq([value, ...suggestions]) : suggestions; + // there are cases when the value is a number, this would cause an exception + const valueAsStr = String(value); + const options = value ? uniq([valueAsStr, ...suggestions]) : suggestions; return ( { })} options={options} getLabel={option => option} - selectedOptions={value ? [value] : []} + selectedOptions={value ? [valueAsStr] : []} onChange={([newValue = '']) => onChange(newValue)} onSearchChange={this.onSearchChange} singleSelection={{ asPlainText: true }} diff --git a/src/legacy/core_plugins/kibana/public/discover/controllers/discover.js b/src/legacy/core_plugins/kibana/public/discover/controllers/discover.js index e3f7701b02a605..9f572a6f8bc2eb 100644 --- a/src/legacy/core_plugins/kibana/public/discover/controllers/discover.js +++ b/src/legacy/core_plugins/kibana/public/discover/controllers/discover.js @@ -54,7 +54,6 @@ import { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query'; import { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; import { getFilterGenerator } from 'ui/filter_manager'; import { SavedObjectsClientProvider } from 'ui/saved_objects'; -import { VisualizeLoaderProvider } from 'ui/visualize/loader/visualize_loader'; import { recentlyAccessed } from 'ui/persisted_log'; import { getDocLink } from 'ui/documentation_links'; import '../components/fetch_error'; @@ -196,8 +195,6 @@ function discoverController( localStorage, uiCapabilities ) { - const visualizeLoader = Private(VisualizeLoaderProvider); - let visualizeHandler; const Vis = Private(VisProvider); const responseHandler = vislibSeriesResponseHandlerProvider().handler; const getUnhashableStates = Private(getUnhashableStatesProvider); @@ -214,6 +211,13 @@ function discoverController( timefilter.disableTimeRangeSelector(); timefilter.disableAutoRefreshSelector(); + $scope.timefilterUpdateHandler = (ranges) => { + timefilter.setTime({ + from: moment(ranges.from).toISOString(), + to: moment(ranges.to).toISOString(), + mode: 'absolute', + }); + }; $scope.getDocLink = getDocLink; $scope.intervalOptions = intervalOptions; @@ -794,15 +798,7 @@ function discoverController( .resolve(buildVislibDimensions($scope.vis, { timeRange: $scope.timeRange, searchSource: $scope.searchSource })) .then(resp => responseHandler(tabifiedData, resp)) .then(resp => { - visualizeHandler.render({ - as: 'visualization', - value: { - visType: $scope.vis.type.name, - visData: resp, - visConfig: $scope.vis.params, - params: {}, - } - }); + $scope.histogramData = resp; }); } @@ -1048,13 +1044,6 @@ function discoverController( $scope.searchSource.setField('aggs', function () { return $scope.vis.getAggConfig().toDsl(); }); - - $timeout(async () => { - const visEl = $element.find('#discoverHistogram')[0]; - visualizeHandler = await visualizeLoader.embedVisualizationWithSavedObject(visEl, visSavedObject, { - autoFetch: false, - }); - }); } function resolveIndexPatternLoading() { diff --git a/src/legacy/core_plugins/kibana/public/discover/directives/_histogram.scss b/src/legacy/core_plugins/kibana/public/discover/directives/_histogram.scss new file mode 100644 index 00000000000000..948f438eea5423 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/directives/_histogram.scss @@ -0,0 +1,4 @@ +.dscHistogram__header--partial { + font-weight: $euiFontWeightRegular; + min-width: $euiSize * 12; +} diff --git a/src/legacy/core_plugins/kibana/public/discover/directives/_index.scss b/src/legacy/core_plugins/kibana/public/discover/directives/_index.scss index 8d092c11a4e1ef..c65243d99c8f49 100644 --- a/src/legacy/core_plugins/kibana/public/discover/directives/_index.scss +++ b/src/legacy/core_plugins/kibana/public/discover/directives/_index.scss @@ -1 +1,2 @@ @import 'no_results'; +@import 'histogram'; \ No newline at end of file diff --git a/src/legacy/core_plugins/kibana/public/discover/directives/histogram.tsx b/src/legacy/core_plugins/kibana/public/discover/directives/histogram.tsx new file mode 100644 index 00000000000000..e387355af69556 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/directives/histogram.tsx @@ -0,0 +1,258 @@ +/* + * 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 { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiSpacer } from '@elastic/eui'; +import moment from 'moment-timezone'; +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import lightEuiTheme from '@elastic/eui/dist/eui_theme_light.json'; +import darkEuiTheme from '@elastic/eui/dist/eui_theme_dark.json'; + +import { + AnnotationDomainTypes, + Axis, + Chart, + HistogramBarSeries, + GeometryValue, + getAnnotationId, + getAxisId, + getSpecId, + LineAnnotation, + Position, + ScaleType, + Settings, + RectAnnotation, + TooltipValue, + TooltipType, +} from '@elastic/charts'; + +import { i18n } from '@kbn/i18n'; + +import { getChartTheme } from 'ui/elastic_charts'; +import chrome from 'ui/chrome'; +// @ts-ignore: path dynamic for kibana +import { timezoneProvider } from 'ui/vis/lib/timezone'; + +export interface DiscoverHistogramProps { + chartData: any; + timefilterUpdateHandler: (ranges: { from: number; to: number }) => void; +} + +export class DiscoverHistogram extends Component { + public static propTypes = { + chartData: PropTypes.object, + timefilterUpdateHandler: PropTypes.func, + }; + + public onBrushEnd = (min: number, max: number) => { + const range = { + from: min, + to: max, + }; + + this.props.timefilterUpdateHandler(range); + }; + + public onElementClick = (xInterval: number) => (elementData: GeometryValue[]) => { + const startRange = elementData[0].x; + + const range = { + from: startRange, + to: startRange + xInterval, + }; + + this.props.timefilterUpdateHandler(range); + }; + + public formatXValue = (val: string) => { + const xAxisFormat = this.props.chartData.xAxisFormat.params.pattern; + + return moment(val).format(xAxisFormat); + }; + + public renderBarTooltip = (xInterval: number, domainStart: number, domainEnd: number) => ( + headerData: TooltipValue + ): JSX.Element | string => { + const headerDataValue = headerData.value; + const formattedValue = this.formatXValue(headerDataValue); + + const partialDataText = i18n.translate('kbn.discover.histogram.partialData.bucketTooltipText', { + defaultMessage: + 'The selected time range does not include this entire bucket, it may contain partial data.', + }); + + if (headerDataValue < domainStart || headerDataValue + xInterval > domainEnd) { + return ( + + + + + + {partialDataText} + + +

{formattedValue}

+
+ ); + } + + return formattedValue; + }; + + public render() { + const uiSettings = chrome.getUiSettingsClient(); + const timeZone = timezoneProvider(uiSettings)(); + const { chartData } = this.props; + + if (!chartData || !chartData.series[0]) { + return null; + } + + const data = chartData.series[0].values; + + /** + * Deprecation: [interval] on [date_histogram] is deprecated, use [fixed_interval] or [calendar_interval]. + * see https://github.com/elastic/kibana/issues/27410 + * TODO: Once the Discover query has been update, we should change the below to use the new field + */ + const xInterval = chartData.ordered.interval; + + const xValues = chartData.xAxisOrderedValues; + const lastXValue = xValues[xValues.length - 1]; + + const domain = chartData.ordered; + const domainStart = domain.min.valueOf(); + const domainEnd = domain.max.valueOf(); + + const domainMin = data[0].x > domainStart ? domainStart : data[0].x; + const domainMax = domainEnd - xInterval > lastXValue ? domainEnd - xInterval : lastXValue; + + const xDomain = { + min: domainMin, + max: domainMax, + minInterval: xInterval, + }; + + // Domain end of 'now' will be milliseconds behind current time, so we extend time by 1 minute and check if + // the annotation is within this range; if so, the line annotation uses the domainEnd as its value + const now = moment(); + const isAnnotationAtEdge = + moment(domainEnd) + .add(60000) + .isAfter(now) && now.isAfter(domainEnd); + const lineAnnotationValue = isAnnotationAtEdge ? domainEnd : now; + + const lineAnnotationData = [ + { + dataValue: lineAnnotationValue, + }, + ]; + const isDarkMode = uiSettings.get('theme:darkMode'); + + const lineAnnotationStyle = { + line: { + strokeWidth: 2, + stroke: isDarkMode ? darkEuiTheme.euiColorDanger : lightEuiTheme.euiColorDanger, + opacity: 0.7, + }, + }; + + const rectAnnotations = []; + if (domainStart !== domainMin) { + rectAnnotations.push({ + coordinates: { + x1: domainStart, + }, + }); + } + if (domainEnd !== domainMax) { + rectAnnotations.push({ + coordinates: { + x0: domainEnd, + }, + }); + } + + const rectAnnotationStyle = { + stroke: isDarkMode ? darkEuiTheme.euiColorLightShade : lightEuiTheme.euiColorDarkShade, + strokeWidth: 0, + opacity: isDarkMode ? 0.6 : 0.2, + fill: isDarkMode ? darkEuiTheme.euiColorLightShade : lightEuiTheme.euiColorDarkShade, + }; + + const tooltipProps = { + headerFormatter: this.renderBarTooltip(xInterval, domainStart, domainEnd), + type: TooltipType.VerticalCursor, + }; + + return ( + + + + + + + + + ); + } +} diff --git a/src/legacy/core_plugins/kibana/public/discover/directives/index.js b/src/legacy/core_plugins/kibana/public/discover/directives/index.js index d13448bbf9c8a1..a6f0ead4f73657 100644 --- a/src/legacy/core_plugins/kibana/public/discover/directives/index.js +++ b/src/legacy/core_plugins/kibana/public/discover/directives/index.js @@ -20,14 +20,10 @@ import 'ngreact'; import { wrapInI18nContext } from 'ui/i18n'; import { uiModules } from 'ui/modules'; - import { DiscoverNoResults } from './no_results'; - import { DiscoverUninitialized } from './uninitialized'; - import { DiscoverUnsupportedIndexPattern } from './unsupported_index_pattern'; - -import './timechart'; +import { DiscoverHistogram } from './histogram'; const app = uiModules.get('apps/discover', ['react']); @@ -42,3 +38,5 @@ app.directive('discoverUninitialized', reactDirective => app.directive('discoverUnsupportedIndexPattern', reactDirective => reactDirective(wrapInI18nContext(DiscoverUnsupportedIndexPattern), ['unsupportedType']) ); + +app.directive('discoverHistogram', reactDirective => reactDirective(DiscoverHistogram)); diff --git a/src/legacy/core_plugins/kibana/public/discover/directives/timechart.js b/src/legacy/core_plugins/kibana/public/discover/directives/timechart.js deleted file mode 100644 index bb4f3a227d0cba..00000000000000 --- a/src/legacy/core_plugins/kibana/public/discover/directives/timechart.js +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import VislibProvider from 'ui/vislib'; -import { uiModules } from 'ui/modules'; -uiModules - .get('apps/discover') - .directive('discoverTimechart', function (Private) { - const vislib = Private(VislibProvider); - - return { - restrict: 'E', - scope: { - data: '=' - }, - link: function ($scope, elem) { - - const init = function () { - // This elem should already have a height/width - const myChart = new vislib.Chart(elem[0], { - addLegend: false - }); - - $scope.$watch('data', function (data) { - if (data != null) { - myChart.render(data); - } - }); - }; - - // Start the directive - init(); - } - }; - }); diff --git a/src/legacy/core_plugins/kibana/public/discover/index.html b/src/legacy/core_plugins/kibana/public/discover/index.html index 980b3fefc6dd88..ae6bf340295fc7 100644 --- a/src/legacy/core_plugins/kibana/public/discover/index.html +++ b/src/legacy/core_plugins/kibana/public/discover/index.html @@ -162,11 +162,13 @@ -
-
+ ng-show="vis && rows.length !== 0" + chart-data="histogramData" + timefilter-update-handler="timefilterUpdateHandler" + watch-depth="reference" + >
{ + if (savedVis.vis.type.setup) { + return savedVis.vis.type.setup(savedVis) + .catch(() => savedVis); + } + return savedVis; + }) .catch(redirectWhenMissing({ '*': '/visualize' })); @@ -98,6 +105,13 @@ uiRoutes savedVis.id); return savedVis; }) + .then(savedVis => { + if (savedVis.vis.type.setup) { + return savedVis.vis.type.setup(savedVis) + .catch(() => savedVis); + } + return savedVis; + }) .catch(redirectWhenMissing({ 'visualization': '/visualize', 'search': '/management/kibana/objects/savedVisualizations/' + $route.current.params.id, diff --git a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js index 4da6ed16a144c1..c2a7afa1875bcf 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js +++ b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js @@ -30,7 +30,7 @@ import { SavedObjectsClientProvider } from 'ui/saved_objects'; import { VisualizeListingTable } from './visualize_listing_table'; import { NewVisModal } from '../wizard/new_vis_modal'; import { VisualizeConstants } from '../visualize_constants'; -import { setup } from '../../../../visualizations/public/np_ready/public/legacy'; +import { setup as visualizationsSetup } from '../../../../visualizations/public/legacy'; import { i18n } from '@kbn/i18n'; const app = uiModules.get('app/visualize', ['ngRoute', 'react']); @@ -46,7 +46,7 @@ export function VisualizeListingController($injector, createNewVis) { const savedObjectClient = Private(SavedObjectsClientProvider); this.visTypeRegistry = Private(VisTypesRegistryProvider); - this.visTypeAliases = setup.types.visTypeAliasRegistry.get(); + this.visTypeAliases = visualizationsSetup.types.visTypeAliasRegistry.get(); timefilter.disableAutoRefreshSelector(); timefilter.disableTimeRangeSelector(); diff --git a/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualizations.js b/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualizations.js index 5f0a2be54e0956..f65802a9dca135 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualizations.js +++ b/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualizations.js @@ -22,7 +22,7 @@ import { VisTypesRegistryProvider } from 'ui/registry/vis_types'; import { uiModules } from 'ui/modules'; import { SavedObjectLoader, SavedObjectsClientProvider } from 'ui/saved_objects'; import { savedObjectManagementRegistry } from '../../management/saved_object_registry'; -import { setup } from '../../../../visualizations/public/np_ready/public/legacy'; +import { setup as visualizationsSetup } from '../../../../visualizations/public/legacy'; import { createVisualizeEditUrl } from '../visualize_constants'; import { findListItems } from './find_list_items'; @@ -86,7 +86,7 @@ app.service('savedVisualizations', function (SavedVis, Private, kbnUrl, chrome) size, mapSavedObjectApiHits: this.mapSavedObjectApiHits.bind(this), savedObjectsClient: this.savedObjectsClient, - visTypes: setup.types.visTypeAliasRegistry.get(), + visTypes: visualizationsSetup.types.visTypeAliasRegistry.get(), }); }; diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx b/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx index f7bb0a8f457310..52815f6a0737e6 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx @@ -24,7 +24,7 @@ import { i18n } from '@kbn/i18n'; import chrome from 'ui/chrome'; import { VisType } from 'ui/vis'; -import { VisTypeAlias } from '../../../../visualizations/public/np_ready/public'; +import { VisTypeAlias } from '../../../../visualizations/public'; import { VisualizeConstants } from '../visualize_constants'; import { SearchSelection } from './search_selection'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/type_selection.tsx b/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/type_selection.tsx index 76fc84921627a8..43df0ba92ffe8d 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/type_selection.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/type_selection.tsx @@ -37,7 +37,7 @@ import { } from '@elastic/eui'; import { memoizeLast } from 'ui/utils/memoize'; import { VisType } from 'ui/vis'; -import { VisTypeAlias } from '../../../../../visualizations/public/np_ready/public'; +import { VisTypeAlias } from '../../../../../visualizations/public'; import { NewVisHelp } from './new_vis_help'; import { VisHelpText } from './vis_help_text'; import { VisTypeIcon } from './vis_type_icon'; diff --git a/src/legacy/core_plugins/region_map/public/__tests__/region_map_visualization.js b/src/legacy/core_plugins/region_map/public/__tests__/region_map_visualization.js index b0b40f8ff704e5..2d70c2120d5f73 100644 --- a/src/legacy/core_plugins/region_map/public/__tests__/region_map_visualization.js +++ b/src/legacy/core_plugins/region_map/public/__tests__/region_map_visualization.js @@ -39,7 +39,7 @@ import afterdatachangePng from './afterdatachange.png'; import afterdatachangeandresizePng from './afterdatachangeandresize.png'; import aftercolorchangePng from './aftercolorchange.png'; import changestartupPng from './changestartup.png'; -import { setup } from '../../../visualizations/public/np_ready/public/legacy'; +import { setup as visualizationsSetup } from '../../../visualizations/public/legacy'; import { createRegionMapVisualization } from '../region_map_visualization'; import { createRegionMapTypeDefinition } from '../region_map_type'; @@ -106,7 +106,7 @@ describe('RegionMapsVisualizationTests', function () { uiSettings, }; - setup.types.registerVisualization(() => createRegionMapTypeDefinition(dependencies)); + visualizationsSetup.types.registerVisualization(() => createRegionMapTypeDefinition(dependencies)); Vis = Private(visModule.VisProvider); RegionMapsVisualization = createRegionMapVisualization(dependencies); diff --git a/src/legacy/core_plugins/region_map/public/components/region_map_options.tsx b/src/legacy/core_plugins/region_map/public/components/region_map_options.tsx index c23e8386e50628..56742eaec52035 100644 --- a/src/legacy/core_plugins/region_map/public/components/region_map_options.tsx +++ b/src/legacy/core_plugins/region_map/public/components/region_map_options.tsx @@ -17,12 +17,11 @@ * under the License. */ -import React, { useEffect, useState, useCallback, useMemo } from 'react'; +import React, { useCallback, useMemo } from 'react'; import { EuiIcon, EuiLink, EuiPanel, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { toastNotifications } from 'ui/notify'; import { FileLayerField, VectorLayer, ServiceSettings } from 'ui/vis/map/service_settings'; import { VisOptionsProps } from 'ui/vis/editors/default'; import { @@ -30,11 +29,8 @@ import { SelectOption, SwitchOption, } from '../../../kbn_vislib_vis_types/public/components'; -import { ORIGIN } from '../../../tile_map/common/origin'; import { WmsOptions } from '../../../tile_map/public/components/wms_options'; -import { mapToLayerWithId } from '../util'; import { RegionMapVisParams } from '../types'; -import { RegionMapsConfig } from '../plugin'; const mapLayerForOption = ({ layerId, name }: VectorLayer) => ({ text: name, @@ -48,22 +44,18 @@ const mapFieldForOption = ({ description, name }: FileLayerField) => ({ export type RegionMapOptionsProps = { serviceSettings: ServiceSettings; - includeElasticMapsService: RegionMapsConfig['includeElasticMapsService']; } & VisOptionsProps; function RegionMapOptions(props: RegionMapOptionsProps) { - const { includeElasticMapsService, serviceSettings, stateParams, vis, setValue } = props; - const [vectorLayers, setVectorLayers] = useState( - vis.type.editorConfig.collections.vectorLayers - ); - const [vectorLayerOptions, setVectorLayerOptions] = useState(vectorLayers.map(mapLayerForOption)); - const currentLayerId = stateParams.selectedLayer && stateParams.selectedLayer.layerId; + const { serviceSettings, stateParams, vis, setValue } = props; + const { vectorLayers } = vis.type.editorConfig.collections; + const vectorLayerOptions = useMemo(() => vectorLayers.map(mapLayerForOption), [vectorLayers]); const fieldOptions = useMemo( () => ((stateParams.selectedLayer && stateParams.selectedLayer.fields) || []).map( mapFieldForOption ), - [currentLayerId] + [stateParams.selectedLayer] ); const setEmsHotLink = useCallback( @@ -71,7 +63,7 @@ function RegionMapOptions(props: RegionMapOptionsProps) { const emsHotLink = await serviceSettings.getEMSHotLink(layer); setValue('emsHotLink', emsHotLink); }, - [setValue] + [setValue, serviceSettings] ); const setLayer = useCallback( @@ -84,7 +76,7 @@ function RegionMapOptions(props: RegionMapOptionsProps) { setEmsHotLink(newLayer); } }, - [vectorLayers, setValue] + [vectorLayers, setEmsHotLink, setValue] ); const setField = useCallback( @@ -93,53 +85,9 @@ function RegionMapOptions(props: RegionMapOptionsProps) { setValue(paramName, stateParams.selectedLayer.fields.find(f => f.name === value)); } }, - [currentLayerId, setValue] + [setValue, stateParams.selectedLayer] ); - useEffect(() => { - async function setDefaultValues() { - try { - const layers = await serviceSettings.getFileLayers(); - const newLayers = layers - .map(mapToLayerWithId.bind(null, ORIGIN.EMS)) - .filter( - layer => !vectorLayers.some(vectorLayer => vectorLayer.layerId === layer.layerId) - ); - - // backfill v1 manifest for now - newLayers.forEach(layer => { - if (layer.format === 'geojson') { - layer.format = { - type: 'geojson', - }; - } - }); - - const newVectorLayers = [...vectorLayers, ...newLayers]; - - setVectorLayers(newVectorLayers); - setVectorLayerOptions(newVectorLayers.map(mapLayerForOption)); - - const [newLayer] = newVectorLayers; - - if (newLayer && !stateParams.selectedLayer) { - setValue('selectedLayer', newLayer); - setValue('selectedJoinField', newLayer.fields[0]); - - if (newLayer.isEMS) { - setEmsHotLink(newLayer); - } - } - } catch (error) { - toastNotifications.addWarning(error.message); - } - } - - if (includeElasticMapsService) { - setDefaultValues(); - } - }, []); - return ( <> diff --git a/src/legacy/core_plugins/region_map/public/legacy.ts b/src/legacy/core_plugins/region_map/public/legacy.ts index 7adbc2117d7ee5..b669a45e3240a5 100644 --- a/src/legacy/core_plugins/region_map/public/legacy.ts +++ b/src/legacy/core_plugins/region_map/public/legacy.ts @@ -20,7 +20,7 @@ import { PluginInitializerContext } from 'kibana/public'; import { npSetup, npStart } from 'ui/new_platform'; -import { setup as setupVisualizations } from '../../visualizations/public/np_ready/public/legacy'; +import { setup as visualizationsSetup } from '../../visualizations/public/legacy'; import { RegionMapPluginSetupDependencies, RegionMapsConfig } from './plugin'; import { LegacyDependenciesPlugin } from './shim'; import { plugin } from '.'; @@ -30,7 +30,7 @@ const regionmapsConfig = npSetup.core.injectedMetadata.getInjectedVar( ) as RegionMapsConfig; const plugins: Readonly = { - visualizations: setupVisualizations, + visualizations: visualizationsSetup, data: npSetup.plugins.data, // Temporary solution diff --git a/src/legacy/core_plugins/region_map/public/plugin.ts b/src/legacy/core_plugins/region_map/public/plugin.ts index 4afee7269c5780..158f548b935dfe 100644 --- a/src/legacy/core_plugins/region_map/public/plugin.ts +++ b/src/legacy/core_plugins/region_map/public/plugin.ts @@ -24,7 +24,7 @@ import { UiSettingsClientContract, } from '../../../../core/public'; import { Plugin as DataPublicPlugin } from '../../../../plugins/data/public'; -import { VisualizationsSetup } from '../../visualizations/public/np_ready/public'; +import { VisualizationsSetup } from '../../visualizations/public'; import { LegacyDependenciesPlugin, LegacyDependenciesPluginSetup } from './shim'; diff --git a/src/legacy/core_plugins/region_map/public/region_map_type.js b/src/legacy/core_plugins/region_map/public/region_map_type.js index 9b28aa96eccbc7..3a28277f9f4c7d 100644 --- a/src/legacy/core_plugins/region_map/public/region_map_type.js +++ b/src/legacy/core_plugins/region_map/public/region_map_type.js @@ -25,7 +25,7 @@ import { createRegionMapVisualization } from './region_map_visualization'; import { Status } from 'ui/vis/update_status'; import { RegionMapOptions } from './components/region_map_options'; -import { visFactory } from '../../visualizations/public/np_ready/public'; +import { visFactory } from '../../visualizations/public'; // TODO: reference to TILE_MAP plugin should be removed import { ORIGIN } from '../../tile_map/common/origin'; @@ -33,9 +33,6 @@ import { ORIGIN } from '../../tile_map/common/origin'; export function createRegionMapTypeDefinition(dependencies) { const { uiSettings, regionmapsConfig, serviceSettings } = dependencies; const visualization = createRegionMapVisualization(dependencies); - const vectorLayers = regionmapsConfig.layers.map(mapToLayerWithId.bind(null, ORIGIN.KIBANA_YML)); - const selectedLayer = vectorLayers[0]; - const selectedJoinField = selectedLayer ? selectedLayer.fields[0] : null; return visFactory.createBaseVisualization({ name: 'region_map', @@ -52,8 +49,6 @@ provided base maps, or add your own. Darker colors represent higher values.', addTooltip: true, colorSchema: 'Yellow to Red', emsHotLink: '', - selectedLayer, - selectedJoinField, isDisplayWarning: true, wms: uiSettings.get('visualization:tileMap:WMSdefaults'), mapZoom: 2, @@ -69,12 +64,10 @@ provided base maps, or add your own. Darker colors represent higher values.', - ), + />), collections: { colorSchemas, - vectorLayers, + vectorLayers: [], tmsLayers: [], }, schemas: new Schemas([ @@ -113,5 +106,54 @@ provided base maps, or add your own. Darker colors represent higher values.', }, ]), }, + setup: async (savedVis) => { + const vis = savedVis.vis; + + const tmsLayers = await serviceSettings.getTMSServices(); + vis.type.editorConfig.collections.tmsLayers = tmsLayers; + if (!vis.params.wms.selectedTmsLayer && tmsLayers.length) { + vis.params.wms.selectedTmsLayer = tmsLayers[0]; + } + + const vectorLayers = regionmapsConfig.layers.map( + mapToLayerWithId.bind(null, ORIGIN.KIBANA_YML) + ); + let selectedLayer = vectorLayers[0]; + let selectedJoinField = selectedLayer ? selectedLayer.fields[0] : null; + if (regionmapsConfig.includeElasticMapsService) { + const layers = await serviceSettings.getFileLayers(); + const newLayers = layers + .map(mapToLayerWithId.bind(null, ORIGIN.EMS)) + .filter( + (layer) => + !vectorLayers.some(vectorLayer => vectorLayer.layerId === layer.layerId) + ); + + // backfill v1 manifest for now + newLayers.forEach((layer) => { + if (layer.format === 'geojson') { + layer.format = { + type: 'geojson', + }; + } + }); + + vis.type.editorConfig.collections.vectorLayers = [...vectorLayers, ...newLayers]; + + [selectedLayer] = vis.type.editorConfig.collections.vectorLayers; + selectedJoinField = selectedLayer ? selectedLayer.fields[0] : null; + + if (selectedLayer && !vis.params.selectedLayer && selectedLayer.isEMS) { + vis.params.emsHotLink = await serviceSettings.getEMSHotLink(selectedLayer); + } + } + + if (!vis.params.selectedLayer) { + vis.params.selectedLayer = selectedLayer; + vis.params.selectedJoinField = selectedJoinField; + } + + return savedVis; + }, }); } diff --git a/src/legacy/core_plugins/tile_map/public/__tests__/coordinate_maps_visualization.js b/src/legacy/core_plugins/tile_map/public/__tests__/coordinate_maps_visualization.js index e32df04a583c2d..17162278b1e208 100644 --- a/src/legacy/core_plugins/tile_map/public/__tests__/coordinate_maps_visualization.js +++ b/src/legacy/core_plugins/tile_map/public/__tests__/coordinate_maps_visualization.js @@ -33,7 +33,7 @@ import EMS_TILES from '../../../../ui/public/vis/__tests__/map/ems_mocks/sample_ import EMS_STYLE_ROAD_MAP_BRIGHT from '../../../../ui/public/vis/__tests__/map/ems_mocks/sample_style_bright'; import EMS_STYLE_ROAD_MAP_DESATURATED from '../../../../ui/public/vis/__tests__/map/ems_mocks/sample_style_desaturated'; import EMS_STYLE_DARK_MAP from '../../../../ui/public/vis/__tests__/map/ems_mocks/sample_style_dark'; -import { setup } from '../../../visualizations/public/np_ready/public/legacy'; +import { setup as visualizationsSetup } from '../../../visualizations/public/legacy'; import { createTileMapVisualization } from '../tile_map_visualization'; import { createTileMapTypeDefinition } from '../tile_map_type'; @@ -84,7 +84,7 @@ describe('CoordinateMapsVisualizationTest', function () { $injector, }; - setup.types.registerVisualization(() => createTileMapTypeDefinition(dependencies)); + visualizationsSetup.types.registerVisualization(() => createTileMapTypeDefinition(dependencies)); Vis = Private(visModule.VisProvider); CoordinateMapsVisualization = createTileMapVisualization(dependencies); diff --git a/src/legacy/core_plugins/tile_map/public/components/tile_map_options.tsx b/src/legacy/core_plugins/tile_map/public/components/tile_map_options.tsx index 096266502417d8..a3ebda8b3df0cb 100644 --- a/src/legacy/core_plugins/tile_map/public/components/tile_map_options.tsx +++ b/src/legacy/core_plugins/tile_map/public/components/tile_map_options.tsx @@ -21,7 +21,6 @@ import React, { useEffect } from 'react'; import { EuiPanel, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { ServiceSettings } from 'ui/vis/map/service_settings'; import { VisOptionsProps } from 'ui/vis/editors/default'; import { BasicOptions, @@ -33,9 +32,7 @@ import { WmsOptions } from './wms_options'; import { TileMapVisParams } from '../types'; import { MapTypes } from '../map_types'; -export type TileMapOptionsProps = { serviceSettings: ServiceSettings } & VisOptionsProps< - TileMapVisParams ->; +export type TileMapOptionsProps = VisOptionsProps; function TileMapOptions(props: TileMapOptionsProps) { const { stateParams, setValue, vis } = props; diff --git a/src/legacy/core_plugins/tile_map/public/components/wms_options.tsx b/src/legacy/core_plugins/tile_map/public/components/wms_options.tsx index 42cc2776fcae41..ef6b2eaea1e521 100644 --- a/src/legacy/core_plugins/tile_map/public/components/wms_options.tsx +++ b/src/legacy/core_plugins/tile_map/public/components/wms_options.tsx @@ -17,12 +17,11 @@ * under the License. */ -import React, { useEffect, useState } from 'react'; +import React, { useMemo } from 'react'; import { EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { toastNotifications } from 'ui/notify'; import { TmsLayer } from 'ui/vis/map/service_settings'; import { SelectOption, SwitchOption } from '../../../kbn_vislib_vis_types/public/components'; import { RegionMapOptionsProps } from '../../../region_map/public/components/region_map_options'; @@ -32,18 +31,10 @@ import { TileMapVisParams } from '../types'; const mapLayerForOption = ({ id }: TmsLayer) => ({ text: id, value: id }); -function WmsOptions({ - serviceSettings, - stateParams, - setValue, - vis, -}: TileMapOptionsProps | RegionMapOptionsProps) { +function WmsOptions({ stateParams, setValue, vis }: TileMapOptionsProps | RegionMapOptionsProps) { const { wms } = stateParams; const { tmsLayers } = vis.type.editorConfig.collections; - const [tmsLayerOptions, setTmsLayersOptions] = useState>( - tmsLayers.map(mapLayerForOption) - ); - const [layers, setLayers] = useState([]); + const tmsLayerOptions = useMemo(() => tmsLayers.map(mapLayerForOption), [tmsLayers]); const setWmsOption = ( paramName: T, @@ -55,31 +46,12 @@ function WmsOptions({ }); const selectTmsLayer = (id: string) => { - const layer = layers.find(l => l.id === id); + const layer = tmsLayers.find((l: TmsLayer) => l.id === id); if (layer) { setWmsOption('selectedTmsLayer', layer); } }; - useEffect(() => { - serviceSettings - .getTMSServices() - .then(services => { - const newBaseLayers: TmsLayer[] = [ - ...tmsLayers, - ...services.filter(service => !tmsLayers.some(({ id }: TmsLayer) => service.id === id)), - ]; - - setLayers(newBaseLayers); - setTmsLayersOptions(newBaseLayers.map(mapLayerForOption)); - - if (!wms.selectedTmsLayer && newBaseLayers.length) { - setWmsOption('selectedTmsLayer', newBaseLayers[0]); - } - }) - .catch((error: Error) => toastNotifications.addWarning(error.message)); - }, []); - return ( diff --git a/src/legacy/core_plugins/tile_map/public/legacy.ts b/src/legacy/core_plugins/tile_map/public/legacy.ts index cf2e702cd09b64..185dac90bf8a04 100644 --- a/src/legacy/core_plugins/tile_map/public/legacy.ts +++ b/src/legacy/core_plugins/tile_map/public/legacy.ts @@ -20,13 +20,13 @@ import { PluginInitializerContext } from 'kibana/public'; import { npSetup, npStart } from 'ui/new_platform'; -import { setup as setupVisualizations } from '../../visualizations/public/np_ready/public/legacy'; +import { setup as visualizationsSetup } from '../../visualizations/public/legacy'; import { TileMapPluginSetupDependencies } from './plugin'; import { LegacyDependenciesPlugin } from './shim'; import { plugin } from '.'; const plugins: Readonly = { - visualizations: setupVisualizations, + visualizations: visualizationsSetup, data: npSetup.plugins.data, // Temporary solution diff --git a/src/legacy/core_plugins/tile_map/public/plugin.ts b/src/legacy/core_plugins/tile_map/public/plugin.ts index 1309c37a4c73a6..ba087d5ca5959b 100644 --- a/src/legacy/core_plugins/tile_map/public/plugin.ts +++ b/src/legacy/core_plugins/tile_map/public/plugin.ts @@ -24,7 +24,7 @@ import { UiSettingsClientContract, } from '../../../../core/public'; import { Plugin as DataPublicPlugin } from '../../../../plugins/data/public'; -import { VisualizationsSetup } from '../../visualizations/public/np_ready/public'; +import { VisualizationsSetup } from '../../visualizations/public'; import { LegacyDependenciesPlugin, LegacyDependenciesPluginSetup } from './shim'; diff --git a/src/legacy/core_plugins/tile_map/public/tile_map_type.js b/src/legacy/core_plugins/tile_map/public/tile_map_type.js index d96d8ad9be7865..243b4c2bf7765a 100644 --- a/src/legacy/core_plugins/tile_map/public/tile_map_type.js +++ b/src/legacy/core_plugins/tile_map/public/tile_map_type.js @@ -27,7 +27,7 @@ import { colorSchemas } from 'ui/vislib/components/color/truncated_colormaps'; import { convertToGeoJson } from 'ui/vis/map/convert_to_geojson'; import { createTileMapVisualization } from './tile_map_visualization'; -import { visFactory } from '../../visualizations/public/np_ready/public'; +import { visFactory } from '../../visualizations/public'; import { TileMapOptions } from './components/tile_map_options'; import { MapTypes } from './map_types'; @@ -119,7 +119,7 @@ export function createTileMapTypeDefinition(dependencies) { ], tmsLayers: [], }, - optionsTemplate: props => , + optionsTemplate: (props) => , schemas: new Schemas([ { group: 'metrics', @@ -144,5 +144,21 @@ export function createTileMapTypeDefinition(dependencies) { }, ]), }, + setup: async (savedVis) => { + const vis = savedVis.vis; + let tmsLayers; + + try { + tmsLayers = await serviceSettings.getTMSServices(); + } catch (e) { + return savedVis; + } + + vis.type.editorConfig.collections.tmsLayers = tmsLayers; + if (!vis.params.wms.selectedTmsLayer && tmsLayers.length) { + vis.params.wms.selectedTmsLayer = tmsLayers[0]; + } + return savedVis; + }, }); } diff --git a/src/legacy/core_plugins/vis_type_markdown/public/legacy.ts b/src/legacy/core_plugins/vis_type_markdown/public/legacy.ts index 539b4e1bdfb456..2552e04fbc9f2a 100644 --- a/src/legacy/core_plugins/vis_type_markdown/public/legacy.ts +++ b/src/legacy/core_plugins/vis_type_markdown/public/legacy.ts @@ -20,12 +20,12 @@ import { PluginInitializerContext } from 'kibana/public'; import { npSetup, npStart } from 'ui/new_platform'; -import { setup as setupVisualizations } from '../../visualizations/public/np_ready/public/legacy'; +import { setup as visualizationsSetup } from '../../visualizations/public/legacy'; import { MarkdownPluginSetupDependencies } from './plugin'; import { plugin } from '.'; const plugins: Readonly = { - visualizations: setupVisualizations, + visualizations: visualizationsSetup, data: npSetup.plugins.data, }; diff --git a/src/legacy/core_plugins/vis_type_markdown/public/markdown_vis.ts b/src/legacy/core_plugins/vis_type_markdown/public/markdown_vis.ts index 2f1a46f1d8f3f9..7b2f8f6c236b29 100644 --- a/src/legacy/core_plugins/vis_type_markdown/public/markdown_vis.ts +++ b/src/legacy/core_plugins/vis_type_markdown/public/markdown_vis.ts @@ -19,7 +19,7 @@ import { i18n } from '@kbn/i18n'; -import { visFactory, DefaultEditorSize } from '../../visualizations/public/np_ready/public'; +import { visFactory, DefaultEditorSize } from '../../visualizations/public'; import { MarkdownVisWrapper } from './markdown_vis_controller'; import { MarkdownOptions } from './markdown_options'; diff --git a/src/legacy/core_plugins/vis_type_markdown/public/plugin.ts b/src/legacy/core_plugins/vis_type_markdown/public/plugin.ts index 5f9c82ca89290a..b013c755168d79 100644 --- a/src/legacy/core_plugins/vis_type_markdown/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_markdown/public/plugin.ts @@ -19,7 +19,7 @@ import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../../../core/public'; import { Plugin as DataPublicPlugin } from '../../../../plugins/data/public'; -import { VisualizationsSetup } from '../../visualizations/public/np_ready/public'; +import { VisualizationsSetup } from '../../visualizations/public'; import { markdownVis } from './markdown_vis'; import { createMarkdownVisFn } from './markdown_fn'; diff --git a/src/legacy/core_plugins/vis_type_metric/public/__tests__/metric_vis.js b/src/legacy/core_plugins/vis_type_metric/public/__tests__/metric_vis.js index ce77c64d776513..f5fb6312d2b0ac 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/__tests__/metric_vis.js +++ b/src/legacy/core_plugins/vis_type_metric/public/__tests__/metric_vis.js @@ -24,7 +24,7 @@ import expect from '@kbn/expect'; import { VisProvider } from 'ui/vis'; import LogstashIndexPatternStubProvider from 'fixtures/stubbed_logstash_index_pattern'; -import { setup as setupVisualizations } from '../../../visualizations/public/np_ready/public/legacy'; +import { setup as visualizationsSetup } from '../../../visualizations/public/legacy'; import { createMetricVisTypeDefinition } from '../metric_vis_type'; describe('metric_vis - createMetricVisTypeDefinition', () => { @@ -38,7 +38,7 @@ describe('metric_vis - createMetricVisTypeDefinition', () => { const Vis = Private(VisProvider); const metricVisType = createMetricVisTypeDefinition(); - setupVisualizations.types.registerVisualization(() => metricVisType); + visualizationsSetup.types.registerVisualization(() => metricVisType); const indexPattern = Private(LogstashIndexPatternStubProvider); diff --git a/src/legacy/core_plugins/vis_type_metric/public/legacy.ts b/src/legacy/core_plugins/vis_type_metric/public/legacy.ts index 2eea4c70309de1..2ded1a05a8ec49 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/legacy.ts +++ b/src/legacy/core_plugins/vis_type_metric/public/legacy.ts @@ -20,13 +20,13 @@ import { PluginInitializerContext } from 'kibana/public'; import { npSetup, npStart } from 'ui/new_platform'; -import { setup as setupVisualizations } from '../../visualizations/public/np_ready/public/legacy'; +import { setup as visualizationsSetup } from '../../visualizations/public/legacy'; import { MetricVisPluginSetupDependencies } from './plugin'; import { LegacyDependenciesPlugin } from './shim'; import { plugin } from '.'; const plugins: Readonly = { - visualizations: setupVisualizations, + visualizations: visualizationsSetup, data: npSetup.plugins.data, // Temporary solution diff --git a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.ts b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.ts index eba61edeeb84a3..76e88888ef7f2c 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.ts +++ b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.ts @@ -26,7 +26,7 @@ import { vislibColorMaps } from 'ui/vislib/components/color/colormaps'; // @ts-ignore import { MetricVisComponent } from './components/metric_vis_controller'; -import { visFactory } from '../../visualizations/public/np_ready/public'; +import { visFactory } from '../../visualizations/public'; export const createMetricVisTypeDefinition = () => { return visFactory.createReactVisualization({ diff --git a/src/legacy/core_plugins/vis_type_metric/public/plugin.ts b/src/legacy/core_plugins/vis_type_metric/public/plugin.ts index 66a3bb007e379b..b8c6001d1f02e5 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_metric/public/plugin.ts @@ -20,7 +20,7 @@ import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../../../core/public'; import { LegacyDependenciesPlugin } from './shim'; import { Plugin as DataPublicPlugin } from '../../../../plugins/data/public'; -import { VisualizationsSetup } from '../../visualizations/public/np_ready/public'; +import { VisualizationsSetup } from '../../visualizations/public'; import { createMetricVisFn } from './metric_vis_fn'; // @ts-ignore diff --git a/src/legacy/core_plugins/vis_type_table/public/__tests__/table_vis_controller.js b/src/legacy/core_plugins/vis_type_table/public/__tests__/table_vis_controller.js index 9110c8dcb84864..f04c953f070ae4 100644 --- a/src/legacy/core_plugins/vis_type_table/public/__tests__/table_vis_controller.js +++ b/src/legacy/core_plugins/vis_type_table/public/__tests__/table_vis_controller.js @@ -28,7 +28,7 @@ import { AppStateProvider } from 'ui/state_management/app_state'; import { tabifyAggResponse } from 'ui/agg_response/tabify'; import { createTableVisTypeDefinition } from '../table_vis_type'; -import { setup as setupVisualizations } from '../../../visualizations/public/np_ready/public/legacy'; +import { setup as visualizationsSetup } from '../../../visualizations/public/legacy'; describe('Table Vis - Controller', async function () { let $rootScope; @@ -52,7 +52,7 @@ describe('Table Vis - Controller', async function () { createAngularVisualization: VisFactoryProvider(Private).createAngularVisualization, }; - setupVisualizations.types.registerVisualization(() => + visualizationsSetup.types.registerVisualization(() => createTableVisTypeDefinition(legacyDependencies) ); diff --git a/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table.js b/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table.js index 8521ee729f313d..323201ded73a3d 100644 --- a/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table.js +++ b/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table.js @@ -31,7 +31,7 @@ import { round } from 'lodash'; import { VisFactoryProvider } from 'ui/vis/vis_factory'; import { createTableVisTypeDefinition } from '../../table_vis_type'; -import { setup } from '../../../../visualizations/public/np_ready/public/legacy'; +import { setup as visualizationsSetup } from '../../../../visualizations/public/legacy'; describe('Table Vis - AggTable Directive', function () { let $rootScope; @@ -107,7 +107,7 @@ describe('Table Vis - AggTable Directive', function () { createAngularVisualization: VisFactoryProvider(Private).createAngularVisualization, }; - setup.types.registerVisualization(() => createTableVisTypeDefinition(legacyDependencies)); + visualizationsSetup.types.registerVisualization(() => createTableVisTypeDefinition(legacyDependencies)); tableAggResponse = legacyResponseHandlerProvider().handler; indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); diff --git a/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table_group.js b/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table_group.js index 7998a92a4759fa..77a0ee5221b956 100644 --- a/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table_group.js +++ b/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table_group.js @@ -28,7 +28,7 @@ import { tabifyAggResponse } from 'ui/agg_response/tabify'; import { VisFactoryProvider } from 'ui/vis/vis_factory'; import { createTableVisTypeDefinition } from '../../table_vis_type'; -import { setup } from '../../../../visualizations/public/np_ready/public/legacy'; +import { setup as visualizationsSetup } from '../../../../visualizations/public/legacy'; describe('Table Vis - AggTableGroup Directive', function () { let $rootScope; @@ -66,7 +66,7 @@ describe('Table Vis - AggTableGroup Directive', function () { createAngularVisualization: VisFactoryProvider(Private).createAngularVisualization, }; - setup.types.registerVisualization(() => createTableVisTypeDefinition(legacyDependencies)); + visualizationsSetup.types.registerVisualization(() => createTableVisTypeDefinition(legacyDependencies)); tableAggResponse = legacyResponseHandlerProvider().handler; indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); diff --git a/src/legacy/core_plugins/vis_type_table/public/legacy.ts b/src/legacy/core_plugins/vis_type_table/public/legacy.ts index 8139a70552c48a..b6d2a4a152926d 100644 --- a/src/legacy/core_plugins/vis_type_table/public/legacy.ts +++ b/src/legacy/core_plugins/vis_type_table/public/legacy.ts @@ -22,11 +22,11 @@ import { npSetup, npStart } from 'ui/new_platform'; import { plugin } from '.'; import { TablePluginSetupDependencies } from './plugin'; -import { setup as setupVisualizations } from '../../visualizations/public/np_ready/public/legacy'; +import { setup as visualizationsSetup } from '../../visualizations/public/legacy'; import { LegacyDependenciesPlugin } from './shim'; const plugins: Readonly = { - visualizations: setupVisualizations, + visualizations: visualizationsSetup, data: npSetup.plugins.data, // Temporary solution diff --git a/src/legacy/core_plugins/vis_type_table/public/plugin.ts b/src/legacy/core_plugins/vis_type_table/public/plugin.ts index 6a39e8079a9fd2..23f6061ad8f93f 100644 --- a/src/legacy/core_plugins/vis_type_table/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_table/public/plugin.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { VisualizationsSetup } from '../../visualizations/public/np_ready/public'; +import { VisualizationsSetup } from '../../visualizations/public'; import { Plugin as DataPublicPlugin } from '../../../../plugins/data/public'; import { diff --git a/src/legacy/core_plugins/vis_type_tagcloud/public/legacy.ts b/src/legacy/core_plugins/vis_type_tagcloud/public/legacy.ts index 6a5e06b6e6978d..dd2c21fe6477aa 100644 --- a/src/legacy/core_plugins/vis_type_tagcloud/public/legacy.ts +++ b/src/legacy/core_plugins/vis_type_tagcloud/public/legacy.ts @@ -20,12 +20,12 @@ import { PluginInitializerContext } from 'kibana/public'; import { npSetup, npStart } from 'ui/new_platform'; -import { setup as setupVisualizations } from '../../visualizations/public/np_ready/public/legacy'; +import { setup as visualizationsSetup } from '../../visualizations/public/legacy'; import { TagCloudPluginSetupDependencies } from './plugin'; import { plugin } from '.'; const plugins: Readonly = { - visualizations: setupVisualizations, + visualizations: visualizationsSetup, data: npSetup.plugins.data, }; diff --git a/src/legacy/core_plugins/vis_type_tagcloud/public/plugin.ts b/src/legacy/core_plugins/vis_type_tagcloud/public/plugin.ts index e13e9896e39403..3947521a9868ee 100644 --- a/src/legacy/core_plugins/vis_type_tagcloud/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_tagcloud/public/plugin.ts @@ -19,7 +19,7 @@ import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../../../core/public'; import { Plugin as DataPublicPlugin } from '../../../../plugins/data/public'; -import { VisualizationsSetup } from '../../visualizations/public/np_ready/public'; +import { VisualizationsSetup } from '../../visualizations/public'; import { createTagCloudFn } from './tag_cloud_fn'; import { createTagCloudTypeDefinition } from './tag_cloud_type'; diff --git a/src/legacy/core_plugins/vis_type_tagcloud/public/tag_cloud_type.ts b/src/legacy/core_plugins/vis_type_tagcloud/public/tag_cloud_type.ts index 421821d93b045b..0b4d90522cc448 100644 --- a/src/legacy/core_plugins/vis_type_tagcloud/public/tag_cloud_type.ts +++ b/src/legacy/core_plugins/vis_type_tagcloud/public/tag_cloud_type.ts @@ -23,7 +23,7 @@ import { Status } from 'ui/vis/update_status'; import { Schemas } from 'ui/vis/editors/default/schemas'; import { TagCloudOptions } from './components/tag_cloud_options'; -import { visFactory } from '../../visualizations/public/np_ready/public'; +import { visFactory } from '../../visualizations/public'; // @ts-ignore import { TagCloudVisualization } from './components/tag_cloud_visualization'; diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/legacy.ts b/src/legacy/core_plugins/vis_type_timeseries/public/legacy.ts index 8dc24503772dbb..79cfb1b164f0dd 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/legacy.ts +++ b/src/legacy/core_plugins/vis_type_timeseries/public/legacy.ts @@ -20,12 +20,12 @@ import { PluginInitializerContext } from 'kibana/public'; import { npSetup, npStart } from 'ui/new_platform'; -import { setup as setupVisualizations } from '../../visualizations/public/np_ready/public/legacy'; +import { setup as visualizationsSetup } from '../../visualizations/public/legacy'; import { MetricsPluginSetupDependencies } from './plugin'; import { plugin } from '.'; const plugins: Readonly = { - visualizations: setupVisualizations, + visualizations: visualizationsSetup, data: npSetup.plugins.data, }; diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/metrics_type.ts b/src/legacy/core_plugins/vis_type_timeseries/public/metrics_type.ts index fd00f95b1e00f0..b2f9269f852f69 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/metrics_type.ts +++ b/src/legacy/core_plugins/vis_type_timeseries/public/metrics_type.ts @@ -22,7 +22,7 @@ import { i18n } from '@kbn/i18n'; // @ts-ignore import { defaultFeedbackMessage } from 'ui/vis/default_feedback_message'; -import { visFactory } from '../../visualizations/public/np_ready/public'; +import { visFactory } from '../../visualizations/public'; // @ts-ignore import { createMetricsRequestHandler } from './request_handler'; diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/plugin.ts b/src/legacy/core_plugins/vis_type_timeseries/public/plugin.ts index 5c103a3ae4b084..673bd202b70f2d 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_timeseries/public/plugin.ts @@ -18,7 +18,7 @@ */ import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../../../core/public'; import { Plugin as DataPublicPlugin } from '../../../../plugins/data/public'; -import { VisualizationsSetup } from '../../visualizations/public/np_ready/public'; +import { VisualizationsSetup } from '../../visualizations/public'; import { createMetricsFn } from './metrics_fn'; import { createMetricsTypeDefinition } from './metrics_type'; diff --git a/src/legacy/core_plugins/vis_type_vega/public/__tests__/vega_visualization.js b/src/legacy/core_plugins/vis_type_vega/public/__tests__/vega_visualization.js index 012f144983e989..9c46dfb9451fa3 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/__tests__/vega_visualization.js +++ b/src/legacy/core_plugins/vis_type_vega/public/__tests__/vega_visualization.js @@ -41,7 +41,7 @@ import vegaMapImage256 from './vega_map_image_256.png'; import { VegaParser } from '../data_model/vega_parser'; import { SearchCache } from '../data_model/search_cache'; -import { setup } from '../../../visualizations/public/np_ready/public/legacy'; +import { setup as visualizationsSetup } from '../../../visualizations/public/legacy'; import { createVegaTypeDefinition } from '../vega_type'; const THRESHOLD = 0.1; @@ -65,7 +65,7 @@ describe('VegaVisualizations', () => { uiSettings: $injector.get('config'), }; - setup.types.registerVisualization(() => + visualizationsSetup.types.registerVisualization(() => createVegaTypeDefinition(vegaVisualizationDependencies) ); diff --git a/src/legacy/core_plugins/vis_type_vega/public/legacy.ts b/src/legacy/core_plugins/vis_type_vega/public/legacy.ts index 15cf97beb57172..ded1e773ac07a0 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/legacy.ts +++ b/src/legacy/core_plugins/vis_type_vega/public/legacy.ts @@ -20,13 +20,13 @@ import { PluginInitializerContext } from 'kibana/public'; import { npSetup, npStart } from 'ui/new_platform'; -import { setup as setupVisualizations } from '../../visualizations/public/np_ready/public/legacy'; +import { setup as visualizationsSetup } from '../../visualizations/public/legacy'; import { VegaPluginSetupDependencies } from './plugin'; import { LegacyDependenciesPlugin } from './shim'; import { plugin } from '.'; const plugins: Readonly = { - visualizations: setupVisualizations, + visualizations: visualizationsSetup, data: npSetup.plugins.data, // Temporary solution diff --git a/src/legacy/core_plugins/vis_type_vega/public/plugin.ts b/src/legacy/core_plugins/vis_type_vega/public/plugin.ts index b2a6fade883ca0..4d90b360bdb69b 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_vega/public/plugin.ts @@ -25,7 +25,7 @@ import { } from '../../../../core/public'; import { LegacyDependenciesPlugin, LegacyDependenciesPluginSetup } from './shim'; import { Plugin as DataPublicPlugin } from '../../../../plugins/data/public'; -import { VisualizationsSetup } from '../../visualizations/public/np_ready/public'; +import { VisualizationsSetup } from '../../visualizations/public'; import { createVegaFn } from './vega_fn'; import { createVegaTypeDefinition } from './vega_type'; diff --git a/src/legacy/core_plugins/vis_type_vega/public/vega_type.ts b/src/legacy/core_plugins/vis_type_vega/public/vega_type.ts index d1f04c794e3c62..6ffcd8867ffea9 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/vega_type.ts +++ b/src/legacy/core_plugins/vis_type_vega/public/vega_type.ts @@ -25,7 +25,7 @@ import { DefaultEditorSize } from 'ui/vis/editor_size'; import { defaultFeedbackMessage } from 'ui/vis/default_feedback_message'; import vegaEditorTemplate from './vega_editor_template.html'; -import { visFactory } from '../../visualizations/public/np_ready/public'; +import { visFactory } from '../../visualizations/public'; import { VegaVisualizationDependencies } from './plugin'; import { createVegaRequestHandler } from './vega_request_handler'; diff --git a/src/legacy/core_plugins/visualizations/index.ts b/src/legacy/core_plugins/visualizations/index.ts index 3642071667f487..bb9ef1588bdc2a 100644 --- a/src/legacy/core_plugins/visualizations/index.ts +++ b/src/legacy/core_plugins/visualizations/index.ts @@ -25,7 +25,7 @@ export default function VisualizationsPlugin(kibana: any) { const config: Legacy.PluginSpecOptions = { id: 'visualizations', require: ['data'], - publicDir: resolve(__dirname, 'public/np_ready/public'), + publicDir: resolve(__dirname, 'public'), config: (Joi: any) => { return Joi.object({ enabled: Joi.boolean().default(true), diff --git a/src/legacy/core_plugins/visualizations/public/index.ts b/src/legacy/core_plugins/visualizations/public/index.ts new file mode 100644 index 00000000000000..eb93bb9b419ebf --- /dev/null +++ b/src/legacy/core_plugins/visualizations/public/index.ts @@ -0,0 +1,57 @@ +/* + * 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. + */ + +/** + * Static legacy code which hasn't been moved to this plugin yet, but + * should be eventually. + * + * @public + */ +// @ts-ignore Used only by tsvb, vega, input control vis +export { defaultFeedbackMessage } from 'ui/vis/default_feedback_message'; +// @ts-ignore +export { visFactory } from 'ui/vis/vis_factory'; +// @ts-ignore +export { DefaultEditorSize } from 'ui/vis/editor_size'; + +/** + * Legacy types which haven't been moved to this plugin yet, but + * should be eventually. + * + * @public + */ +import * as types from 'ui/vis/vis'; +export type Vis = types.Vis; +export type VisParams = types.VisParams; +export type VisProvider = types.VisProvider; +export type VisState = types.VisState; +export { VisualizationController, VisType } from 'ui/vis/vis_types/vis_type'; +export { VisTypesRegistry } from 'ui/registry/vis_types'; +export { Status } from 'ui/vis/update_status'; + +/** + * Static np-ready code, re-exported here so consumers can import from + * `src/legacy/core_plugins/visualizations/public` + * + * @public + */ +export * from './np_ready'; + +// for backwards compatibility with 7.3 +export { setup as visualizations } from './legacy'; diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy.ts b/src/legacy/core_plugins/visualizations/public/legacy.ts similarity index 58% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/legacy.ts rename to src/legacy/core_plugins/visualizations/public/legacy.ts index 04a49294bd0c68..3dd31f624c4ad6 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy.ts +++ b/src/legacy/core_plugins/visualizations/public/legacy.ts @@ -16,24 +16,37 @@ * specific language governing permissions and limitations * under the License. */ -/* eslint-disable @kbn/eslint/no-restricted-paths */ + +/** + * New Platform Shim + * + * In this file, we import any legacy dependencies we have, and shim them into + * our plugin by manually constructing the values that the new platform will + * eventually be passing to the `setup/start` method of our plugin definition. + * + * The idea is that our `plugin.ts` can stay "pure" and not contain any legacy + * world code. Then when it comes time to migrate to the new platform, we can + * simply delete this shim file. + * + * We are also calling `setup/start` here and exporting our public contract so that + * other legacy plugins are able to import from '../core_plugins/visualizations/legacy' + * and receive the response value of the `setup/start` contract, mimicking the + * data that will eventually be injected by the new platform. + */ + +import { PluginInitializerContext } from 'src/core/public'; import { npSetup, npStart } from 'ui/new_platform'; // @ts-ignore import { VisFiltersProvider, createFilter } from 'ui/vis/vis_filters'; // @ts-ignore -import { defaultFeedbackMessage } from 'ui/vis/default_feedback_message'; -// @ts-ignore import { VisProvider as Vis } from 'ui/vis/index.js'; // @ts-ignore import { VisFactoryProvider } from 'ui/vis/vis_factory'; import { VisTypesRegistryProvider } from 'ui/registry/vis_types'; -/* eslint-enable @kbn/eslint/no-restricted-paths */ - -import { visTypeAliasRegistry } from './types/vis_type_alias_registry'; import { plugin } from '.'; -const pluginInstance = plugin({} as any); +const pluginInstance = plugin({} as PluginInitializerContext); export const setup = pluginInstance.setup(npSetup.core, { __LEGACY: { @@ -43,8 +56,6 @@ export const setup = pluginInstance.setup(npSetup.core, { Vis, VisFactoryProvider, VisTypesRegistryProvider, - defaultFeedbackMessage, - visTypeAliasRegistry, }, }); -export const start = pluginInstance.start(npStart.core); +export const start = pluginInstance.start(npStart.core, {}); diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/mocks.ts b/src/legacy/core_plugins/visualizations/public/mocks.ts similarity index 72% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/mocks.ts rename to src/legacy/core_plugins/visualizations/public/mocks.ts index df5e4d25dedccb..5cfe78fc2fa349 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/mocks.ts +++ b/src/legacy/core_plugins/visualizations/public/mocks.ts @@ -17,7 +17,6 @@ * under the License. */ -/* eslint-disable @kbn/eslint/no-restricted-paths */ jest.mock('ui/vis/vis_filters'); jest.mock('ui/vis/default_feedback_message'); jest.mock('ui/vis/index.js'); @@ -26,23 +25,18 @@ jest.mock('ui/registry/vis_types'); // @ts-ignore import { VisFiltersProvider, createFilter } from 'ui/vis/vis_filters'; // @ts-ignore -import { defaultFeedbackMessage } from 'ui/vis/default_feedback_message'; -// @ts-ignore import { VisProvider as Vis } from 'ui/vis/index.js'; // @ts-ignore import { VisFactoryProvider } from 'ui/vis/vis_factory'; import { VisTypesRegistryProvider } from 'ui/registry/vis_types'; -/* eslint-enable @kbn/eslint/no-restricted-paths */ jest.mock('./types/vis_type_alias_registry'); -import { visTypeAliasRegistry } from './types/vis_type_alias_registry'; - -import { Plugin } from '.'; -import { coreMock } from '../../../../../../core/public/mocks'; +import { PluginInitializerContext } from 'src/core/public'; -export type Setup = jest.Mocked>; -export type Start = jest.Mocked>; +import { VisualizationsSetup, VisualizationsStart } from './'; +import { VisualizationsPlugin } from './np_ready/plugin'; +import { coreMock } from '../../../../core/public/mocks'; -const createSetupContract = (): Setup => ({ +const createSetupContract = (): VisualizationsSetup => ({ filters: { VisFiltersProvider: jest.fn(), createFilter: jest.fn(), @@ -51,7 +45,6 @@ const createSetupContract = (): Setup => ({ Vis, VisFactoryProvider: jest.fn(), registerVisualization: jest.fn(), - defaultFeedbackMessage, visTypeAliasRegistry: { add: jest.fn(), get: jest.fn(), @@ -59,10 +52,10 @@ const createSetupContract = (): Setup => ({ }, }); -const createStartContract = (): Start => {}; +const createStartContract = (): VisualizationsStart => ({}); const createInstance = () => { - const plugin = new Plugin({} as any); + const plugin = new VisualizationsPlugin({} as PluginInitializerContext); const setup = plugin.setup(coreMock.createSetup(), { __LEGACY: { @@ -72,11 +65,9 @@ const createInstance = () => { Vis, VisFactoryProvider, VisTypesRegistryProvider, - defaultFeedbackMessage, - visTypeAliasRegistry, }, }); - const doStart = () => plugin.start(coreMock.createStart()); + const doStart = () => plugin.start(coreMock.createStart(), {}); return { plugin, diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/filters/filters_service.ts b/src/legacy/core_plugins/visualizations/public/np_ready/filters/filters_service.ts similarity index 100% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/filters/filters_service.ts rename to src/legacy/core_plugins/visualizations/public/np_ready/filters/filters_service.ts diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/filters/index.ts b/src/legacy/core_plugins/visualizations/public/np_ready/filters/index.ts similarity index 92% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/filters/index.ts rename to src/legacy/core_plugins/visualizations/public/np_ready/filters/index.ts index 591f7c9bc77151..480796c3771752 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/filters/index.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/filters/index.ts @@ -17,4 +17,4 @@ * under the License. */ -export { FiltersService, FiltersSetup } from './filters_service'; +export * from './filters_service'; diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/index.ts b/src/legacy/core_plugins/visualizations/public/np_ready/index.ts new file mode 100644 index 00000000000000..38b6a321253589 --- /dev/null +++ b/src/legacy/core_plugins/visualizations/public/np_ready/index.ts @@ -0,0 +1,46 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * Visualizations Plugin - public + * + * This is the entry point for the entire client-side public contract of the plugin. + * If something is not explicitly exported here, you can safely assume it is private + * to the plugin and not considered stable. + * + * All stateful contracts will be injected by the platform at runtime, and are defined + * in the setup/start interfaces in `plugin.ts`. The remaining items exported here are + * either types, or static code. + */ +import { PluginInitializerContext } from 'src/core/public'; +import { VisualizationsPlugin, VisualizationsSetup, VisualizationsStart } from './plugin'; + +/** @public */ +export type VisualizationsSetup = VisualizationsSetup; +export type VisualizationsStart = VisualizationsStart; + +/** @public types */ +export { VisTypeAlias } from './types'; + +export function plugin(initializerContext: PluginInitializerContext) { + return new VisualizationsPlugin(initializerContext); +} + +/** @public static code */ +// TODO once items are moved from ui/vis into this service diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/kibana.json b/src/legacy/core_plugins/visualizations/public/np_ready/kibana.json deleted file mode 100644 index 8ecf3dfce6e945..00000000000000 --- a/src/legacy/core_plugins/visualizations/public/np_ready/kibana.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "id": "visualizations", - "version": "kibana", - "requiredPlugins": [ - ], - "server": false, - "ui": true - } - \ No newline at end of file diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/plugin.ts b/src/legacy/core_plugins/visualizations/public/np_ready/plugin.ts similarity index 53% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/plugin.ts rename to src/legacy/core_plugins/visualizations/public/np_ready/plugin.ts index abf5974b77532e..90ca5fc79175ca 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/plugin.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/plugin.ts @@ -20,9 +20,13 @@ import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from 'src/core import { FiltersService, FiltersSetup } from './filters'; import { TypesService, TypesSetup } from './types'; -import { VisTypeAliasRegistry } from './types/vis_type_alias_registry'; -interface SetupDependencies { +/** + * Interface for any dependencies on other plugins' contracts. + * + * @internal + */ +interface VisualizationsPluginSetupDependencies { __LEGACY: { VisFiltersProvider: any; createFilter: any; @@ -30,36 +34,53 @@ interface SetupDependencies { Vis: any; VisFactoryProvider: any; VisTypesRegistryProvider: any; - defaultFeedbackMessage: any; - visTypeAliasRegistry: VisTypeAliasRegistry; }; } -export interface Setup { +// eslint-disable-next-line @typescript-eslint/no-empty-interface +interface VisualizationsPluginStartDependencies {} + +/** + * Interface for this plugin's returned setup/start contracts. + * + * @public + */ +export interface VisualizationsSetup { filters: FiltersSetup; types: TypesSetup; } -export type Start = void; +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface VisualizationsStart {} -export class VisualizationsPublicPlugin implements Plugin { - private readonly filters: FiltersService; - private readonly types: TypesService; +/** + * Visualizations Plugin - public + * + * This plugin's stateful contracts are returned from the `setup` and `start` methods + * below. The interfaces for these contracts are provided above. + * + * @internal + */ +export class VisualizationsPlugin + implements + Plugin< + VisualizationsSetup, + VisualizationsStart, + VisualizationsPluginSetupDependencies, + VisualizationsPluginStartDependencies + > { + private readonly filters: FiltersService = new FiltersService(); + private readonly types: TypesService = new TypesService(); - constructor(initializerContext: PluginInitializerContext) { - this.filters = new FiltersService(); - this.types = new TypesService(); - } + constructor(initializerContext: PluginInitializerContext) {} - public setup(core: CoreSetup, { __LEGACY }: SetupDependencies) { + public setup(core: CoreSetup, { __LEGACY }: VisualizationsPluginSetupDependencies) { const { VisFiltersProvider, createFilter, Vis, VisFactoryProvider, VisTypesRegistryProvider, - defaultFeedbackMessage, - visTypeAliasRegistry, } = __LEGACY; return { @@ -67,18 +88,12 @@ export class VisualizationsPublicPlugin implements Plugin any) => { VisTypesRegistryProvider.register(registerFn); }, - defaultFeedbackMessage, // make default in base vis type, or move? visTypeAliasRegistry, }; } @@ -64,17 +47,11 @@ export class TypesService { } } -/** @public */ +/** @internal */ export type TypesSetup = ReturnType; -export { visFactory, DefaultEditorSize }; - /** @public types */ export type VisTypeAlias = VisTypeAlias; -export type Vis = types.Vis; -export type VisParams = types.VisParams; -export type VisProvider = types.VisProvider; -export type VisState = types.VisState; -// todo: this breaks it // export { VisualizationController, VisType } from 'ui/vis/vis_types/vis_type'; -export { VisTypesRegistry } from 'ui/registry/vis_types'; -export { Status } from 'ui/vis/update_status'; + +/** @public static code */ +// TODO once items are moved from ui/vis into this service diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/types/vis_type_alias_registry.ts b/src/legacy/core_plugins/visualizations/public/np_ready/types/vis_type_alias_registry.ts similarity index 91% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/types/vis_type_alias_registry.ts rename to src/legacy/core_plugins/visualizations/public/np_ready/types/vis_type_alias_registry.ts index eb84f93a0d3bae..f982d1ed3ca215 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/types/vis_type_alias_registry.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/types/vis_type_alias_registry.ts @@ -17,7 +17,7 @@ * under the License. */ -export interface VisualizationListItem { +interface VisualizationListItem { editUrl: string; icon: string; id: string; @@ -27,7 +27,7 @@ export interface VisualizationListItem { typeTitle: string; } -export interface VisualizationsAppExtension { +interface VisualizationsAppExtension { docTypes: string[]; searchFields?: string[]; toListItem: (savedObject: { @@ -52,14 +52,14 @@ export interface VisTypeAlias { const registry: VisTypeAlias[] = []; -export interface VisTypeAliasRegistry { +interface VisTypeAliasRegistry { get: () => VisTypeAlias[]; add: (newVisTypeAlias: VisTypeAlias) => void; } export const visTypeAliasRegistry: VisTypeAliasRegistry = { get: () => [...registry], - add: (newVisTypeAlias: VisTypeAlias) => { + add: newVisTypeAlias => { if (registry.find(visTypeAlias => visTypeAlias.name === newVisTypeAlias.name)) { throw new Error(`${newVisTypeAlias.name} already registered`); } diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/index.ts b/src/legacy/ui/public/elastic_charts/index.ts similarity index 57% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/index.ts rename to src/legacy/ui/public/elastic_charts/index.ts index d38acaa3cf3f2c..661ea693102a73 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/index.ts +++ b/src/legacy/ui/public/elastic_charts/index.ts @@ -17,29 +17,10 @@ * under the License. */ -import { PluginInitializerContext } from 'src/core/public'; -import { VisualizationsPublicPlugin, Setup } from './plugin'; +import chrome from 'ui/chrome'; +import { Theme, LIGHT_THEME, DARK_THEME } from '@elastic/charts'; -/** @public */ -export type VisualizationsSetup = Setup; - -/** @public types */ -export { - Vis, - visFactory, - DefaultEditorSize, - VisParams, - VisProvider, - VisState, - // VisualizationController, - // VisType, - VisTypeAlias, - VisTypesRegistry, - Status, -} from './types'; - -export function plugin(initializerContext: PluginInitializerContext) { - return new VisualizationsPublicPlugin(initializerContext); +export function getChartTheme(): Theme { + const isDarkMode = chrome.getUiSettingsClient().get('theme:darkMode'); + return isDarkMode ? DARK_THEME : LIGHT_THEME; } - -export { VisualizationsPublicPlugin as Plugin }; diff --git a/test/functional/apps/discover/_discover.js b/test/functional/apps/discover/_discover.js index c29542e6a40354..9f37feef781cae 100644 --- a/test/functional/apps/discover/_discover.js +++ b/test/functional/apps/discover/_discover.js @@ -26,7 +26,7 @@ export default function ({ getService, getPageObjects }) { const browser = getService('browser'); const kibanaServer = getService('kibanaServer'); const filterBar = getService('filterBar'); - const PageObjects = getPageObjects(['common', 'discover', 'header', 'visualize', 'timePicker']); + const PageObjects = getPageObjects(['common', 'discover', 'header', 'timePicker']); const defaultSettings = { defaultIndex: 'logstash-*', }; @@ -85,83 +85,30 @@ export default function ({ getService, getPageObjects }) { }); }); - it('should show the correct bar chart', async function () { - const expectedBarChartData = [ - 35, - 189, - 694, - 1347, - 1285, - 704, - 176, - 29, - 39, - 189, - 640, - 1276, - 1327, - 663, - 166, - 25, - 30, - 164, - 663, - 1320, - 1270, - 681, - 188, - 27, - ]; - await verifyChartData(expectedBarChartData); - }); - it('should show correct time range string in chart', async function () { const actualTimeString = await PageObjects.discover.getChartTimespan(); const expectedTimeString = `Sep 19, 2015 @ 06:31:44.000 - Sep 23, 2015 @ 18:31:44.000`; expect(actualTimeString).to.be(expectedTimeString); }); - it('should show bars in the correct time zone', async function () { - const maxTicks = [ - '2015-09-20 00:00', - '2015-09-20 12:00', - '2015-09-21 00:00', - '2015-09-21 12:00', - '2015-09-22 00:00', - '2015-09-22 12:00', - '2015-09-23 00:00', - '2015-09-23 12:00' - ]; - - for (const tick of await PageObjects.discover.getBarChartXTicks()) { - if (!maxTicks.includes(tick)) { - throw new Error(`unexpected x-axis tick "${tick}"`); - } - } - }); - it('should modify the time range when a bar is clicked', async function () { await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); - await PageObjects.visualize.waitForVisualization(); - await PageObjects.discover.clickHistogramBar(0); - await PageObjects.visualize.waitForVisualization(); + await PageObjects.discover.clickHistogramBar(); const time = await PageObjects.timePicker.getTimeConfig(); - expect(time.start).to.be('Sep 20, 2015 @ 00:00:00.000'); - expect(time.end).to.be('Sep 20, 2015 @ 03:00:00.000'); - const rowData = await PageObjects.discover.getDocTableIndex(1); - expect(rowData).to.have.string('Sep 20, 2015 @ 02:57:03.761'); + expect(time.start).to.be('Sep 21, 2015 @ 09:00:00.000'); + expect(time.end).to.be('Sep 21, 2015 @ 12:00:00.000'); + const rowData = await PageObjects.discover.getDocTableField(1); + expect(rowData).to.have.string('Sep 21, 2015 @ 11:59:22.316'); }); it('should modify the time range when the histogram is brushed', async function () { await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); - await PageObjects.visualize.waitForVisualization(); - await PageObjects.discover.brushHistogram(0, 1); - await PageObjects.visualize.waitForVisualization(); + await PageObjects.discover.brushHistogram(); const newDurationHours = await PageObjects.timePicker.getTimeDurationInHours(); - expect(Math.round(newDurationHours)).to.be(3); + expect(Math.round(newDurationHours)).to.be(108); const rowData = await PageObjects.discover.getDocTableField(1); - expect(rowData).to.have.string('Sep 20, 2015 @ 02:56:02.323'); + expect(rowData).to.have.string('Sep 22, 2015 @ 23:50:13.253'); }); it('should show correct initial chart interval of Auto', async function () { @@ -173,167 +120,6 @@ export default function ({ getService, getPageObjects }) { expect(actualInterval).to.be(expectedInterval); }); - it('should show correct data for chart interval Hourly', async function () { - await PageObjects.header.awaitGlobalLoadingIndicatorHidden(); - await PageObjects.discover.setChartInterval('Hourly'); - - const expectedBarChartData = [ - 4, - 7, - 16, - 23, - 38, - 87, - 132, - 159, - 248, - 320, - 349, - 376, - 380, - 324, - 293, - 233, - 188, - 125, - 69, - 40, - 28, - 17, - 2, - 3, - 8, - 10, - 12, - 28, - 36, - 84, - 111, - 157, - 229, - 292, - 324, - 373, - 378, - 345, - 306, - 223, - 167, - 124, - 72, - 35, - 22, - 11, - 7, - 1, - 6, - 5, - 12, - 25, - 27, - 76, - 111, - 175, - 228, - 294, - 358, - 372, - 366, - 344, - 276, - 213, - 201, - 113, - 72, - 39, - 36, - 12, - 7, - 3, - ]; - await verifyChartData(expectedBarChartData); - }); - - it('should show correct data for chart interval Daily', async function () { - const chartInterval = 'Daily'; - const expectedBarChartData = [4757, 4614, 4633]; - await PageObjects.discover.setChartInterval(chartInterval); - await retry.try(async () => { - await verifyChartData(expectedBarChartData); - }); - }); - - it('should show correct data for chart interval Weekly', async function () { - const chartInterval = 'Weekly'; - const expectedBarChartData = [4757, 9247]; - - await PageObjects.discover.setChartInterval(chartInterval); - await retry.try(async () => { - await verifyChartData(expectedBarChartData); - }); - }); - - it('browser back button should show previous interval Daily', async function () { - const expectedChartInterval = 'Daily'; - const expectedBarChartData = [4757, 4614, 4633]; - - await browser.goBack(); - await retry.try(async function tryingForTime() { - const actualInterval = await PageObjects.discover.getChartInterval(); - expect(actualInterval).to.be(expectedChartInterval); - }); - await verifyChartData(expectedBarChartData); - }); - - it('should show correct data for chart interval Monthly', async function () { - const chartInterval = 'Monthly'; - const expectedBarChartData = [13129]; - - await PageObjects.discover.setChartInterval(chartInterval); - await verifyChartData(expectedBarChartData); - }); - - it('should show correct data for chart interval Yearly', async function () { - const chartInterval = 'Yearly'; - const expectedBarChartData = [13129]; - - await PageObjects.discover.setChartInterval(chartInterval); - await verifyChartData(expectedBarChartData); - }); - - it('should show correct data for chart interval Auto', async function () { - const chartInterval = 'Auto'; - const expectedBarChartData = [ - 35, - 189, - 694, - 1347, - 1285, - 704, - 176, - 29, - 39, - 189, - 640, - 1276, - 1327, - 663, - 166, - 25, - 30, - 164, - 663, - 1320, - 1270, - 681, - 188, - 27, - ]; - - await PageObjects.discover.setChartInterval(chartInterval); - await verifyChartData(expectedBarChartData); - }); - it('should show Auto chart interval', async function () { const expectedChartInterval = 'Auto'; @@ -345,42 +131,6 @@ export default function ({ getService, getPageObjects }) { const isVisible = await PageObjects.discover.hasNoResults(); expect(isVisible).to.be(false); }); - - async function verifyChartData(expectedBarChartData) { - await retry.try(async function tryingForTime() { - const paths = await PageObjects.discover.getBarChartData(); - // the largest bars are over 100 pixels high so this is less than 1% tolerance - const barHeightTolerance = 1; - let stringResults = ''; - let hasFailure = false; - for (let y = 0; y < expectedBarChartData.length; y++) { - stringResults += - y + - ': expected = ' + - expectedBarChartData[y] + - ', actual = ' + - paths[y] + - ', Pass = ' + - (Math.abs(expectedBarChartData[y] - paths[y]) < - barHeightTolerance) + - '\n'; - if ( - Math.abs(expectedBarChartData[y] - paths[y]) > barHeightTolerance - ) { - hasFailure = true; - } - } - if (hasFailure) { - log.debug(stringResults); - log.debug(paths); - } - for (let x = 0; x < expectedBarChartData.length; x++) { - expect( - Math.abs(expectedBarChartData[x] - paths[x]) < barHeightTolerance - ).to.be.ok(); - } - }); - } }); describe('query #2, which has an empty time range', () => { @@ -439,35 +189,15 @@ export default function ({ getService, getPageObjects }) { describe('time zone switch', () => { it('should show bars in the correct time zone after switching', async function () { - await kibanaServer.uiSettings.replace({ 'dateFormat:tz': 'America/Phoenix' }); await browser.refresh(); await PageObjects.header.awaitKibanaChrome(); await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); - const maxTicks = [ - '2015-09-20 00:00', - '2015-09-20 12:00', - '2015-09-21 00:00', - '2015-09-21 12:00', - '2015-09-22 00:00', - '2015-09-22 12:00', - '2015-09-23 00:00', - '2015-09-23 12:00' - ]; - - await retry.try(async function () { - for (const tick of await PageObjects.discover.getBarChartXTicks()) { - if (!maxTicks.includes(tick)) { - throw new Error(`unexpected x-axis tick "${tick}"`); - } - } - }); log.debug('check that the newest doc timestamp is now -7 hours from the UTC time in the first test'); const rowData = await PageObjects.discover.getDocTableIndex(1); expect(rowData.startsWith('Sep 22, 2015 @ 16:50:13.253')).to.be.ok(); }); - }); }); } diff --git a/test/functional/apps/management/_scripted_fields.js b/test/functional/apps/management/_scripted_fields.js index ec3941264450de..32d410eb240408 100644 --- a/test/functional/apps/management/_scripted_fields.js +++ b/test/functional/apps/management/_scripted_fields.js @@ -129,13 +129,13 @@ export default function ({ getService, getPageObjects }) { const toTime = '2015-09-18 18:31:44.000'; await PageObjects.common.navigateToApp('discover'); await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); - await PageObjects.visualize.waitForVisualization(); + await PageObjects.discover.clickFieldListItem(scriptedPainlessFieldName); await retry.try(async function () { await PageObjects.discover.clickFieldListItemAdd(scriptedPainlessFieldName); }); await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.visualize.waitForVisualization(); + await retry.try(async function () { const rowData = await PageObjects.discover.getDocTableIndex(1); expect(rowData).to.be('Sep 18, 2015 @ 18:20:57.916\n18'); @@ -147,7 +147,7 @@ export default function ({ getService, getPageObjects }) { await log.debug('filter by the first value (14) in the expanded scripted field list'); await PageObjects.discover.clickFieldListPlusFilter(scriptedPainlessFieldName, '14'); await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.visualize.waitForVisualization(); + await retry.try(async function () { expect(await PageObjects.discover.getHitCount()).to.be('31'); }); @@ -161,7 +161,7 @@ export default function ({ getService, getPageObjects }) { await filterBar.removeAllFilters(); await PageObjects.discover.clickFieldListItemVisualize(scriptedPainlessFieldName); await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.visualize.waitForVisualization(); + await inspector.open(); await inspector.setTablePageSize(50); await inspector.expectTableData(expectedChartValues); @@ -191,13 +191,13 @@ export default function ({ getService, getPageObjects }) { const toTime = '2015-09-18 18:31:44.000'; await PageObjects.common.navigateToApp('discover'); await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); - await PageObjects.visualize.waitForVisualization(); + await PageObjects.discover.clickFieldListItem(scriptedPainlessFieldName2); await retry.try(async function () { await PageObjects.discover.clickFieldListItemAdd(scriptedPainlessFieldName2); }); await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.visualize.waitForVisualization(); + await retry.try(async function () { const rowData = await PageObjects.discover.getDocTableIndex(1); expect(rowData).to.be('Sep 18, 2015 @ 18:20:57.916\ngood'); @@ -210,7 +210,7 @@ export default function ({ getService, getPageObjects }) { await log.debug('filter by "bad" in the expanded scripted field list'); await PageObjects.discover.clickFieldListPlusFilter(scriptedPainlessFieldName2, 'bad'); await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.visualize.waitForVisualization(); + await retry.try(async function () { expect(await PageObjects.discover.getHitCount()).to.be('27'); }); @@ -220,7 +220,6 @@ export default function ({ getService, getPageObjects }) { it('should visualize scripted field in vertical bar chart', async function () { await PageObjects.discover.clickFieldListItemVisualize(scriptedPainlessFieldName2); await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.visualize.waitForVisualization(); await inspector.open(); await inspector.expectTableData([ ['good', '359'], @@ -252,13 +251,13 @@ export default function ({ getService, getPageObjects }) { const toTime = '2015-09-18 18:31:44.000'; await PageObjects.common.navigateToApp('discover'); await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); - await PageObjects.visualize.waitForVisualization(); + await PageObjects.discover.clickFieldListItem(scriptedPainlessFieldName2); await retry.try(async function () { await PageObjects.discover.clickFieldListItemAdd(scriptedPainlessFieldName2); }); await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.visualize.waitForVisualization(); + await retry.try(async function () { const rowData = await PageObjects.discover.getDocTableIndex(1); expect(rowData).to.be('Sep 18, 2015 @ 18:20:57.916\ntrue'); @@ -271,7 +270,7 @@ export default function ({ getService, getPageObjects }) { await log.debug('filter by "true" in the expanded scripted field list'); await PageObjects.discover.clickFieldListPlusFilter(scriptedPainlessFieldName2, 'true'); await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.visualize.waitForVisualization(); + await retry.try(async function () { expect(await PageObjects.discover.getHitCount()).to.be('359'); }); @@ -281,7 +280,6 @@ export default function ({ getService, getPageObjects }) { it('should visualize scripted field in vertical bar chart', async function () { await PageObjects.discover.clickFieldListItemVisualize(scriptedPainlessFieldName2); await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.visualize.waitForVisualization(); await inspector.open(); await inspector.expectTableData([ ['true', '359'], @@ -314,13 +312,13 @@ export default function ({ getService, getPageObjects }) { const toTime = '2015-09-18 07:00:00.000'; await PageObjects.common.navigateToApp('discover'); await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); - await PageObjects.visualize.waitForVisualization(); + await PageObjects.discover.clickFieldListItem(scriptedPainlessFieldName2); await retry.try(async function () { await PageObjects.discover.clickFieldListItemAdd(scriptedPainlessFieldName2); }); await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.visualize.waitForVisualization(); + await retry.try(async function () { const rowData = await PageObjects.discover.getDocTableIndex(1); expect(rowData).to.be('Sep 18, 2015 @ 06:52:55.953\n2015-09-18 07:00'); @@ -332,7 +330,7 @@ export default function ({ getService, getPageObjects }) { await log.debug('filter by "2015-09-17 23:00" in the expanded scripted field list'); await PageObjects.discover.clickFieldListPlusFilter(scriptedPainlessFieldName2, '2015-09-17 23:00'); await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.visualize.waitForVisualization(); + await retry.try(async function () { expect(await PageObjects.discover.getHitCount()).to.be('1'); }); @@ -342,7 +340,6 @@ export default function ({ getService, getPageObjects }) { it('should visualize scripted field in vertical bar chart', async function () { await PageObjects.discover.clickFieldListItemVisualize(scriptedPainlessFieldName2); await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.visualize.waitForVisualization(); await inspector.open(); await inspector.setTablePageSize(50); await inspector.expectTableData([ diff --git a/test/functional/apps/visualize/_gauge_chart.js b/test/functional/apps/visualize/_gauge_chart.js index d38d2b3cc2f0fe..60254b5c30f697 100644 --- a/test/functional/apps/visualize/_gauge_chart.js +++ b/test/functional/apps/visualize/_gauge_chart.js @@ -26,7 +26,8 @@ export default function ({ getService, getPageObjects }) { const testSubjects = getService('testSubjects'); const PageObjects = getPageObjects(['common', 'visualize', 'timePicker']); - describe('gauge chart', function indexPatternCreation() { + // FLAKY: https://github.com/elastic/kibana/issues/45089 + describe.skip('gauge chart', function indexPatternCreation() { this.tags('smoke'); const fromTime = '2015-09-19 06:31:44.000'; const toTime = '2015-09-23 18:31:44.000'; diff --git a/test/functional/apps/visualize/_tsvb_chart.ts b/test/functional/apps/visualize/_tsvb_chart.ts index cf7930603c20a8..3505ec3b1daf41 100644 --- a/test/functional/apps/visualize/_tsvb_chart.ts +++ b/test/functional/apps/visualize/_tsvb_chart.ts @@ -27,7 +27,8 @@ export default function({ getService, getPageObjects }: FtrProviderContext) { const inspector = getService('inspector'); const PageObjects = getPageObjects(['visualize', 'visualBuilder', 'timePicker']); - describe('visual builder', function describeIndexTests() { + // FLAKY: https://github.com/elastic/kibana/issues/45315 + describe.skip('visual builder', function describeIndexTests() { this.tags('smoke'); beforeEach(async () => { await PageObjects.visualize.navigateToNewVisualization(); diff --git a/test/functional/apps/visualize/_tsvb_markdown.ts b/test/functional/apps/visualize/_tsvb_markdown.ts index 55fd297ee216e4..b433236492dc5e 100644 --- a/test/functional/apps/visualize/_tsvb_markdown.ts +++ b/test/functional/apps/visualize/_tsvb_markdown.ts @@ -37,7 +37,9 @@ export default function({ getPageObjects }: FtrProviderContext) { }); } - describe('visual builder', function describeIndexTests() { + // FLAKY: https://github.com/elastic/kibana/issues/45323 + // FLAKY: https://github.com/elastic/kibana/issues/45330 + describe.skip('visual builder', function describeIndexTests() { describe('markdown', () => { before(async () => { await visualBuilder.resetPage(); diff --git a/test/functional/apps/visualize/_vertical_bar_chart.js b/test/functional/apps/visualize/_vertical_bar_chart.js index 1479d2bf034cc4..221ce68cf5ed83 100644 --- a/test/functional/apps/visualize/_vertical_bar_chart.js +++ b/test/functional/apps/visualize/_vertical_bar_chart.js @@ -278,7 +278,8 @@ export default function ({ getService, getPageObjects }) { }); }); - describe('vertical bar with multiple splits', function () { + // FLAKY: https://github.com/elastic/kibana/issues/45105 + describe.skip('vertical bar with multiple splits', function () { before(initBarChart); it('should show correct series', async function () { diff --git a/test/functional/page_objects/discover_page.js b/test/functional/page_objects/discover_page.js index dfe8dc1071d46c..52b4c8d931ae21 100644 --- a/test/functional/page_objects/discover_page.js +++ b/test/functional/page_objects/discover_page.js @@ -116,16 +116,20 @@ export function DiscoverPageProvider({ getService, getPageObjects }) { await testSubjects.click('discoverOpenButton'); } - async clickHistogramBar(i) { - const bars = await find.allByCssSelector(`.series.histogram rect`); - await bars[i].click(); + async clickHistogramBar() { + const el = await find.byCssSelector('.echChart canvas:last-of-type'); + + await browser.getActions() + .move({ x: 200, y: 20, origin: el._webElement }) + .click() + .perform(); } - async brushHistogram(from, to) { - const bars = await find.allByCssSelector('.series.histogram rect'); + async brushHistogram() { + const el = await find.byCssSelector('.echChart canvas:last-of-type'); await browser.dragAndDrop( - { location: bars[from], offset: { x: 0, y: -5 } }, - { location: bars[to], offset: { x: 0, y: -5 } } + { location: el, offset: { x: 200, y: 20 } }, + { location: el, offset: { x: 400, y: 30 } } ); } @@ -133,12 +137,6 @@ export function DiscoverPageProvider({ getService, getPageObjects }) { return await globalNav.getLastBreadcrumb(); } - async getBarChartXTicks() { - const xAxis = await find.byCssSelector('.x.axis.CategoryAxis-1'); - const $ = await xAxis.parseDomContent(); - return $('.tick > text').toArray().map(tick => $(tick).text().trim()); - } - async getBarChartData() { let yAxisLabel = 0; diff --git a/test/scripts/jenkins_xpack.sh b/test/scripts/jenkins_xpack.sh index 61525b7a50398d..bdfc9dcebb308e 100755 --- a/test/scripts/jenkins_xpack.sh +++ b/test/scripts/jenkins_xpack.sh @@ -23,11 +23,13 @@ checks-reporter-with-killswitch "X-Pack SIEM cyclic dependency test" node legacy echo "" echo "" -echo " -> Running jest contracts tests" -cd "$XPACK_DIR" -SLAPSHOT_ONLINE=true CONTRACT_ONLINE=true node scripts/jest_contract.js --ci --verbose -echo "" -echo "" +# FAILING: https://github.com/elastic/kibana/issues/44250 +# echo " -> Running jest contracts tests" +# cd "$XPACK_DIR" +# SLAPSHOT_ONLINE=true CONTRACT_ONLINE=true node scripts/jest_contract.js --ci --verbose +# echo "" +# echo "" + # echo " -> Running jest integration tests" # cd "$XPACK_DIR" # node scripts/jest_integration --ci --verbose diff --git a/test/visual_regression/config.ts b/test/visual_regression/config.ts index 74f283f74683a8..77450b517dcc6d 100644 --- a/test/visual_regression/config.ts +++ b/test/visual_regression/config.ts @@ -26,7 +26,7 @@ export default async function({ readConfigFile }: FtrConfigProviderContext) { return { ...functionalConfig.getAll(), - testFiles: [require.resolve('./tests/console_app')], + testFiles: [require.resolve('./tests/console_app'), require.resolve('./tests/discover')], services, diff --git a/test/visual_regression/services/visual_testing/visual_testing.ts b/test/visual_regression/services/visual_testing/visual_testing.ts index 2b05f666a3500b..210de4c714d5cb 100644 --- a/test/visual_regression/services/visual_testing/visual_testing.ts +++ b/test/visual_regression/services/visual_testing/visual_testing.ts @@ -27,15 +27,15 @@ import { FtrProviderContext } from '../../ftr_provider_context'; // @ts-ignore internal js that is passed to the browser as is import { takePercySnapshot, takePercySnapshotWithAgent } from './take_percy_snapshot'; +export const DEFAULT_OPTIONS = { + widths: [1200], +}; + export async function VisualTestingProvider({ getService }: FtrProviderContext) { const browser = getService('browser'); const log = getService('log'); const lifecycle = getService('lifecycle'); - const DEFAULT_OPTIONS = { - widths: [1200], - }; - let currentTest: Test | undefined; lifecycle.on('beforeEachTest', (test: Test) => { currentTest = test; diff --git a/test/visual_regression/tests/discover/chart_visualization.js b/test/visual_regression/tests/discover/chart_visualization.js new file mode 100644 index 00000000000000..ca3def70627d9a --- /dev/null +++ b/test/visual_regression/tests/discover/chart_visualization.js @@ -0,0 +1,123 @@ +/* + * 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 log = getService('log'); + const retry = getService('retry'); + const esArchiver = getService('esArchiver'); + const browser = getService('browser'); + const kibanaServer = getService('kibanaServer'); + const PageObjects = getPageObjects(['common', 'discover', 'header', 'timePicker']); + const visualTesting = getService('visualTesting'); + const defaultSettings = { + defaultIndex: 'logstash-*', + }; + + describe('discover', function describeIndexTests() { + const fromTime = '2015-09-19 06:31:44.000'; + const toTime = '2015-09-23 18:31:44.000'; + + before(async function () { + log.debug('load kibana index with default index pattern'); + await esArchiver.load('discover'); + + // and load a set of makelogs data + await esArchiver.loadIfNeeded('logstash_functional'); + await kibanaServer.uiSettings.replace(defaultSettings); + log.debug('discover'); + await PageObjects.common.navigateToApp('discover'); + await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); + }); + + describe('query', function () { + this.tags(['skipFirefox']); + + it('should show bars in the correct time zone', async function () { + await PageObjects.header.awaitGlobalLoadingIndicatorHidden(); + await visualTesting.snapshot(); + }); + + it('should show correct data for chart interval Hourly', async function () { + await PageObjects.header.awaitGlobalLoadingIndicatorHidden(); + await PageObjects.discover.setChartInterval('Hourly'); + await visualTesting.snapshot(); + }); + + it('should show correct data for chart interval Daily', async function () { + await PageObjects.header.awaitGlobalLoadingIndicatorHidden(); + await PageObjects.discover.setChartInterval('Daily'); + await retry.try(async () => { + await visualTesting.snapshot(); + }); + }); + + it('should show correct data for chart interval Weekly', async function () { + await PageObjects.header.awaitGlobalLoadingIndicatorHidden(); + await PageObjects.discover.setChartInterval('Weekly'); + await retry.try(async () => { + await visualTesting.snapshot(); + }); + }); + + it('browser back button should show previous interval Daily', async function () { + await browser.goBack(); + await retry.try(async function tryingForTime() { + const actualInterval = await PageObjects.discover.getChartInterval(); + expect(actualInterval).to.be('Daily'); + }); + await PageObjects.header.awaitGlobalLoadingIndicatorHidden(); + await visualTesting.snapshot(); + }); + + it('should show correct data for chart interval Monthly', async function () { + await PageObjects.header.awaitGlobalLoadingIndicatorHidden(); + await PageObjects.discover.setChartInterval('Monthly'); + await visualTesting.snapshot(); + }); + + it('should show correct data for chart interval Yearly', async function () { + await PageObjects.header.awaitGlobalLoadingIndicatorHidden(); + await PageObjects.discover.setChartInterval('Yearly'); + await visualTesting.snapshot(); + }); + + it('should show correct data for chart interval Auto', async function () { + await PageObjects.header.awaitGlobalLoadingIndicatorHidden(); + await PageObjects.discover.setChartInterval('Auto'); + await visualTesting.snapshot(); + }); + }); + + describe('time zone switch', () => { + it('should show bars in the correct time zone after switching', async function () { + await kibanaServer.uiSettings.replace({ 'dateFormat:tz': 'America/Phoenix' }); + await browser.refresh(); + await PageObjects.header.awaitKibanaChrome(); + await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); + await PageObjects.header.awaitGlobalLoadingIndicatorHidden(); + await retry.try(async function () { + await visualTesting.snapshot(); + }); + }); + + }); + }); +} diff --git a/test/visual_regression/tests/discover/index.js b/test/visual_regression/tests/discover/index.js new file mode 100644 index 00000000000000..ba44fb12eef703 --- /dev/null +++ b/test/visual_regression/tests/discover/index.js @@ -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 { DEFAULT_OPTIONS } from '../../services/visual_testing/visual_testing'; + +// Width must be the same as visual_testing or canvas image widths will get skewed +const [SCREEN_WIDTH] = DEFAULT_OPTIONS.widths || []; + +export default function ({ getService, loadTestFile }) { + const esArchiver = getService('esArchiver'); + const browser = getService('browser'); + + describe('discover app', function () { + this.tags('ciGroup6'); + + before(function () { + return browser.setWindowSize(SCREEN_WIDTH, 1000); + }); + + after(function unloadMakelogs() { + return esArchiver.unload('logstash_functional'); + }); + + loadTestFile(require.resolve('./chart_visualization')); + }); +} diff --git a/x-pack/legacy/plugins/code/model/repository.ts b/x-pack/legacy/plugins/code/model/repository.ts index ec2777caf8584e..c4469a7d593d72 100644 --- a/x-pack/legacy/plugins/code/model/repository.ts +++ b/x-pack/legacy/plugins/code/model/repository.ts @@ -171,6 +171,7 @@ export interface IndexWorkerProgress extends WorkerProgress { export enum RepoState { CLONING, + UPDATING, DELETING, INDEXING, READY, diff --git a/x-pack/legacy/plugins/code/public/components/admin_page/project_item.tsx b/x-pack/legacy/plugins/code/public/components/admin_page/project_item.tsx index 48a6407b911c04..b3a2c49c08f516 100644 --- a/x-pack/legacy/plugins/code/public/components/admin_page/project_item.tsx +++ b/x-pack/legacy/plugins/code/public/components/admin_page/project_item.tsx @@ -30,6 +30,7 @@ import { RepoStatus } from '../../actions/status'; const stateColor = { [RepoState.CLONING]: 'secondary', + [RepoState.UPDATING]: 'secondary', [RepoState.DELETING]: 'accent', [RepoState.INDEXING]: 'primary', }; @@ -126,6 +127,12 @@ class CodeProjectItem extends React.PureComponent< ); + } else if (status.state === RepoState.UPDATING) { + footer = ( +
+ +
+ ); } else if (status.state === RepoState.DELETE_ERROR) { footer = (
diff --git a/x-pack/legacy/plugins/code/public/reducers/search.ts b/x-pack/legacy/plugins/code/public/reducers/search.ts index 182f1905d2345c..5092e611215b8d 100644 --- a/x-pack/legacy/plugins/code/public/reducers/search.ts +++ b/x-pack/legacy/plugins/code/public/reducers/search.ts @@ -156,7 +156,7 @@ export const search = handleActions( took, stats: { total, - from: from! + 1, + from: from as number, to: from! + results!.length, page: page!, totalPage: totalPage!, diff --git a/x-pack/legacy/plugins/code/public/reducers/status.ts b/x-pack/legacy/plugins/code/public/reducers/status.ts index fe9e58ffcc488c..4e57d5e7e7565c 100644 --- a/x-pack/legacy/plugins/code/public/reducers/status.ts +++ b/x-pack/legacy/plugins/code/public/reducers/status.ts @@ -71,6 +71,9 @@ const getGitState = (gitStatus: CloneWorkerProgress) => { ) { return RepoState.CLONE_ERROR; } else if (progress < WorkerReservedProgress.COMPLETED) { + if (gitStatus.cloneProgress && gitStatus.cloneProgress.isCloned) { + return RepoState.UPDATING; + } return RepoState.CLONING; } else if (progress === WorkerReservedProgress.COMPLETED) { return RepoState.READY; diff --git a/x-pack/legacy/plugins/console_extensions/spec/overrides/sql.query.json b/x-pack/legacy/plugins/console_extensions/spec/overrides/sql.query.json new file mode 100644 index 00000000000000..f2fe456a0c6e2a --- /dev/null +++ b/x-pack/legacy/plugins/console_extensions/spec/overrides/sql.query.json @@ -0,0 +1,23 @@ +{ + "sql.query": { + "data_autocomplete_rules": { + "query": { + "__template": { + "__raw": true, + "value": "\"\"\"\nSELECT * FROM \"TABLE\"\n\"\"\"" + } + } + }, + "url_params": { + "format": [ + "csv", + "json", + "tsv", + "txt", + "yaml", + "cbor", + "smile" + ] + } + } +} diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_get_jobs_summary_api.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_get_jobs_summary_api.ts index c5ca408371d454..ec14492571c462 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_get_jobs_summary_api.ts +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_get_jobs_summary_api.ts @@ -34,28 +34,33 @@ export const fetchJobStatusRequestPayloadRT = rt.type({ export type FetchJobStatusRequestPayload = rt.TypeOf; -// TODO: Get this to align with the payload - something is tripping it up somewhere -// export const fetchJobStatusResponsePayloadRT = rt.array(rt.type({ -// datafeedId: rt.string, -// datafeedIndices: rt.array(rt.string), -// datafeedState: rt.string, -// description: rt.string, -// earliestTimestampMs: rt.number, -// groups: rt.array(rt.string), -// hasDatafeed: rt.boolean, -// id: rt.string, -// isSingleMetricViewerJob: rt.boolean, -// jobState: rt.string, -// latestResultsTimestampMs: rt.number, -// latestTimestampMs: rt.number, -// memory_status: rt.string, -// nodeName: rt.union([rt.string, rt.undefined]), -// processed_record_count: rt.number, -// fullJob: rt.any, -// auditMessage: rt.any, -// deleting: rt.union([rt.boolean, rt.undefined]), -// })); - -export const fetchJobStatusResponsePayloadRT = rt.any; +const datafeedStateRT = rt.keyof({ + started: null, + stopped: null, +}); + +const jobStateRT = rt.keyof({ + closed: null, + closing: null, + failed: null, + opened: null, + opening: null, +}); + +export const jobSummaryRT = rt.intersection([ + rt.type({ + id: rt.string, + jobState: jobStateRT, + }), + rt.partial({ + datafeedIndices: rt.array(rt.string), + datafeedState: datafeedStateRT, + fullJob: rt.partial({ + finished_time: rt.number, + }), + }), +]); + +export const fetchJobStatusResponsePayloadRT = rt.array(jobSummaryRT); export type FetchJobStatusResponsePayload = rt.TypeOf; diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_jobs.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_jobs.tsx index fa32123e9ae81b..c380160b08d69b 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_jobs.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_jobs.tsx @@ -5,33 +5,23 @@ */ import createContainer from 'constate-latest'; -import { useMemo, useEffect, useState } from 'react'; -import { bucketSpan, getJobId } from '../../../../common/log_analysis'; +import { useEffect, useMemo, useState } from 'react'; + +import { bucketSpan, getDatafeedId, getJobId, JobType } from '../../../../common/log_analysis'; import { useTrackedPromise } from '../../../utils/use_tracked_promise'; +import { callJobsSummaryAPI, FetchJobStatusResponsePayload } from './api/ml_get_jobs_summary_api'; import { callSetupMlModuleAPI, SetupMlModuleResponsePayload } from './api/ml_setup_module_api'; -import { callJobsSummaryAPI } from './api/ml_get_jobs_summary_api'; // combines and abstracts job and datafeed status type JobStatus = | 'unknown' | 'missing' - | 'inconsistent' - | 'created' + | 'initializing' + | 'stopped' | 'started' - | 'opening' - | 'opened' + | 'finished' | 'failed'; -interface AllJobStatuses { - [key: string]: JobStatus; -} - -const getInitialJobStatuses = (): AllJobStatuses => { - return { - logEntryRate: 'unknown', - }; -}; - export const useLogAnalysisJobs = ({ indexPattern, sourceId, @@ -43,14 +33,19 @@ export const useLogAnalysisJobs = ({ spaceId: string; timeField: string; }) => { - const [jobStatus, setJobStatus] = useState(getInitialJobStatuses()); + const [jobStatus, setJobStatus] = useState>({ + 'log-entry-rate': 'unknown', + }); const [hasCompletedSetup, setHasCompletedSetup] = useState(false); const [setupMlModuleRequest, setupMlModule] = useTrackedPromise( { cancelPreviousOn: 'resolution', createPromise: async (start, end) => { - setJobStatus(getInitialJobStatuses()); + setJobStatus(currentJobStatus => ({ + ...currentJobStatus, + 'log-entry-rate': 'initializing', + })); return await callSetupMlModuleAPI( start, end, @@ -62,26 +57,25 @@ export const useLogAnalysisJobs = ({ ); }, onResolve: ({ datafeeds, jobs }: SetupMlModuleResponsePayload) => { - const hasSuccessfullyCreatedJobs = jobs.every(job => job.success); - const hasSuccessfullyStartedDatafeeds = datafeeds.every( - datafeed => datafeed.success && datafeed.started - ); - const hasAnyErrors = - jobs.some(job => !!job.error) || datafeeds.some(datafeed => !!datafeed.error); - setJobStatus(currentJobStatus => ({ ...currentJobStatus, - logEntryRate: hasAnyErrors - ? 'failed' - : hasSuccessfullyCreatedJobs - ? hasSuccessfullyStartedDatafeeds + 'log-entry-rate': + hasSuccessfullyCreatedJob(getJobId(spaceId, sourceId, 'log-entry-rate'))(jobs) && + hasSuccessfullyStartedDatafeed(getDatafeedId(spaceId, sourceId, 'log-entry-rate'))( + datafeeds + ) ? 'started' - : 'failed' - : 'failed', + : 'failed', })); setHasCompletedSetup(true); }, + onReject: () => { + setJobStatus(currentJobStatus => ({ + ...currentJobStatus, + 'log-entry-rate': 'failed', + })); + }, }, [indexPattern, spaceId, sourceId] ); @@ -90,20 +84,19 @@ export const useLogAnalysisJobs = ({ { cancelPreviousOn: 'resolution', createPromise: async () => { - return callJobsSummaryAPI(spaceId, sourceId); + return await callJobsSummaryAPI(spaceId, sourceId); }, onResolve: response => { - if (response && response.length) { - const logEntryRate = response.find( - (job: any) => job.id === getJobId(spaceId, sourceId, 'log-entry-rate') - ); - setJobStatus({ - logEntryRate: logEntryRate ? logEntryRate.jobState : 'unknown', - }); - } + setJobStatus(currentJobStatus => ({ + ...currentJobStatus, + 'log-entry-rate': getJobStatus(getJobId(spaceId, sourceId, 'log-entry-rate'))(response), + })); }, - onReject: error => { - // TODO: Handle errors + onReject: err => { + setJobStatus(currentJobStatus => ({ + ...currentJobStatus, + 'log-entry-rate': 'unknown', + })); }, }, [indexPattern, spaceId, sourceId] @@ -114,11 +107,7 @@ export const useLogAnalysisJobs = ({ }, []); const isSetupRequired = useMemo(() => { - const jobStates = Object.values(jobStatus); - return ( - jobStates.filter(state => ['opened', 'opening', 'created', 'started'].includes(state)) - .length < jobStates.length - ); + return !Object.values(jobStatus).every(state => ['started', 'finished'].includes(state)); }, [jobStatus]); const isLoadingSetupStatus = useMemo(() => fetchJobStatusRequest.state === 'pending', [ @@ -147,3 +136,52 @@ export const useLogAnalysisJobs = ({ }; export const LogAnalysisJobs = createContainer(useLogAnalysisJobs); + +const hasSuccessfullyCreatedJob = (jobId: string) => ( + jobSetupResponses: SetupMlModuleResponsePayload['jobs'] +) => + jobSetupResponses.filter( + jobSetupResponse => + jobSetupResponse.id === jobId && jobSetupResponse.success && !jobSetupResponse.error + ).length > 0; + +const hasSuccessfullyStartedDatafeed = (datafeedId: string) => ( + datafeedSetupResponses: SetupMlModuleResponsePayload['datafeeds'] +) => + datafeedSetupResponses.filter( + datafeedSetupResponse => + datafeedSetupResponse.id === datafeedId && + datafeedSetupResponse.success && + datafeedSetupResponse.started && + !datafeedSetupResponse.error + ).length > 0; + +const getJobStatus = (jobId: string) => (jobSummaries: FetchJobStatusResponsePayload): JobStatus => + jobSummaries + .filter(jobSummary => jobSummary.id === jobId) + .map( + (jobSummary): JobStatus => { + if (jobSummary.jobState === 'failed') { + return 'failed'; + } else if ( + jobSummary.jobState === 'closed' && + jobSummary.datafeedState === 'stopped' && + jobSummary.fullJob && + jobSummary.fullJob.finished_time != null + ) { + return 'finished'; + } else if ( + jobSummary.jobState === 'closed' || + jobSummary.jobState === 'closing' || + jobSummary.datafeedState === 'stopped' + ) { + return 'stopped'; + } else if (jobSummary.jobState === 'opening') { + return 'initializing'; + } else if (jobSummary.jobState === 'opened' && jobSummary.datafeedState === 'started') { + return 'started'; + } + + return 'unknown'; + } + )[0] || 'missing'; diff --git a/x-pack/legacy/plugins/maps/public/register_vis_type_alias.js b/x-pack/legacy/plugins/maps/public/register_vis_type_alias.js index 0799e59bbb0113..8a103a554f7f2c 100644 --- a/x-pack/legacy/plugins/maps/public/register_vis_type_alias.js +++ b/x-pack/legacy/plugins/maps/public/register_vis_type_alias.js @@ -4,11 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { setup } from '../../../../../src/legacy/core_plugins/visualizations/public/np_ready/public/legacy'; +import { setup as visualizationsSetup } from '../../../../../src/legacy/core_plugins/visualizations/public/legacy'; import { i18n } from '@kbn/i18n'; import { APP_ID, APP_ICON, MAP_BASE_URL } from '../common/constants'; -setup.types.visTypeAliasRegistry.add({ +visualizationsSetup.types.visTypeAliasRegistry.add({ aliasUrl: MAP_BASE_URL, name: APP_ID, title: i18n.translate('xpack.maps.visTypeAlias.title', { diff --git a/x-pack/legacy/plugins/ml/public/components/data_recognizer/data_recognizer.d.ts b/x-pack/legacy/plugins/ml/public/components/data_recognizer/data_recognizer.d.ts new file mode 100644 index 00000000000000..c8a7bba2d189f5 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/components/data_recognizer/data_recognizer.d.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 { FC } from 'react'; + +import { IndexPattern } from 'ui/index_patterns'; + +declare const DataRecognizer: FC<{ + indexPattern: IndexPattern; + results: any; + className: string; +}>; diff --git a/x-pack/legacy/plugins/ml/public/components/data_recognizer/index.js b/x-pack/legacy/plugins/ml/public/components/data_recognizer/index.ts similarity index 84% rename from x-pack/legacy/plugins/ml/public/components/data_recognizer/index.js rename to x-pack/legacy/plugins/ml/public/components/data_recognizer/index.ts index 68952807d42402..bb5a2ac4e479ff 100644 --- a/x-pack/legacy/plugins/ml/public/components/data_recognizer/index.js +++ b/x-pack/legacy/plugins/ml/public/components/data_recognizer/index.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ - - import './data_recognizer_directive'; -import './data_recognizer'; + +export { DataRecognizer } from './data_recognizer'; diff --git a/x-pack/legacy/plugins/ml/public/data_frame/common/index.ts b/x-pack/legacy/plugins/ml/public/data_frame/common/index.ts index 8b33eb666c7386..7af8c7ee73cff9 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame/common/index.ts +++ b/x-pack/legacy/plugins/ml/public/data_frame/common/index.ts @@ -37,7 +37,7 @@ export { DATA_FRAME_MODE, DATA_FRAME_TRANSFORM_STATE, } from './transform_stats'; -export { moveToDataFrameTransformList, moveToDataFrameWizard, moveToDiscover } from './navigation'; +export { moveToDataFrameWizard, getDiscoverUrl } from './navigation'; export { getEsAggFromAggConfig, isPivotAggsConfigWithUiSupport, diff --git a/x-pack/legacy/plugins/ml/public/data_frame/common/navigation.test.ts b/x-pack/legacy/plugins/ml/public/data_frame/common/navigation.test.ts new file mode 100644 index 00000000000000..97cb5b4d5d2367 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/data_frame/common/navigation.test.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 { getDiscoverUrl } from './navigation'; + +describe('navigation', () => { + test('getDiscoverUrl should provide encoded url to Discover page', () => { + expect(getDiscoverUrl('farequote-airline', 'http://example.com')).toBe( + 'http://example.com#/discover?_g=()&_a=(index:farequote-airline)' + ); + }); +}); diff --git a/x-pack/legacy/plugins/ml/public/data_frame/common/navigation.ts b/x-pack/legacy/plugins/ml/public/data_frame/common/navigation.ts index 746193f1e1262a..e8726e8c2eedd7 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame/common/navigation.ts +++ b/x-pack/legacy/plugins/ml/public/data_frame/common/navigation.ts @@ -10,11 +10,12 @@ export function moveToDataFrameWizard() { window.location.href = '#/data_frames/new_transform'; } -export function moveToDataFrameTransformList() { - window.location.href = '#/data_frames'; -} - -export function moveToDiscover(indexPatternId: string, baseUrl: string) { +/** + * Gets a url for navigating to Discover page. + * @param indexPatternId Index pattern id. + * @param baseUrl Base url. + */ +export function getDiscoverUrl(indexPatternId: string, baseUrl: string): string { const _g = rison.encode({}); // Add the index pattern ID to the appState part of the URL. @@ -24,5 +25,5 @@ export function moveToDiscover(indexPatternId: string, baseUrl: string) { const hash = `#/discover?_g=${_g}&_a=${_a}`; - window.location.href = `${baseUrl}${hash}`; + return `${baseUrl}${hash}`; } diff --git a/x-pack/legacy/plugins/ml/public/data_frame/pages/data_frame_new_pivot/components/step_create/step_create_form.tsx b/x-pack/legacy/plugins/ml/public/data_frame/pages/data_frame_new_pivot/components/step_create/step_create_form.tsx index 1ce9f5749fc2f3..7c13d0addece9f 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame/pages/data_frame_new_pivot/components/step_create/step_create_form.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame/pages/data_frame_new_pivot/components/step_create/step_create_form.tsx @@ -34,11 +34,7 @@ import { useKibanaContext } from '../../../../../contexts/kibana/use_kibana_cont import { useUiChromeContext } from '../../../../../contexts/ui/use_ui_chrome_context'; import { PROGRESS_JOBS_REFRESH_INTERVAL_MS } from '../../../../../../common/constants/jobs_list'; -import { - getTransformProgress, - moveToDataFrameTransformList, - moveToDiscover, -} from '../../../../common'; +import { getTransformProgress, getDiscoverUrl } from '../../../../common'; export interface StepDetailsExposedState { created: boolean; @@ -367,7 +363,7 @@ export const StepCreateForm: SFC = React.memo( defaultMessage: 'Return to the data frame transform management page.', } )} - onClick={moveToDataFrameTransformList} + href="#/data_frames" /> {started === true && createIndexPattern === true && indexPatternId === undefined && ( @@ -400,7 +396,7 @@ export const StepCreateForm: SFC = React.memo( defaultMessage: 'Use Discover to explore the data frame pivot.', } )} - onClick={() => moveToDiscover(indexPatternId, baseUrl)} + href={getDiscoverUrl(indexPatternId, baseUrl)} /> )} diff --git a/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/components/actions_panel/actions_panel.tsx b/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/components/actions_panel/actions_panel.tsx index 80cb73a1f64ed5..9a291cabf558f9 100644 --- a/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/components/actions_panel/actions_panel.tsx +++ b/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/components/actions_panel/actions_panel.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { FC } from 'react'; +import React, { FC, useState } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; @@ -15,12 +15,22 @@ import { EuiPanel, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui'; import { useUiChromeContext } from '../../../../contexts/ui/use_ui_chrome_context'; import { CreateJobLinkCard } from '../../../../components/create_job_link_card'; +import { DataRecognizer } from '../../../../components/data_recognizer'; interface Props { indexPattern: IndexPattern; } export const ActionsPanel: FC = ({ indexPattern }) => { + const [recognizerResultsCount, setRecognizerResultsCount] = useState(0); + + const recognizerResults = { + count: 0, + onChange() { + setRecognizerResultsCount(recognizerResults.count); + }, + }; + const basePath = useUiChromeContext().getBasePath(); function openAdvancedJobWizard() { @@ -29,6 +39,9 @@ export const ActionsPanel: FC = ({ indexPattern }) => { window.open(`${basePath}/app/ml#/jobs/new_job/advanced?index=${indexPattern}`, '_self'); } + // Note we use display:none for the DataRecognizer section as it needs to be + // passed the recognizerResults object, and then run the recognizer check which + // controls whether the recognizer section is ultimately displayed. return ( @@ -40,6 +53,23 @@ export const ActionsPanel: FC = ({ indexPattern }) => { +
+ +

+ +

+
+ + + +

| null = null; private _existingJobsAndGroups: ExistingJobsAndGroups; private _basicValidations: BasicValidations = { jobId: { valid: true }, @@ -46,6 +46,7 @@ export class JobValidator { bucketSpan: { valid: true }, duplicateDetectors: { valid: true }, }; + private _validating: boolean = false; constructor(jobCreator: JobCreatorType, existingJobsAndGroups: ExistingJobsAndGroups) { this._jobCreator = jobCreator; @@ -54,25 +55,30 @@ export class JobValidator { basic: false, advanced: false, }; - this._validateTimeout = setTimeout(() => {}, 0); this._existingJobsAndGroups = existingJobsAndGroups; } - public validate() { + public validate(callback: () => void) { + this._validating = true; const formattedJobConfig = this._jobCreator.formattedJobJson; - return new Promise((resolve: () => void) => { - // only validate if the config has changed - if (formattedJobConfig !== this._lastJobConfig) { + // only validate if the config has changed + if (formattedJobConfig !== this._lastJobConfig) { + if (this._validateTimeout !== null) { + // clear any previous on going validation timeouts clearTimeout(this._validateTimeout); - this._lastJobConfig = formattedJobConfig; - this._validateTimeout = setTimeout(() => { - this._runBasicValidation(); - resolve(); - }, VALIDATION_DELAY_MS); - } else { - resolve(); } - }); + this._lastJobConfig = formattedJobConfig; + this._validateTimeout = setTimeout(() => { + this._runBasicValidation(); + this._validating = false; + this._validateTimeout = null; + callback(); + }, VALIDATION_DELAY_MS); + } else { + // _validating is still true if there is a previous validation timeout on going. + this._validating = this._validateTimeout !== null; + } + callback(); } private _resetBasicValidations() { @@ -135,4 +141,8 @@ export class JobValidator { public set advancedValid(valid: boolean) { this._validationSummary.advanced = valid; } + + public get validating(): boolean { + return this._validating; + } } diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/job_details_step/job_details.tsx b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/job_details_step/job_details.tsx index 244608f400f55d..6fafadf6c29b90 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/job_details_step/job_details.tsx +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/job_details_step/job_details.tsx @@ -37,7 +37,8 @@ export const JobDetailsStep: FC = ({ const active = jobValidator.jobId.valid && jobValidator.modelMemoryLimit.valid && - jobValidator.groupIds.valid; + jobValidator.groupIds.valid && + jobValidator.validating === false; setNextActive(active); }, [jobValidatorUpdated]); diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/pick_fields.tsx b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/pick_fields.tsx index 712d49159b5420..ca46b88f1bc2cb 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/pick_fields.tsx +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/pick_fields.tsx @@ -30,7 +30,8 @@ export const PickFieldsStep: FC = ({ setCurrentStep, isCurrentStep }) const active = jobCreator.detectors.length > 0 && jobValidator.bucketSpan.valid && - jobValidator.duplicateDetectors.valid; + jobValidator.duplicateDetectors.valid && + jobValidator.validating === false; setNextActive(active); }, [jobValidatorUpdated]); diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/new_job/page.tsx b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/new_job/page.tsx index 66ccd76d048919..039e55cc29aa12 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/new_job/page.tsx +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/new_job/page.tsx @@ -63,8 +63,22 @@ export const Page: FC = ({ existingJobsAndGroups, jobType }) => { if (skipTimeRangeStep === false) { jobCreator.jobId = ''; } + mlJobService.tempJobCloningObjects.skipTimeRangeStep = false; mlJobService.tempJobCloningObjects.job = undefined; + + if ( + mlJobService.tempJobCloningObjects.start !== undefined && + mlJobService.tempJobCloningObjects.end !== undefined + ) { + // auto select the start and end dates for the time range picker + jobCreator.setTimeRange( + mlJobService.tempJobCloningObjects.start, + mlJobService.tempJobCloningObjects.end + ); + mlJobService.tempJobCloningObjects.start = undefined; + mlJobService.tempJobCloningObjects.end = undefined; + } } else { jobCreator.bucketSpan = DEFAULT_BUCKET_SPAN; @@ -80,7 +94,7 @@ export const Page: FC = ({ existingJobsAndGroups, jobType }) => { jobCreator.modelPlot = true; } - if (kibanaContext.currentSavedSearch.id === undefined) { + if (kibanaContext.currentSavedSearch.id !== undefined) { // Jobs created from saved searches cannot be cloned in the wizard as the // ML job config holds no reference to the saved search ID. jobCreator.createdBy = null; diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/new_job/wizard.tsx b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/new_job/wizard.tsx index a6ce188ddbad93..fcd1c4808a30ac 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/new_job/wizard.tsx +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/new_job/wizard.tsx @@ -86,11 +86,9 @@ export const Wizard: FC = ({ ); useEffect(() => { - // IIFE to run the validation. the useEffect callback can't be async - (async () => { - await jobValidator.validate(); + jobValidator.validate(() => { setJobValidatorUpdate(jobValidatorUpdated); - })(); + }); // if the job config has changed, reset the highestStep // compare a stringified config to ensure the configs have actually changed diff --git a/x-pack/legacy/plugins/ml/public/services/job_service.d.ts b/x-pack/legacy/plugins/ml/public/services/job_service.d.ts index d32e55a58f3a49..f94c0e866451d2 100644 --- a/x-pack/legacy/plugins/ml/public/services/job_service.d.ts +++ b/x-pack/legacy/plugins/ml/public/services/job_service.d.ts @@ -14,6 +14,8 @@ declare interface JobService { tempJobCloningObjects: { job: any; skipTimeRangeStep: boolean; + start?: number; + end?: number; }; skipTimeRangeStep: boolean; saveNewJob(job: any): Promise; diff --git a/x-pack/legacy/plugins/ml/public/services/job_service.js b/x-pack/legacy/plugins/ml/public/services/job_service.js index cdb7b430def659..f7b01e983fe06d 100644 --- a/x-pack/legacy/plugins/ml/public/services/job_service.js +++ b/x-pack/legacy/plugins/ml/public/services/job_service.js @@ -34,6 +34,8 @@ class JobService { this.tempJobCloningObjects = { job: undefined, skipTimeRangeStep: false, + start: undefined, + end: undefined, }; this.jobs = []; diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/__tests__/execute_job.js b/x-pack/legacy/plugins/reporting/export_types/csv/server/__tests__/execute_job.js index 3d2c7c3b0ae3c6..b4c46f3539640a 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/__tests__/execute_job.js +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/__tests__/execute_job.js @@ -474,7 +474,8 @@ describe('CSV Execute Job', function () { }); }); - describe('cancellation', function () { + // FLAKY: https://github.com/elastic/kibana/issues/43069 + describe.skip('cancellation', function () { const scrollId = getRandomScrollId(); beforeEach(function () { diff --git a/x-pack/legacy/plugins/siem/cypress/README.md b/x-pack/legacy/plugins/siem/cypress/README.md index 996d23ab5fab28..d3313ef64e4490 100644 --- a/x-pack/legacy/plugins/siem/cypress/README.md +++ b/x-pack/legacy/plugins/siem/cypress/README.md @@ -201,7 +201,7 @@ CYPRESS_baseUrl=http://localhost:5601 CYPRESS_ELASTICSEARCH_USERNAME=elastic CYP When Cypress tests are run on the command line via `yarn cypress:run`, reporting artifacts are generated under the `target` directory in the root -of the Kibana, as detailed for each artifact type in the sections bleow. +of the Kibana, as detailed for each artifact type in the sections below. ### HTML Reports diff --git a/x-pack/legacy/plugins/siem/cypress/integration/lib/ml_conditional_links/index.ts b/x-pack/legacy/plugins/siem/cypress/integration/lib/ml_conditional_links/index.ts new file mode 100644 index 00000000000000..d261832a92f2a9 --- /dev/null +++ b/x-pack/legacy/plugins/siem/cypress/integration/lib/ml_conditional_links/index.ts @@ -0,0 +1,76 @@ +/* + * 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. + */ + +/* + * These links are for different test scenarios that try and capture different drill downs into + * ml-network and ml-hosts and are of the flavor of testing: + * A filter being null: (filterQuery:!n) + * A filter being set with single values: filterQuery:(expression:%27process.name%20:%20%22conhost.exe%22%27,kind:kuery) + * A filter being set with multiple values: filterQuery:(expression:%27process.name%20:%20%22conhost.exe,sc.exe%22%27,kind:kuery) + * A filter containing variables not replaced: filterQuery:(expression:%27process.name%20:%20%$process.name$%22%27,kind:kuery) + * + * In different combination with: + * network not being set: $ip$ + * host not being set: $host.name$ + * ...or... + * network being set normally: 127.0.0.1 + * host being set normally: suricata-iowa + * ...or... + * network having multiple values: 127.0.0.1,127.0.0.2 + * host having multiple values: suricata-iowa,siem-windows + */ + +// Single IP with a null for the filterQuery: +export const mlNetworkSingleIpNullFilterQuery = + "/app/siem#/ml-network/ip/127.0.0.1?kqlQuery=(filterQuery:!n,queryLocation:network.details,type:details)&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-28T11:00:00.000Z',kind:absolute,to:'2019-08-28T13:59:59.999Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-28T11:00:00.000Z',kind:absolute,to:'2019-08-28T13:59:59.999Z')))"; + +// Single IP with a value for the filterQuery: +export const mlNetworkSingleIpFilterQuery = + "/app/siem#/ml-network/ip/127.0.0.1?kqlQuery=(filterQuery:(expression:'process.name%20:%20%22conhost.exe,sc.exe%22',kind:kuery),queryLocation:network.details,type:details)&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-28T11:00:00.000Z',kind:absolute,to:'2019-08-28T13:59:59.999Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-28T11:00:00.000Z',kind:absolute,to:'2019-08-28T13:59:59.999Z')))"; + +// Multiple IPs with a null for the filterQuery: +export const mlNetworkMultipleIpNullFilterQuery = + "/app/siem#/ml-network/ip/127.0.0.1,127.0.0.2?kqlQuery=(filterQuery:!n,queryLocation:network.details,type:details)&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-28T11:00:00.000Z',kind:absolute,to:'2019-08-28T13:59:59.999Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-28T11:00:00.000Z',kind:absolute,to:'2019-08-28T13:59:59.999Z')))"; + +// Multiple IPs with a value for the filterQuery: +export const mlNetworkMultipleIpFilterQuery = + "/app/siem#/ml-network/ip/127.0.0.1,127.0.0.2?kqlQuery=(filterQuery:(expression:'process.name%20:%20%22conhost.exe,sc.exe%22',kind:kuery),queryLocation:network.details,type:details)&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-28T11:00:00.000Z',kind:absolute,to:'2019-08-28T13:59:59.999Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-28T11:00:00.000Z',kind:absolute,to:'2019-08-28T13:59:59.999Z')))"; + +// $ip$ with a null filterQuery: +export const mlNetworkNullFilterQuery = + "/app/siem#/ml-network/ip/$ip$?kqlQuery=(filterQuery:!n,queryLocation:network.details,type:details)&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-28T11:00:00.000Z',kind:absolute,to:'2019-08-28T13:59:59.999Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-28T11:00:00.000Z',kind:absolute,to:'2019-08-28T13:59:59.999Z')))"; + +// $ip$ with a value for the filterQuery: +export const mlNetworkFilterQuery = + "/app/siem#/ml-network/ip/$ip$?kqlQuery=(filterQuery:(expression:'process.name%20:%20%22conhost.exe,sc.exe%22',kind:kuery),queryLocation:network.details,type:details)&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-28T11:00:00.000Z',kind:absolute,to:'2019-08-28T13:59:59.999Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-28T11:00:00.000Z',kind:absolute,to:'2019-08-28T13:59:59.999Z')))"; + +// Single host name with a null for the filterQuery: +export const mlHostSingleHostNullFilterQuery = + "/app/siem#/ml-hosts/siem-windows?_g=()&kqlQuery=(filterQuery:!n,queryLocation:hosts.details,type:details)&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-06-06T06:00:00.000Z',kind:absolute,to:'2019-06-07T05:59:59.999Z')),timeline:(linkTo:!(global),timerange:(from:'2019-06-06T06:00:00.000Z',kind:absolute,to:'2019-06-07T05:59:59.999Z')))"; + +// Single host name with a variable in the filterQuery +export const mlHostSingleHostFilterQueryVariable = + "/app/siem#/ml-hosts/siem-windows?_g=()&kqlQuery=(filterQuery:(expression:'process.name%20:%20%22$process.name$%22',kind:kuery),queryLocation:hosts.details,type:details)&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-06-06T06:00:00.000Z',kind:absolute,to:'2019-06-07T05:59:59.999Z')),timeline:(linkTo:!(global),timerange:(from:'2019-06-06T06:00:00.000Z',kind:absolute,to:'2019-06-07T05:59:59.999Z')))"; + +// Single host name with a value for filterQuery: +export const mlHostSingleHostFilterQuery = + "/app/siem#/ml-hosts/siem-windows?_g=()&kqlQuery=(filterQuery:(expression:'process.name%20:%20%22conhost.exe,sc.exe%22',kind:kuery),queryLocation:hosts.details,type:details)&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-06-06T06:00:00.000Z',kind:absolute,to:'2019-06-07T05:59:59.999Z')),timeline:(linkTo:!(global),timerange:(from:'2019-06-06T06:00:00.000Z',kind:absolute,to:'2019-06-07T05:59:59.999Z')))"; + +// Multiple host names with null for filterQuery +export const mlHostMultiHostNullFilterQuery = + "/app/siem#/ml-hosts/siem-windows,siem-suricata?_g=()&kqlQuery=(filterQuery:!n,queryLocation:hosts.details,type:details)&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-06-06T06:00:00.000Z',kind:absolute,to:'2019-06-07T05:59:59.999Z')),timeline:(linkTo:!(global),timerange:(from:'2019-06-06T06:00:00.000Z',kind:absolute,to:'2019-06-07T05:59:59.999Z')))"; + +// Multiple host names with a value for filterQuery +export const mlHostMultiHostFilterQuery = + "/app/siem#/ml-hosts/siem-windows,siem-suricata?_g=()&kqlQuery=(filterQuery:(expression:'process.name%20:%20%22conhost.exe,sc.exe%22',kind:kuery),queryLocation:hosts.details,type:details)&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-06-06T06:00:00.000Z',kind:absolute,to:'2019-06-07T05:59:59.999Z')),timeline:(linkTo:!(global),timerange:(from:'2019-06-06T06:00:00.000Z',kind:absolute,to:'2019-06-07T05:59:59.999Z')))"; + +// Undefined/null host name with a null for the KQL: +export const mlHostVariableHostNullFilterQuery = + "/app/siem#/ml-hosts/$host.name$?_g=()&kqlQuery=(filterQuery:!n,queryLocation:hosts.details,type:details)&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-06-06T06:00:00.000Z',kind:absolute,to:'2019-06-07T05:59:59.999Z')),timeline:(linkTo:!(global),timerange:(from:'2019-06-06T06:00:00.000Z',kind:absolute,to:'2019-06-07T05:59:59.999Z')))"; + +// Undefined/null host name but with a value for filterQuery +export const mlHostVariableHostFilterQuery = + "/app/siem#/ml-hosts/$host.name$?_g=()&kqlQuery=(filterQuery:(expression:'process.name%20:%20%22conhost.exe,sc.exe%22',kind:kuery),queryLocation:hosts.details,type:details)&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-06-06T06:00:00.000Z',kind:absolute,to:'2019-06-07T05:59:59.999Z')),timeline:(linkTo:!(global),timerange:(from:'2019-06-06T06:00:00.000Z',kind:absolute,to:'2019-06-07T05:59:59.999Z')))"; diff --git a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/ml_conditional_links/ml_conditional_links.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/ml_conditional_links/ml_conditional_links.spec.ts new file mode 100644 index 00000000000000..a178f6364d54cf --- /dev/null +++ b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/ml_conditional_links/ml_conditional_links.spec.ts @@ -0,0 +1,206 @@ +/* + * 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 { logout } from '../../lib/logout'; +import { + mlNetworkSingleIpNullFilterQuery, + mlNetworkSingleIpFilterQuery, + mlNetworkMultipleIpNullFilterQuery, + mlNetworkMultipleIpFilterQuery, + mlNetworkNullFilterQuery, + mlNetworkFilterQuery, + mlHostSingleHostNullFilterQuery, + mlHostSingleHostFilterQueryVariable, + mlHostSingleHostFilterQuery, + mlHostMultiHostNullFilterQuery, + mlHostMultiHostFilterQuery, + mlHostVariableHostNullFilterQuery, + mlHostVariableHostFilterQuery, +} from '../../lib/ml_conditional_links'; +import { loginAndWaitForPage } from '../../lib/util/helpers'; +import { KQL_INPUT } from '../../lib/url_state'; + +describe('ml conditional links', () => { + afterEach(() => { + return logout(); + }); + + it('sets the KQL from a single IP with a value for the filterQuery', () => { + loginAndWaitForPage(mlNetworkSingleIpFilterQuery); + cy.get(KQL_INPUT, { timeout: 5000 }).should( + 'have.attr', + 'value', + '(process.name: "conhost.exe" or process.name: "sc.exe")' + ); + }); + + it('sets the KQL from a multiple IPs with a null for the filterQuery', () => { + loginAndWaitForPage(mlNetworkMultipleIpNullFilterQuery); + cy.get(KQL_INPUT, { timeout: 5000 }).should( + 'have.attr', + 'value', + '((source.ip: "127.0.0.1" or destination.ip: "127.0.0.1") or (source.ip: "127.0.0.2" or destination.ip: "127.0.0.2"))' + ); + }); + + it('sets the KQL from a multiple IPs with a value for the filterQuery', () => { + loginAndWaitForPage(mlNetworkMultipleIpFilterQuery); + cy.get(KQL_INPUT, { timeout: 5000 }).should( + 'have.attr', + 'value', + '((source.ip: "127.0.0.1" or destination.ip: "127.0.0.1") or (source.ip: "127.0.0.2" or destination.ip: "127.0.0.2")) and ((process.name: "conhost.exe" or process.name: "sc.exe"))' + ); + }); + + it('sets the KQL from a $ip$ with a value for the filterQuery', () => { + loginAndWaitForPage(mlNetworkFilterQuery); + cy.get(KQL_INPUT, { timeout: 5000 }).should( + 'have.attr', + 'value', + '(process.name: "conhost.exe" or process.name: "sc.exe")' + ); + }); + + it('sets the KQL from a single host name with a value for filterQuery', () => { + loginAndWaitForPage(mlHostSingleHostFilterQuery); + cy.get(KQL_INPUT, { timeout: 5000 }).should( + 'have.attr', + 'value', + '(process.name: "conhost.exe" or process.name: "sc.exe")' + ); + }); + + it('sets the KQL from a multiple host names with null for filterQuery', () => { + loginAndWaitForPage(mlHostMultiHostNullFilterQuery); + cy.get(KQL_INPUT, { timeout: 5000 }).should( + 'have.attr', + 'value', + '(host.name: "siem-windows" or host.name: "siem-suricata")' + ); + }); + + it('sets the KQL from a multiple host names with a value for filterQuery', () => { + loginAndWaitForPage(mlHostMultiHostFilterQuery); + cy.get(KQL_INPUT, { timeout: 5000 }).should( + 'have.attr', + 'value', + '(host.name: "siem-windows" or host.name: "siem-suricata") and ((process.name: "conhost.exe" or process.name: "sc.exe"))' + ); + }); + + it('sets the KQL from a undefined/null host name but with a value for filterQuery', () => { + loginAndWaitForPage(mlHostVariableHostFilterQuery); + cy.get(KQL_INPUT, { timeout: 5000 }).should( + 'have.attr', + 'value', + '(process.name: "conhost.exe" or process.name: "sc.exe")' + ); + }); + + it('redirects from a single IP with a null for the filterQuery', () => { + loginAndWaitForPage(mlNetworkSingleIpNullFilterQuery); + cy.url().should( + 'equal', + 'http://localhost:5601/app/siem#/network/ip/127.0.0.1?timerange=(global:(linkTo:!(timeline),timerange:(from:1566990000000,kind:absolute,to:1567000799999)),timeline:(linkTo:!(global),timerange:(from:1566990000000,kind:absolute,to:1567000799999)))' + ); + }); + + it('redirects from a single IP with a value for the filterQuery', () => { + loginAndWaitForPage(mlNetworkSingleIpFilterQuery); + cy.url().should( + 'equal', + "http://localhost:5601/app/siem#/network/ip/127.0.0.1?kqlQuery=(filterQuery:(expression:'(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)',kind:kuery),queryLocation:network.details)&timerange=(global:(linkTo:!(timeline),timerange:(from:1566990000000,kind:absolute,to:1567000799999)),timeline:(linkTo:!(global),timerange:(from:1566990000000,kind:absolute,to:1567000799999)))" + ); + }); + + it('redirects from a multiple IPs with a null for the filterQuery', () => { + loginAndWaitForPage(mlNetworkMultipleIpNullFilterQuery); + cy.url().should( + 'equal', + "http://localhost:5601/app/siem#/network?kqlQuery=(filterQuery:(expression:'((source.ip:%20%22127.0.0.1%22%20or%20destination.ip:%20%22127.0.0.1%22)%20or%20(source.ip:%20%22127.0.0.2%22%20or%20destination.ip:%20%22127.0.0.2%22))'),queryLocation:network.page)&timerange=(global:(linkTo:!(timeline),timerange:(from:1566990000000,kind:absolute,to:1567000799999)),timeline:(linkTo:!(global),timerange:(from:1566990000000,kind:absolute,to:1567000799999)))" + ); + }); + + it('redirects from a multiple IPs with a value for the filterQuery', () => { + loginAndWaitForPage(mlNetworkMultipleIpFilterQuery); + cy.url().should( + 'equal', + "http://localhost:5601/app/siem#/network?kqlQuery=(filterQuery:(expression:'((source.ip:%20%22127.0.0.1%22%20or%20destination.ip:%20%22127.0.0.1%22)%20or%20(source.ip:%20%22127.0.0.2%22%20or%20destination.ip:%20%22127.0.0.2%22))%20and%20((process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22))',kind:kuery),queryLocation:network.page)&timerange=(global:(linkTo:!(timeline),timerange:(from:1566990000000,kind:absolute,to:1567000799999)),timeline:(linkTo:!(global),timerange:(from:1566990000000,kind:absolute,to:1567000799999)))" + ); + }); + + it('redirects from a $ip$ with a null filterQuery', () => { + loginAndWaitForPage(mlNetworkNullFilterQuery); + cy.url().should( + 'equal', + 'http://localhost:5601/app/siem#/network?timerange=(global:(linkTo:!(timeline),timerange:(from:1566990000000,kind:absolute,to:1567000799999)),timeline:(linkTo:!(global),timerange:(from:1566990000000,kind:absolute,to:1567000799999)))' + ); + }); + + it('redirects from a $ip$ with a value for the filterQuery', () => { + loginAndWaitForPage(mlNetworkFilterQuery); + cy.url().should( + 'equal', + "http://localhost:5601/app/siem#/network?kqlQuery=(filterQuery:(expression:'(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)',kind:kuery),queryLocation:network.page)&timerange=(global:(linkTo:!(timeline),timerange:(from:1566990000000,kind:absolute,to:1567000799999)),timeline:(linkTo:!(global),timerange:(from:1566990000000,kind:absolute,to:1567000799999)))" + ); + }); + + it('redirects from a single host name with a null for the filterQuery', () => { + loginAndWaitForPage(mlHostSingleHostNullFilterQuery); + cy.url().should( + 'equal', + 'http://localhost:5601/app/siem#/hosts/siem-windows/anomalies?_g=()&timerange=(global:(linkTo:!(timeline),timerange:(from:1559800800000,kind:absolute,to:1559887199999)),timeline:(linkTo:!(global),timerange:(from:1559800800000,kind:absolute,to:1559887199999)))' + ); + }); + + it('redirects from a host name with a variable in the filterQuery', () => { + loginAndWaitForPage(mlHostSingleHostFilterQueryVariable); + cy.url().should( + 'equal', + 'http://localhost:5601/app/siem#/hosts/siem-windows/anomalies?_g=()&timerange=(global:(linkTo:!(timeline),timerange:(from:1559800800000,kind:absolute,to:1559887199999)),timeline:(linkTo:!(global),timerange:(from:1559800800000,kind:absolute,to:1559887199999)))' + ); + }); + + it('redirects from a single host name with a value for filterQuery', () => { + loginAndWaitForPage(mlHostSingleHostFilterQuery); + cy.url().should( + 'equal', + "http://localhost:5601/app/siem#/hosts/siem-windows/anomalies?_g=()&kqlQuery=(filterQuery:(expression:'(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)',kind:kuery),queryLocation:hosts.details)&timerange=(global:(linkTo:!(timeline),timerange:(from:1559800800000,kind:absolute,to:1559887199999)),timeline:(linkTo:!(global),timerange:(from:1559800800000,kind:absolute,to:1559887199999)))" + ); + }); + + it('redirects from a multiple host names with null for filterQuery', () => { + loginAndWaitForPage(mlHostMultiHostNullFilterQuery); + cy.url().should( + 'equal', + "http://localhost:5601/app/siem#/hosts/anomalies?_g=()&kqlQuery=(filterQuery:(expression:'(host.name:%20%22siem-windows%22%20or%20host.name:%20%22siem-suricata%22)'),queryLocation:hosts.page)&timerange=(global:(linkTo:!(timeline),timerange:(from:1559800800000,kind:absolute,to:1559887199999)),timeline:(linkTo:!(global),timerange:(from:1559800800000,kind:absolute,to:1559887199999)))" + ); + }); + + it('redirects from a multiple host names with a value for filterQuery', () => { + loginAndWaitForPage(mlHostMultiHostFilterQuery); + cy.url().should( + 'equal', + "http://localhost:5601/app/siem#/hosts/anomalies?_g=()&kqlQuery=(filterQuery:(expression:'(host.name:%20%22siem-windows%22%20or%20host.name:%20%22siem-suricata%22)%20and%20((process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22))',kind:kuery),queryLocation:hosts.page)&timerange=(global:(linkTo:!(timeline),timerange:(from:1559800800000,kind:absolute,to:1559887199999)),timeline:(linkTo:!(global),timerange:(from:1559800800000,kind:absolute,to:1559887199999)))" + ); + }); + + it('redirects from a undefined/null host name with a null for the KQL', () => { + loginAndWaitForPage(mlHostVariableHostNullFilterQuery); + cy.url().should( + 'equal', + 'http://localhost:5601/app/siem#/hosts/anomalies?_g=()&timerange=(global:(linkTo:!(timeline),timerange:(from:1559800800000,kind:absolute,to:1559887199999)),timeline:(linkTo:!(global),timerange:(from:1559800800000,kind:absolute,to:1559887199999)))' + ); + }); + + it('redirects from a undefined/null host name but with a value for filterQuery', () => { + loginAndWaitForPage(mlHostVariableHostFilterQuery); + cy.url().should( + 'equal', + "http://localhost:5601/app/siem#/hosts/anomalies?_g=()&kqlQuery=(filterQuery:(expression:'(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)',kind:kuery),queryLocation:hosts.page)&timerange=(global:(linkTo:!(timeline),timerange:(from:1559800800000,kind:absolute,to:1559887199999)),timeline:(linkTo:!(global),timerange:(from:1559800800000,kind:absolute,to:1559887199999)))" + ); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/components/ml/conditional_links/add_entities_to_kql.test.ts b/x-pack/legacy/plugins/siem/public/components/ml/conditional_links/add_entities_to_kql.test.ts index c039cc2f88646e..bab39a437b0df7 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/conditional_links/add_entities_to_kql.test.ts +++ b/x-pack/legacy/plugins/siem/public/components/ml/conditional_links/add_entities_to_kql.test.ts @@ -17,6 +17,7 @@ describe('add_entities_to_kql', () => { afterAll(() => { console.log = originalError; }); + describe('#entityToKql', () => { test('returns empty string with no entity names defined and an empty entity string', () => { const entity = entityToKql([], ''); @@ -165,5 +166,10 @@ describe('add_entities_to_kql', () => { '(filterQuery:(expression:\'(host.name: "host-name-1" or host.name: "host-name-2") and (process.name : "")\',kind:kuery))' ); }); + + test('returns kql expression with a null filterQuery', () => { + const entity = addEntitiesToKql(['host.name'], ['host-1'], '(filterQuery:!n)'); + expect(entity).toEqual('(filterQuery:(expression:\'(host.name: "host-1")\'))'); + }); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/ml/conditional_links/add_entities_to_kql.ts b/x-pack/legacy/plugins/siem/public/components/ml/conditional_links/add_entities_to_kql.ts index c6c7704aa09994..490bb8fd324c30 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/conditional_links/add_entities_to_kql.ts +++ b/x-pack/legacy/plugins/siem/public/components/ml/conditional_links/add_entities_to_kql.ts @@ -52,6 +52,10 @@ export const addEntitiesToKql = ( } return encode(value); } + } else if (value.filterQuery == null) { + const entitiesKql = entitiesToKql(entityNames, entities); + value.filterQuery = { expression: `(${entitiesKql})` }; + return encode(value); } } return kqlQuery; diff --git a/x-pack/legacy/plugins/siem/public/components/ml/conditional_links/ml_host_conditional_container.tsx b/x-pack/legacy/plugins/siem/public/components/ml/conditional_links/ml_host_conditional_container.tsx index eec33a657c6ee2..f2e60a5fb86e9c 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/conditional_links/ml_host_conditional_container.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml/conditional_links/ml_host_conditional_container.tsx @@ -6,31 +6,27 @@ import React from 'react'; -import { match as RouteMatch, Redirect, Route, Switch } from 'react-router-dom'; +import { Redirect, Route, Switch, RouteComponentProps } from 'react-router-dom'; import { QueryString } from 'ui/utils/query_string'; -import { pure } from 'recompose'; import { addEntitiesToKql } from './add_entities_to_kql'; import { replaceKQLParts } from './replace_kql_parts'; import { emptyEntity, multipleEntities, getMultipleEntities } from './entity_helpers'; import { replaceKqlQueryLocationForHostPage } from './replace_kql_query_location_for_host_page'; -interface MlHostConditionalProps { - match: RouteMatch<{}>; - location: Location; -} - interface QueryStringType { '?_g': string; kqlQuery: string | null; timerange: string | null; } -export const MlHostConditionalContainer = pure(({ match }) => ( +type MlHostConditionalProps = Partial> & { url: string }; + +export const MlHostConditionalContainer = React.memo(({ url }) => ( { const queryStringDecoded: QueryStringType = QueryString.decode( location.search.substring(1) @@ -43,7 +39,7 @@ export const MlHostConditionalContainer = pure(({ match }} /> (({ match ); } const reEncoded = QueryString.encode(queryStringDecoded); - return ; + return ; } else if (multipleEntities(hostName)) { const hosts: string[] = getMultipleEntities(hostName); if (queryStringDecoded.kqlQuery != null) { @@ -77,10 +73,10 @@ export const MlHostConditionalContainer = pure(({ match ); } const reEncoded = QueryString.encode(queryStringDecoded); - return ; + return ; } else { const reEncoded = QueryString.encode(queryStringDecoded); - return ; + return ; } }} /> diff --git a/x-pack/legacy/plugins/siem/public/components/ml/conditional_links/ml_network_conditional_container.tsx b/x-pack/legacy/plugins/siem/public/components/ml/conditional_links/ml_network_conditional_container.tsx index 4089689fe9b689..200a00b6dd5626 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/conditional_links/ml_network_conditional_container.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml/conditional_links/ml_network_conditional_container.tsx @@ -6,31 +6,27 @@ import React from 'react'; -import { match as RouteMatch, Redirect, Route, Switch } from 'react-router-dom'; +import { Redirect, Route, Switch, RouteComponentProps } from 'react-router-dom'; import { QueryString } from 'ui/utils/query_string'; -import { pure } from 'recompose'; import { addEntitiesToKql } from './add_entities_to_kql'; import { replaceKQLParts } from './replace_kql_parts'; import { emptyEntity, getMultipleEntities, multipleEntities } from './entity_helpers'; import { replaceKqlQueryLocationForNetworkPage } from './replace_kql_query_location_for_network_page'; -interface MlNetworkConditionalProps { - match: RouteMatch<{}>; - location: Location; -} - interface QueryStringType { '?_g': string; kqlQuery: string | null; timerange: string | null; } -export const MlNetworkConditionalContainer = pure(({ match }) => ( +type MlNetworkConditionalProps = Partial> & { url: string }; + +export const MlNetworkConditionalContainer = React.memo(({ url }) => ( { const queryStringDecoded: QueryStringType = QueryString.decode( location.search.substring(1) @@ -43,7 +39,7 @@ export const MlNetworkConditionalContainer = pure(({ }} /> ( /> } /> - - + ( + + )} + /> + ( + + )} + /> diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 208bb36b58ac1e..44a1009707d89a 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -4306,6 +4306,7 @@ "xpack.code.repoItem.cancelButtonText": "否,取消", "xpack.code.repoItem.cloneErrorText": "克隆错误", "xpack.code.repoItem.cloningText": "克隆中...", + "xpack.code.repoItem.updatingText": "更新中...", "xpack.code.repoItem.confirmButtonText": "是,继续", "xpack.code.repoItem.deleteButtonLabel": "删除", "xpack.code.repoItem.deleteConfirmTitle": "确认删除仓库?", diff --git a/x-pack/test/functional/apps/graph/graph.ts b/x-pack/test/functional/apps/graph/graph.ts index 6db62ea11d995e..97bb7c1b9918b3 100644 --- a/x-pack/test/functional/apps/graph/graph.ts +++ b/x-pack/test/functional/apps/graph/graph.ts @@ -13,7 +13,8 @@ export default function({ getService, getPageObjects }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const browser = getService('browser'); - describe('graph', function() { + // FLAKY: https://github.com/elastic/kibana/issues/45317 + describe.skip('graph', function() { before(async () => { await browser.setWindowSize(1600, 1000); log.debug('load graph/secrepo data');