From 87c7289b745642832b093f8685daa8ab6a0a872c Mon Sep 17 00:00:00 2001 From: Tyler Smalley Date: Tue, 2 Nov 2021 09:17:16 -0700 Subject: [PATCH 01/41] [ci] Increase heap of Jest Signed-off-by: Tyler Smalley --- .buildkite/scripts/steps/test/jest_parallel.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.buildkite/scripts/steps/test/jest_parallel.sh b/.buildkite/scripts/steps/test/jest_parallel.sh index d3ee75b7add4a..963cfae581b1f 100755 --- a/.buildkite/scripts/steps/test/jest_parallel.sh +++ b/.buildkite/scripts/steps/test/jest_parallel.sh @@ -14,7 +14,7 @@ exitCode=0 find src x-pack packages -name jest.config.js -not -path "*/__fixtures__/*" | sort | while read config; do if [ "$(($i % $JOB_COUNT))" -eq $JOB ]; then echo "--- $ node scripts/jest --config $config" - node --max-old-space-size=5632 ./node_modules/.bin/jest --config=$config --runInBand --coverage=false + node --max-old-space-size=7168 ./node_modules/.bin/jest --config=$config --runInBand --coverage=false if [ $? -ne 0 ]; then exitCode=10 From e53771df2dbba9615dc6a9dab5fa9f85ad700901 Mon Sep 17 00:00:00 2001 From: Matthew Kime Date: Tue, 2 Nov 2021 11:34:59 -0500 Subject: [PATCH 02/41] [docs] index pattern => data view (#110421) (#115497) * [user docs - index patterns] index pattern => data view (#110421) --- ...-patterns.asciidoc => data-views.asciidoc} | 78 +++++++++---------- docs/concepts/index.asciidoc | 16 ++-- docs/concepts/set-time-filter.asciidoc | 2 +- docs/discover/search.asciidoc | 2 +- docs/maps/search.asciidoc | 2 +- docs/redirects.asciidoc | 5 ++ docs/setup/connect-to-elasticsearch.asciidoc | 2 +- docs/user/canvas.asciidoc | 2 +- docs/user/dashboard/dashboard.asciidoc | 4 +- docs/user/dashboard/lens-advanced.asciidoc | 2 +- docs/user/dashboard/tsvb.asciidoc | 2 +- docs/user/discover.asciidoc | 2 +- docs/user/graph/getting-started.asciidoc | 2 +- 13 files changed, 63 insertions(+), 58 deletions(-) rename docs/concepts/{index-patterns.asciidoc => data-views.asciidoc} (55%) diff --git a/docs/concepts/index-patterns.asciidoc b/docs/concepts/data-views.asciidoc similarity index 55% rename from docs/concepts/index-patterns.asciidoc rename to docs/concepts/data-views.asciidoc index b8a10572fd8eb..7eb95405db6bc 100644 --- a/docs/concepts/index-patterns.asciidoc +++ b/docs/concepts/data-views.asciidoc @@ -1,45 +1,45 @@ -[[index-patterns]] -=== Create an index pattern +[[data-views]] +=== Create a data view -{kib} requires an index pattern to access the {es} data that you want to explore. -An index pattern selects the data to use and allows you to define properties of the fields. +{kib} requires a data view to access the {es} data that you want to explore. +A data view selects the data to use and allows you to define properties of the fields. -An index pattern can point to one or more indices, {ref}/data-streams.html[data stream], or {ref}/alias.html[index aliases]. -For example, an index pattern can point to your log data from yesterday, +A data view can point to one or more indices, {ref}/data-streams.html[data stream], or {ref}/alias.html[index aliases]. +For example, a data view can point to your log data from yesterday, or all indices that contain your data. [float] -[[index-patterns-read-only-access]] +[[data-views-read-only-access]] === Required permissions -* Access to *Index Patterns* requires the <> -`Index Pattern Management`. +* Access to *Data Views* requires the <> +`Data View Management`. -* To create an index pattern, you must have the <> +* To create a data view, you must have the <> `view_index_metadata`. * If a read-only indicator appears in {kib}, you have insufficient privileges -to create or save index patterns. The buttons to create new index patterns or -save existing index patterns are not visible. For more information, +to create or save data views. The buttons to create new data views or +save existing data views are not visible. For more information, refer to <>. [float] [[settings-create-pattern]] -=== Create an index pattern +=== Create a data view If you collected data using one of the {kib} <>, uploaded a file, or added sample data, -you get an index pattern for free, and can start exploring your data. -If you loaded your own data, follow these steps to create an index pattern. +you get a data view for free, and can start exploring your data. +If you loaded your own data, follow these steps to create a data view. -. Open the main menu, then click to *Stack Management > Index Patterns*. +. Open the main menu, then click to *Stack Management > Data Views*. + +. Click *Create data view*. -. Click *Create index pattern*. -+ [role="screenshot"] -image:management/index-patterns/images/create-index-pattern.png["Create index pattern"] +image:management/index-patterns/images/create-index-pattern.png["Create data view"] -. Start typing in the *Index pattern* field, and {kib} looks for the names of +. Start typing in the *name* field, and {kib} looks for the names of indices, data streams, and aliases that match your input. + ** To match multiple sources, use a wildcard (*). For example, `filebeat-*` matches @@ -61,21 +61,21 @@ global time filters on your dashboards. This is useful if you have multiple time fields and want to create dashboards that combine visualizations based on different timestamps. -. Click *Create index pattern*. +. Click *Create data view*. + [[reload-fields]] {kib} is now configured to use your {es} data. When a new field is added to an index, -the index pattern field list is updated -the next time the index pattern is loaded, for example, when you load the page or +the data view field list is updated +the next time the data view is loaded, for example, when you load the page or move between {kib} apps. -. Select this index pattern when you search and visualize your data. +. Select this data view when you search and visualize your data. [float] -[[rollup-index-pattern]] -==== Create an index pattern for rolled up data +[[rollup-data-view]] +==== Create a data view for rolled up data -An index pattern can match one rollup index. For a combination rollup -index pattern with both raw and rolled up data, use the standard notation: +A data view can match one rollup index. For a combination rollup +data view with both raw and rolled up data, use the standard notation: ```ts rollup_logstash,kibana_sample_data_logs @@ -84,7 +84,7 @@ For an example, refer to < Index Patterns*. +. Open the main menu, then click *Stack Management > Data Views*. -. Click the index pattern to delete. +. Click the data view to delete. -. Delete (image:management/index-patterns/images/delete.png[Delete icon]) the index pattern. +. Delete (image:management/index-patterns/images/delete.png[Delete icon]) the data view. diff --git a/docs/concepts/index.asciidoc b/docs/concepts/index.asciidoc index 20d7103f021cd..eac26beee1f9b 100644 --- a/docs/concepts/index.asciidoc +++ b/docs/concepts/index.asciidoc @@ -35,19 +35,19 @@ Open the search bar using the keyboard shortcut Ctrl+/ on Windows and Linux, Com image:concepts/images/global-search.png["Global search showing matches to apps and saved objects for the word visualize"] [float] -=== Accessing data with index patterns +=== Accessing data with data views -{kib} requires an index pattern to tell it which {es} data you want to access, -and whether the data is time-based. An index pattern can point to one or more {es} +{kib} requires a data view to tell it which {es} data you want to access, +and whether the data is time-based. A data view can point to one or more {es} data streams, indices, or index aliases by name. For example, `logs-elasticsearch-prod-*` is an index pattern, and it is time-based with a time field of `@timestamp`. The time field is not editable. -Index patterns are typically created by an administrator when sending data to {es}. -You can <> in *Stack Management*, or by using a script +Data views are typically created by an administrator when sending data to {es}. +You can <> in *Stack Management*, or by using a script that accesses the {kib} API. -{kib} uses the index pattern to show you a list of fields, such as +{kib} uses the data view to show you a list of fields, such as `event.duration`. You can customize the display name and format for each field. For example, you can tell {kib} to display `event.duration` in seconds. {kib} has <> for strings, @@ -75,7 +75,7 @@ and can optionally contain the time filter and extra filters. ==== Time filter The <> limits the time range of data displayed. -In most cases, the time filter applies to the time field in the index pattern, +In most cases, the time filter applies to the time field in the data view, but some apps allow you to use a different time field. Using the time filter, you can configure a refresh rate to periodically @@ -159,7 +159,7 @@ Use the global search to quickly open a saved object. * Go to <> for instructions on searching your data. -include::index-patterns.asciidoc[] +include::data-views.asciidoc[] include::set-time-filter.asciidoc[] diff --git a/docs/concepts/set-time-filter.asciidoc b/docs/concepts/set-time-filter.asciidoc index e4784a97e816b..116bcd6f91f77 100644 --- a/docs/concepts/set-time-filter.asciidoc +++ b/docs/concepts/set-time-filter.asciidoc @@ -2,7 +2,7 @@ === Set the time range Display data within a specified time range when your index contains time-based events, and a time-field is configured for the -selected <>. +selected <>. The default time range is 15 minutes, but you can customize it in <>. diff --git a/docs/discover/search.asciidoc b/docs/discover/search.asciidoc index 0306be3eb670d..4f4f8f5b48d10 100644 --- a/docs/discover/search.asciidoc +++ b/docs/discover/search.asciidoc @@ -3,7 +3,7 @@ You can search your data in any app that has a query bar, or by clicking on elements in a visualization. A search matches indices in the current -<> and in the current <>. +<> and in the current <>. [float] diff --git a/docs/maps/search.asciidoc b/docs/maps/search.asciidoc index 08624e4ddff57..a170bcc414d3b 100644 --- a/docs/maps/search.asciidoc +++ b/docs/maps/search.asciidoc @@ -43,7 +43,7 @@ To prevent the global search from applying to a layer, configure the following: [[maps-narrow-layer-by-global-time]] ==== Narrow layers by global time -Layers that request data from {es} using an <> with a configured time field are narrowed by the <>. +Layers that request data from {es} using a <> with a configured time field are narrowed by the <>. These layers contain the clock icon image:maps/images/clock_icon.png[clock icon] next to the layer name in the legend. Use the time slider to quickly select time slices within the global time range: diff --git a/docs/redirects.asciidoc b/docs/redirects.asciidoc index d5bc2ccd8ef7d..4010083d601b5 100644 --- a/docs/redirects.asciidoc +++ b/docs/redirects.asciidoc @@ -358,3 +358,8 @@ This content has moved. Refer to <>. == Rendering pre-captured profiler JSON This content has moved. Refer to <>. + +[role="exclude",id="index-patterns"] +== Index patterns has been renamed to data views. + +This content has moved. Refer to <>. diff --git a/docs/setup/connect-to-elasticsearch.asciidoc b/docs/setup/connect-to-elasticsearch.asciidoc index ad38ac1710fd5..b1d9d3ea2ea18 100644 --- a/docs/setup/connect-to-elasticsearch.asciidoc +++ b/docs/setup/connect-to-elasticsearch.asciidoc @@ -84,7 +84,7 @@ You can manage your roles, privileges, and spaces in **{stack-manage-app}** in If the {kib} ingest options don't work for you, you can index your data into Elasticsearch with {ref}/getting-started-index.html[REST APIs] or https://www.elastic.co/guide/en/elasticsearch/client/index.html[client libraries]. -After you add your data, you're required to create an <> to tell +After you add your data, you're required to create a <> to tell {kib} where to find the data. * To add data for Elastic Observability, refer to {observability-guide}/add-observability-data.html[Send data to Elasticsearch]. diff --git a/docs/user/canvas.asciidoc b/docs/user/canvas.asciidoc index 1cd8eacc456c7..1f469b697c218 100644 --- a/docs/user/canvas.asciidoc +++ b/docs/user/canvas.asciidoc @@ -43,7 +43,7 @@ To create workpads, you must meet the minimum requirements. * If you need to set up {kib}, use https://www.elastic.co/cloud/elasticsearch-service/signup?baymax=docs-body&elektra=docs[our free trial]. -* Make sure you have {ref}/getting-started-index.html[data indexed into {es}] and an <>. +* Make sure you have {ref}/getting-started-index.html[data indexed into {es}] and a <>. * Have an understanding of {ref}/documents-indices.html[{es} documents and indices]. diff --git a/docs/user/dashboard/dashboard.asciidoc b/docs/user/dashboard/dashboard.asciidoc index a2e0eb6bf92e9..474b45f4989fb 100644 --- a/docs/user/dashboard/dashboard.asciidoc +++ b/docs/user/dashboard/dashboard.asciidoc @@ -5,7 +5,7 @@ -- **_Visualize your data with dashboards._** -The best way to understand your data is to visualize it. With dashboards, you can turn your data from one or more <> into a collection of panels +The best way to understand your data is to visualize it. With dashboards, you can turn your data from one or more <> into a collection of panels that bring clarity to your data, tell a story about your data, and allow you to focus on only the data that's important to you. [role="screenshot"] @@ -53,7 +53,7 @@ To create dashboards, you must meet the minimum requirements. * If you need to set up {kib}, use https://www.elastic.co/cloud/elasticsearch-service/signup?baymax=docs-body&elektra=docs[our free trial]. -* Make sure you have {ref}/getting-started-index.html[data indexed into {es}] and an <>. +* Make sure you have {ref}/getting-started-index.html[data indexed into {es}] and a <>. * When the read-only indicator appears, you have insufficient privileges to create or save dashboards, and the options to create and save dashboards are not visible. For more information, diff --git a/docs/user/dashboard/lens-advanced.asciidoc b/docs/user/dashboard/lens-advanced.asciidoc index d5a52428cff36..02e0afd2c0311 100644 --- a/docs/user/dashboard/lens-advanced.asciidoc +++ b/docs/user/dashboard/lens-advanced.asciidoc @@ -33,7 +33,7 @@ Open *Lens*, then make sure the correct fields appear. . Make sure the *kibana_sample_data_ecommerce* index appears. + -If you are using your own data, select the <> that contains your data. +If you are using your own data, select the <> that contains your data. [discrete] [[custom-time-interval]] diff --git a/docs/user/dashboard/tsvb.asciidoc b/docs/user/dashboard/tsvb.asciidoc index 9fe6af2d3da6d..c944ec2c9e083 100644 --- a/docs/user/dashboard/tsvb.asciidoc +++ b/docs/user/dashboard/tsvb.asciidoc @@ -8,7 +8,7 @@ With *TSVB*, you can: * Combine an infinite number of <> to display your data. * Annotate time series data with timestamped events from an {es} index. * View the data in several types of visualizations, including charts, data tables, and markdown panels. -* Display multiple <> in each visualization. +* Display multiple <> in each visualization. * Use custom functions and some math on aggregations. * Customize the data with labels and colors. diff --git a/docs/user/discover.asciidoc b/docs/user/discover.asciidoc index e52531f9decdc..a485bb4c96efe 100644 --- a/docs/user/discover.asciidoc +++ b/docs/user/discover.asciidoc @@ -64,7 +64,7 @@ Tell {kib} where to find the data you want to explore, and then specify the time . Select the data you want to work with. + -{kib} uses an <> to tell it where to find +{kib} uses a <> to tell it where to find your {es} data. To view the ecommerce sample data, make sure the index pattern is set to **kibana_sample_data_ecommerce**. + diff --git a/docs/user/graph/getting-started.asciidoc b/docs/user/graph/getting-started.asciidoc index 086c0707b3c2c..5e87efc5e8aca 100644 --- a/docs/user/graph/getting-started.asciidoc +++ b/docs/user/graph/getting-started.asciidoc @@ -3,7 +3,7 @@ == Create a graph You must index data into {es} before you can create a graph. -<> or get started with a <>. +<> or get started with a <>. [float] [[exploring-connections]] From c149fe6f926e8d5c8214294d330e621205140e88 Mon Sep 17 00:00:00 2001 From: Claudio Procida Date: Tue, 2 Nov 2021 17:43:24 +0100 Subject: [PATCH 03/41] [RAC] Updates Alerts table cell actions (#116446) * Adds Filter Out button to alert table cell flyout * Adds translations * Fixes capitalization of labels * Removes unused declarations and imports * Fixes and adds functional tests for Alerts table action buttons * Addresses review comments * Fixes Alert table cell actions functional tests * Removes Filter out action for now Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../pages/alerts/default_cell_actions.tsx | 50 ++----------------- .../public/pages/alerts/filter_for_value.tsx | 2 +- .../public/components/t_grid/body/index.tsx | 9 +++- .../services/observability/alerts/common.ts | 15 ++---- .../apps/observability/alerts/index.ts | 10 ++-- 5 files changed, 20 insertions(+), 66 deletions(-) diff --git a/x-pack/plugins/observability/public/pages/alerts/default_cell_actions.tsx b/x-pack/plugins/observability/public/pages/alerts/default_cell_actions.tsx index 5ad4804f88d5e..3adfb0a1d9c89 100644 --- a/x-pack/plugins/observability/public/pages/alerts/default_cell_actions.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/default_cell_actions.tsx @@ -7,58 +7,16 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; -import { ObservabilityPublicPluginsStart } from '../..'; import { getMappedNonEcsValue } from './render_cell_value'; import FilterForValueButton from './filter_for_value'; -import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; import { TimelineNonEcsData } from '../../../../timelines/common/search_strategy'; import { TGridCellAction } from '../../../../timelines/common/types/timeline'; -import { getPageRowIndex, TimelinesUIStart } from '../../../../timelines/public'; +import { getPageRowIndex } from '../../../../timelines/public'; export const FILTER_FOR_VALUE = i18n.translate('xpack.observability.hoverActions.filterForValue', { defaultMessage: 'Filter for value', }); -/** a hook to eliminate the verbose boilerplate required to use common services */ -const useKibanaServices = () => { - const { timelines } = useKibana<{ timelines: TimelinesUIStart }>().services; - const { - services: { - data: { - query: { filterManager }, - }, - }, - } = useKibana(); - - return { timelines, filterManager }; -}; - -/** actions common to all cells (e.g. copy to clipboard) */ -const commonCellActions: TGridCellAction[] = [ - ({ data, pageSize }: { data: TimelineNonEcsData[][]; pageSize: number }) => - ({ rowIndex, columnId, Component }) => { - const { timelines } = useKibanaServices(); - - const value = getMappedNonEcsValue({ - data: data[getPageRowIndex(rowIndex, pageSize)], - fieldName: columnId, - }); - - return ( - <> - {timelines.getHoverActions().getCopyButton({ - Component, - field: columnId, - isHoverAction: false, - ownFocus: false, - showTooltip: false, - value, - })} - - ); - }, -]; - /** actions for adding filters to the search bar */ const buildFilterCellActions = (addToQuery: (value: string) => void): TGridCellAction[] => [ ({ data, pageSize }: { data: TimelineNonEcsData[][]; pageSize: number }) => @@ -80,7 +38,5 @@ const buildFilterCellActions = (addToQuery: (value: string) => void): TGridCellA ]; /** returns the default actions shown in `EuiDataGrid` cells */ -export const getDefaultCellActions = ({ addToQuery }: { addToQuery: (value: string) => void }) => [ - ...buildFilterCellActions(addToQuery), - ...commonCellActions, -]; +export const getDefaultCellActions = ({ addToQuery }: { addToQuery: (value: string) => void }) => + buildFilterCellActions(addToQuery); diff --git a/x-pack/plugins/observability/public/pages/alerts/filter_for_value.tsx b/x-pack/plugins/observability/public/pages/alerts/filter_for_value.tsx index 77cac9d482a37..f75ae488c9b28 100644 --- a/x-pack/plugins/observability/public/pages/alerts/filter_for_value.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/filter_for_value.tsx @@ -11,7 +11,7 @@ import { i18n } from '@kbn/i18n'; export const filterForValueButtonLabel = i18n.translate( 'xpack.observability.hoverActions.filterForValueButtonLabel', { - defaultMessage: 'Filter for value', + defaultMessage: 'Filter in', } ); diff --git a/x-pack/plugins/timelines/public/components/t_grid/body/index.tsx b/x-pack/plugins/timelines/public/components/t_grid/body/index.tsx index 9e43c16fd5e6f..29766a5b8a1f5 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/body/index.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/body/index.tsx @@ -146,7 +146,14 @@ const EuiDataGridContainer = styled.div<{ hideLastPage: boolean }>` } `; -const FIELDS_WITHOUT_CELL_ACTIONS = ['@timestamp', 'signal.rule.risk_score', 'signal.reason']; +// TODO: accept extra list of column ids without actions from callsites +const FIELDS_WITHOUT_CELL_ACTIONS = [ + '@timestamp', + 'signal.rule.risk_score', + 'signal.reason', + 'kibana.alert.duration.us', + 'kibana.alert.reason', +]; const hasCellActions = (columnId?: string) => columnId && FIELDS_WITHOUT_CELL_ACTIONS.indexOf(columnId) < 0; const transformControlColumns = ({ diff --git a/x-pack/test/functional/services/observability/alerts/common.ts b/x-pack/test/functional/services/observability/alerts/common.ts index f47d17039b5ae..7e29b94c85fa3 100644 --- a/x-pack/test/functional/services/observability/alerts/common.ts +++ b/x-pack/test/functional/services/observability/alerts/common.ts @@ -16,7 +16,7 @@ const DATE_WITH_DATA = { }; const ALERTS_FLYOUT_SELECTOR = 'alertsFlyout'; -const COPY_TO_CLIPBOARD_BUTTON_SELECTOR = 'copy-to-clipboard'; +const FILTER_FOR_VALUE_BUTTON_SELECTOR = 'filter-for-value'; const ALERTS_TABLE_CONTAINER_SELECTOR = 'events-viewer-panel'; const ACTION_COLUMN_INDEX = 1; @@ -149,16 +149,12 @@ export function ObservabilityAlertsCommonProvider({ // Cell actions - const copyToClipboardButtonExists = async () => { - return await testSubjects.exists(COPY_TO_CLIPBOARD_BUTTON_SELECTOR); - }; - - const getCopyToClipboardButton = async () => { - return await testSubjects.find(COPY_TO_CLIPBOARD_BUTTON_SELECTOR); + const filterForValueButtonExists = async () => { + return await testSubjects.exists(FILTER_FOR_VALUE_BUTTON_SELECTOR); }; const getFilterForValueButton = async () => { - return await testSubjects.find('filter-for-value'); + return await testSubjects.find(FILTER_FOR_VALUE_BUTTON_SELECTOR); }; const openActionsMenuForRow = async (rowIndex: number) => { @@ -216,15 +212,14 @@ export function ObservabilityAlertsCommonProvider({ getQueryBar, clearQueryBar, closeAlertsFlyout, + filterForValueButtonExists, getAlertsFlyout, getAlertsFlyoutDescriptionListDescriptions, getAlertsFlyoutDescriptionListTitles, getAlertsFlyoutOrFail, getAlertsFlyoutTitle, getAlertsFlyoutViewInAppButtonOrFail, - getCopyToClipboardButton, getFilterForValueButton, - copyToClipboardButtonExists, getNoDataPageOrFail, getNoDataStateOrFail, getTableCells, diff --git a/x-pack/test/observability_functional/apps/observability/alerts/index.ts b/x-pack/test/observability_functional/apps/observability/alerts/index.ts index 112c24f7c3a88..216a9736fbe87 100644 --- a/x-pack/test/observability_functional/apps/observability/alerts/index.ts +++ b/x-pack/test/observability_functional/apps/observability/alerts/index.ts @@ -189,19 +189,15 @@ export default ({ getService }: FtrProviderContext) => { await alertStatusCell.moveMouseTo(); await retry.waitFor( 'cell actions visible', - async () => await observability.alerts.common.copyToClipboardButtonExists() + async () => await observability.alerts.common.filterForValueButtonExists() ); }); }); afterEach(async () => { await observability.alerts.common.clearQueryBar(); - }); - - it('Copy button works', async () => { - // NOTE: We don't have access to the clipboard in a headless environment, - // so we'll just check the button is clickable in the functional tests. - await (await observability.alerts.common.getCopyToClipboardButton()).click(); + // Reset the query bar by hiding the dropdown + await observability.alerts.common.submitQuery(''); }); it('Filter for value works', async () => { From 441003df5cf909d6922527713de2911ae36ad7fa Mon Sep 17 00:00:00 2001 From: Tyler Smalley Date: Tue, 2 Nov 2021 09:46:00 -0700 Subject: [PATCH 04/41] Ignore eslint no-explicit-any Signed-off-by: Tyler Smalley --- x-pack/plugins/osquery/public/saved_queries/use_saved_queries.ts | 1 + x-pack/plugins/osquery/public/saved_queries/use_saved_query.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/x-pack/plugins/osquery/public/saved_queries/use_saved_queries.ts b/x-pack/plugins/osquery/public/saved_queries/use_saved_queries.ts index 8f697581642e6..9de40c759c2cf 100644 --- a/x-pack/plugins/osquery/public/saved_queries/use_saved_queries.ts +++ b/x-pack/plugins/osquery/public/saved_queries/use_saved_queries.ts @@ -24,6 +24,7 @@ export const useSavedQueries = ({ return useQuery( [SAVED_QUERIES_ID, { pageIndex, pageSize, sortField, sortDirection }], () => + // eslint-disable-next-line @typescript-eslint/no-explicit-any http.get('/internal/osquery/saved_query', { query: { pageIndex, pageSize, sortField, sortDirection }, }), diff --git a/x-pack/plugins/osquery/public/saved_queries/use_saved_query.ts b/x-pack/plugins/osquery/public/saved_queries/use_saved_query.ts index 8f24f7734fc46..f05f38b8259ce 100644 --- a/x-pack/plugins/osquery/public/saved_queries/use_saved_query.ts +++ b/x-pack/plugins/osquery/public/saved_queries/use_saved_query.ts @@ -26,6 +26,7 @@ export const useSavedQuery = ({ savedQueryId }: UseSavedQueryProps) => { return useQuery( [SAVED_QUERY_ID, { savedQueryId }], + // eslint-disable-next-line @typescript-eslint/no-explicit-any () => http.get(`/internal/osquery/saved_query/${savedQueryId}`), { keepPreviousData: true, From 5d557539e48f86bba722cb87333bd764c189fd42 Mon Sep 17 00:00:00 2001 From: Tyler Smalley Date: Tue, 2 Nov 2021 10:05:31 -0700 Subject: [PATCH 05/41] Revert "[ci] Increase heap of Jest" This reverts commit 87c7289b745642832b093f8685daa8ab6a0a872c. --- .buildkite/scripts/steps/test/jest_parallel.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.buildkite/scripts/steps/test/jest_parallel.sh b/.buildkite/scripts/steps/test/jest_parallel.sh index 963cfae581b1f..d3ee75b7add4a 100755 --- a/.buildkite/scripts/steps/test/jest_parallel.sh +++ b/.buildkite/scripts/steps/test/jest_parallel.sh @@ -14,7 +14,7 @@ exitCode=0 find src x-pack packages -name jest.config.js -not -path "*/__fixtures__/*" | sort | while read config; do if [ "$(($i % $JOB_COUNT))" -eq $JOB ]; then echo "--- $ node scripts/jest --config $config" - node --max-old-space-size=7168 ./node_modules/.bin/jest --config=$config --runInBand --coverage=false + node --max-old-space-size=5632 ./node_modules/.bin/jest --config=$config --runInBand --coverage=false if [ $? -ne 0 ]; then exitCode=10 From 98de5f673cd89c991f8414a10fd53cfa779121f4 Mon Sep 17 00:00:00 2001 From: Tyler Smalley Date: Tue, 2 Nov 2021 10:05:55 -0700 Subject: [PATCH 06/41] Revert "[ci] Run Jest tests in parallel (#115687)" This reverts commit 237d68d6e907c0ee6fd537501405f3d16c562798. --- .buildkite/pipelines/hourly.yml | 15 +++++----- .buildkite/pipelines/pull_request/base.yml | 15 +++++----- .buildkite/scripts/steps/test/jest.sh | 4 +-- .../scripts/steps/test/jest_parallel.sh | 28 ------------------- .../kbn-cli-dev-mode/src/dev_server.test.ts | 6 ++-- packages/kbn-rule-data-utils/jest.config.js | 13 +++++++++ .../jest.config.js | 13 +++++++++ .../jest.config.js | 13 +++++++++ packages/kbn-test/jest-preset.js | 10 +------ packages/kbn-test/src/jest/run.ts | 5 ++-- src/plugins/expression_error/jest.config.js | 16 +++++++++++ .../plugins/metrics_entities/jest.config.js | 15 ++++++++++ .../download/ensure_downloaded.test.ts | 3 +- .../server/routes/deprecations.test.ts | 3 +- 14 files changed, 93 insertions(+), 66 deletions(-) delete mode 100755 .buildkite/scripts/steps/test/jest_parallel.sh create mode 100644 packages/kbn-rule-data-utils/jest.config.js create mode 100644 packages/kbn-securitysolution-list-constants/jest.config.js create mode 100644 packages/kbn-securitysolution-t-grid/jest.config.js create mode 100644 src/plugins/expression_error/jest.config.js create mode 100644 x-pack/plugins/metrics_entities/jest.config.js diff --git a/.buildkite/pipelines/hourly.yml b/.buildkite/pipelines/hourly.yml index 81875dee70f18..b03a46b5b5c66 100644 --- a/.buildkite/pipelines/hourly.yml +++ b/.buildkite/pipelines/hourly.yml @@ -119,14 +119,6 @@ steps: - exit_status: '*' limit: 1 - - command: .buildkite/scripts/steps/test/jest.sh - label: 'Jest Tests' - parallelism: 6 - agents: - queue: n2-4 - timeout_in_minutes: 90 - key: jest - - command: .buildkite/scripts/steps/test/jest_integration.sh label: 'Jest Integration Tests' agents: @@ -141,6 +133,13 @@ steps: timeout_in_minutes: 120 key: api-integration + - command: .buildkite/scripts/steps/test/jest.sh + label: 'Jest Tests' + agents: + queue: c2-16 + timeout_in_minutes: 120 + key: jest + - command: .buildkite/scripts/steps/lint.sh label: 'Linting' agents: diff --git a/.buildkite/pipelines/pull_request/base.yml b/.buildkite/pipelines/pull_request/base.yml index a3a1881c856c5..1013a841dfd27 100644 --- a/.buildkite/pipelines/pull_request/base.yml +++ b/.buildkite/pipelines/pull_request/base.yml @@ -117,14 +117,6 @@ steps: - exit_status: '*' limit: 1 - - command: .buildkite/scripts/steps/test/jest.sh - label: 'Jest Tests' - parallelism: 6 - agents: - queue: n2-4 - timeout_in_minutes: 90 - key: jest - - command: .buildkite/scripts/steps/test/jest_integration.sh label: 'Jest Integration Tests' agents: @@ -139,6 +131,13 @@ steps: timeout_in_minutes: 120 key: api-integration + - command: .buildkite/scripts/steps/test/jest.sh + label: 'Jest Tests' + agents: + queue: c2-16 + timeout_in_minutes: 120 + key: jest + - command: .buildkite/scripts/steps/lint.sh label: 'Linting' agents: diff --git a/.buildkite/scripts/steps/test/jest.sh b/.buildkite/scripts/steps/test/jest.sh index d2d1ed10043d6..2c4e3fe21902d 100755 --- a/.buildkite/scripts/steps/test/jest.sh +++ b/.buildkite/scripts/steps/test/jest.sh @@ -9,5 +9,5 @@ is_test_execution_step .buildkite/scripts/bootstrap.sh echo '--- Jest' -checks-reporter-with-killswitch "Jest Unit Tests $((BUILDKITE_PARALLEL_JOB+1))" \ - .buildkite/scripts/steps/test/jest_parallel.sh +checks-reporter-with-killswitch "Jest Unit Tests" \ + node scripts/jest --ci --verbose --maxWorkers=10 diff --git a/.buildkite/scripts/steps/test/jest_parallel.sh b/.buildkite/scripts/steps/test/jest_parallel.sh deleted file mode 100755 index d3ee75b7add4a..0000000000000 --- a/.buildkite/scripts/steps/test/jest_parallel.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash - -set -uo pipefail - -JOB=$BUILDKITE_PARALLEL_JOB -JOB_COUNT=$BUILDKITE_PARALLEL_JOB_COUNT - -# a jest failure will result in the script returning an exit -# code of 10 - -i=0 -exitCode=0 - -find src x-pack packages -name jest.config.js -not -path "*/__fixtures__/*" | sort | while read config; do - if [ "$(($i % $JOB_COUNT))" -eq $JOB ]; then - echo "--- $ node scripts/jest --config $config" - node --max-old-space-size=5632 ./node_modules/.bin/jest --config=$config --runInBand --coverage=false - - if [ $? -ne 0 ]; then - exitCode=10 - echo "^^^ +++" - fi - fi - - ((i=i+1)) -done - -exit $exitCode \ No newline at end of file diff --git a/packages/kbn-cli-dev-mode/src/dev_server.test.ts b/packages/kbn-cli-dev-mode/src/dev_server.test.ts index 5e386e3de5972..92dbe484eb005 100644 --- a/packages/kbn-cli-dev-mode/src/dev_server.test.ts +++ b/packages/kbn-cli-dev-mode/src/dev_server.test.ts @@ -79,7 +79,6 @@ expect.addSnapshotSerializer(extendedEnvSerializer); beforeEach(() => { jest.clearAllMocks(); log.messages.length = 0; - process.execArgv = ['--inheritted', '--exec', '--argv']; currentProc = undefined; }); @@ -139,9 +138,8 @@ describe('#run$', () => { "isDevCliChild": "true", }, "nodeOptions": Array [ - "--inheritted", - "--exec", - "--argv", + "--preserve-symlinks-main", + "--preserve-symlinks", ], "stdio": "pipe", }, diff --git a/packages/kbn-rule-data-utils/jest.config.js b/packages/kbn-rule-data-utils/jest.config.js new file mode 100644 index 0000000000000..26cb39fe8b55a --- /dev/null +++ b/packages/kbn-rule-data-utils/jest.config.js @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/packages/kbn-rule-data-utils'], +}; diff --git a/packages/kbn-securitysolution-list-constants/jest.config.js b/packages/kbn-securitysolution-list-constants/jest.config.js new file mode 100644 index 0000000000000..21dffdfcf5a68 --- /dev/null +++ b/packages/kbn-securitysolution-list-constants/jest.config.js @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/packages/kbn-securitysolution-list-constants'], +}; diff --git a/packages/kbn-securitysolution-t-grid/jest.config.js b/packages/kbn-securitysolution-t-grid/jest.config.js new file mode 100644 index 0000000000000..21e7d2d71b61a --- /dev/null +++ b/packages/kbn-securitysolution-t-grid/jest.config.js @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/packages/kbn-securitysolution-t-grid'], +}; diff --git a/packages/kbn-test/jest-preset.js b/packages/kbn-test/jest-preset.js index db64f070b37d9..0199aa6e311b6 100644 --- a/packages/kbn-test/jest-preset.js +++ b/packages/kbn-test/jest-preset.js @@ -46,15 +46,7 @@ module.exports = { modulePathIgnorePatterns: ['__fixtures__/', 'target/'], // Use this configuration option to add custom reporters to Jest - reporters: [ - 'default', - [ - '@kbn/test/target_node/jest/junit_reporter', - { - rootDirectory: '.', - }, - ], - ], + reporters: ['default', '@kbn/test/target_node/jest/junit_reporter'], // The paths to modules that run some code to configure or set up the testing environment before each test setupFiles: [ diff --git a/packages/kbn-test/src/jest/run.ts b/packages/kbn-test/src/jest/run.ts index f2592500beeee..4a5dd4e9281ba 100644 --- a/packages/kbn-test/src/jest/run.ts +++ b/packages/kbn-test/src/jest/run.ts @@ -52,12 +52,11 @@ export function runJest(configName = 'jest.config.js') { const runStartTime = Date.now(); const reportTime = getTimeReporter(log, 'scripts/jest'); - + let cwd: string; let testFiles: string[]; - const cwd: string = process.env.INIT_CWD || process.cwd(); - if (!argv.config) { + cwd = process.env.INIT_CWD || process.cwd(); testFiles = argv._.splice(2).map((p) => resolve(cwd, p)); const commonTestFiles = commonBasePath(testFiles); const testFilesProvided = testFiles.length > 0; diff --git a/src/plugins/expression_error/jest.config.js b/src/plugins/expression_error/jest.config.js new file mode 100644 index 0000000000000..27774f4003f9e --- /dev/null +++ b/src/plugins/expression_error/jest.config.js @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/expression_error'], + coverageDirectory: '/target/kibana-coverage/jest/src/plugins/expression_error', + coverageReporters: ['text', 'html'], + collectCoverageFrom: ['/src/plugins/expression_error/{common,public}/**/*.{ts,tsx}'], +}; diff --git a/x-pack/plugins/metrics_entities/jest.config.js b/x-pack/plugins/metrics_entities/jest.config.js new file mode 100644 index 0000000000000..98a391223cc0f --- /dev/null +++ b/x-pack/plugins/metrics_entities/jest.config.js @@ -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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +module.exports = { + collectCoverageFrom: ['/x-pack/plugins/metrics_entities/{common,server}/**/*.{ts,tsx}'], + coverageDirectory: '/target/kibana-coverage/jest/x-pack/plugins/metrics_entities', + coverageReporters: ['text', 'html'], + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/x-pack/plugins/metrics_entities'], +}; diff --git a/x-pack/plugins/reporting/server/browsers/download/ensure_downloaded.test.ts b/x-pack/plugins/reporting/server/browsers/download/ensure_downloaded.test.ts index 9db128c019ac0..955e8214af8fa 100644 --- a/x-pack/plugins/reporting/server/browsers/download/ensure_downloaded.test.ts +++ b/x-pack/plugins/reporting/server/browsers/download/ensure_downloaded.test.ts @@ -17,8 +17,7 @@ import { LevelLogger } from '../../lib'; jest.mock('./checksum'); jest.mock('./download'); -// https://github.com/elastic/kibana/issues/115881 -describe.skip('ensureBrowserDownloaded', () => { +describe('ensureBrowserDownloaded', () => { let logger: jest.Mocked; beforeEach(() => { diff --git a/x-pack/plugins/reporting/server/routes/deprecations.test.ts b/x-pack/plugins/reporting/server/routes/deprecations.test.ts index 63be2acf52c25..5367b6bd531ed 100644 --- a/x-pack/plugins/reporting/server/routes/deprecations.test.ts +++ b/x-pack/plugins/reporting/server/routes/deprecations.test.ts @@ -24,8 +24,7 @@ import { registerDeprecationsRoutes } from './deprecations'; type SetupServerReturn = UnwrapPromise>; -// https://github.com/elastic/kibana/issues/115881 -describe.skip(`GET ${API_GET_ILM_POLICY_STATUS}`, () => { +describe(`GET ${API_GET_ILM_POLICY_STATUS}`, () => { const reportingSymbol = Symbol('reporting'); let server: SetupServerReturn['server']; let httpSetup: SetupServerReturn['httpSetup']; From 39d79f7237c8b71ae41ee6e2b71e6f44e27e0ce0 Mon Sep 17 00:00:00 2001 From: Corey Robertson Date: Tue, 2 Nov 2021 13:07:29 -0400 Subject: [PATCH 07/41] Remove fullscreen mode when workpad unmounts (#114551) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../workpad/hooks/use_fullscreen_presentation_helper.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/x-pack/plugins/canvas/public/routes/workpad/hooks/use_fullscreen_presentation_helper.ts b/x-pack/plugins/canvas/public/routes/workpad/hooks/use_fullscreen_presentation_helper.ts index 9021c6d6c2753..ca66fa227e4eb 100644 --- a/x-pack/plugins/canvas/public/routes/workpad/hooks/use_fullscreen_presentation_helper.ts +++ b/x-pack/plugins/canvas/public/routes/workpad/hooks/use_fullscreen_presentation_helper.ts @@ -5,6 +5,7 @@ * 2.0. */ import { useContext, useEffect } from 'react'; +import useEffectOnce from 'react-use/lib/useEffectOnce'; import { usePlatformService } from '../../../services'; import { WorkpadRoutingContext } from '..'; @@ -27,4 +28,10 @@ export const useFullscreenPresentationHelper = () => { setFullscreen(true); } }, [isFullscreen, setFullscreen]); + + // Remove fullscreen when component unmounts + useEffectOnce(() => () => { + setFullscreen(true); + document.querySelector('body')?.classList.remove(fullscreenClass); + }); }; From 4e294e3153071019d84de3efb245e11d37fc44c7 Mon Sep 17 00:00:00 2001 From: Dominique Clarke Date: Tue, 2 Nov 2021 13:10:58 -0400 Subject: [PATCH 08/41] [Uptime] Fix: Last successful screenshot should be from same location (#116906) * update get_last_successful_step query to include location logic * adjust types --- .../common/runtime_types/ping/synthetics.ts | 5 + .../step_expanded_row/step_screenshots.tsx | 1 + .../check_steps/use_expanded_row.test.tsx | 3 + .../uptime/public/state/api/journey.ts | 3 + .../requests/get_last_successful_step.test.ts | 132 ++++++++++++++++++ .../lib/requests/get_last_successful_step.ts | 43 +++++- .../synthetics/last_successful_step.ts | 4 +- 7 files changed, 185 insertions(+), 6 deletions(-) create mode 100644 x-pack/plugins/uptime/server/lib/requests/get_last_successful_step.test.ts diff --git a/x-pack/plugins/uptime/common/runtime_types/ping/synthetics.ts b/x-pack/plugins/uptime/common/runtime_types/ping/synthetics.ts index 7b181ac2cf50c..040f0a83e84ab 100644 --- a/x-pack/plugins/uptime/common/runtime_types/ping/synthetics.ts +++ b/x-pack/plugins/uptime/common/runtime_types/ping/synthetics.ts @@ -27,6 +27,11 @@ export const JourneyStepType = t.intersection([ lt: t.string, }), }), + observer: t.type({ + geo: t.type({ + name: t.string, + }), + }), synthetics: t.partial({ error: t.partial({ message: t.string, diff --git a/x-pack/plugins/uptime/public/components/synthetics/check_steps/step_expanded_row/step_screenshots.tsx b/x-pack/plugins/uptime/public/components/synthetics/check_steps/step_expanded_row/step_screenshots.tsx index 54f73fb39a52a..f8776f74b780e 100644 --- a/x-pack/plugins/uptime/public/components/synthetics/check_steps/step_expanded_row/step_screenshots.tsx +++ b/x-pack/plugins/uptime/public/components/synthetics/check_steps/step_expanded_row/step_screenshots.tsx @@ -36,6 +36,7 @@ export const StepScreenshots = ({ step }: Props) => { timestamp: step['@timestamp'], monitorId: step.monitor.id, stepIndex: step.synthetics?.step?.index!, + location: step.observer?.geo?.name, }); } }, [step._id, step['@timestamp']]); diff --git a/x-pack/plugins/uptime/public/components/synthetics/check_steps/use_expanded_row.test.tsx b/x-pack/plugins/uptime/public/components/synthetics/check_steps/use_expanded_row.test.tsx index 7aa763c15ca1f..e1f43cfebdbb2 100644 --- a/x-pack/plugins/uptime/public/components/synthetics/check_steps/use_expanded_row.test.tsx +++ b/x-pack/plugins/uptime/public/components/synthetics/check_steps/use_expanded_row.test.tsx @@ -228,6 +228,9 @@ const browserConsoleStep = { _id: 'IvT1oXwB5ds00bB_FVXP', observer: { hostname: '16Elastic', + geo: { + name: 'au-heartbeat', + }, }, agent: { name: '16Elastic', diff --git a/x-pack/plugins/uptime/public/state/api/journey.ts b/x-pack/plugins/uptime/public/state/api/journey.ts index b982da90d9dc5..05d4a9e356919 100644 --- a/x-pack/plugins/uptime/public/state/api/journey.ts +++ b/x-pack/plugins/uptime/public/state/api/journey.ts @@ -51,10 +51,12 @@ export async function fetchLastSuccessfulStep({ monitorId, timestamp, stepIndex, + location, }: { monitorId: string; timestamp: string; stepIndex: number; + location?: string; }): Promise { return await apiService.get( `/api/uptime/synthetics/step/success/`, @@ -62,6 +64,7 @@ export async function fetchLastSuccessfulStep({ monitorId, timestamp, stepIndex, + location, }, JourneyStepType ); diff --git a/x-pack/plugins/uptime/server/lib/requests/get_last_successful_step.test.ts b/x-pack/plugins/uptime/server/lib/requests/get_last_successful_step.test.ts new file mode 100644 index 0000000000000..63274bf64536c --- /dev/null +++ b/x-pack/plugins/uptime/server/lib/requests/get_last_successful_step.test.ts @@ -0,0 +1,132 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getLastSuccessfulStepParams } from './get_last_successful_step'; + +describe('getLastSuccessfulStep', () => { + describe('getLastSuccessfulStepParams', () => { + it('formats ES params with location', () => { + const monitorId = 'my-monitor'; + const stepIndex = 1; + const location = 'au-heartbeat'; + const timestamp = '2021-10-31T19:47:52.392Z'; + const params = getLastSuccessfulStepParams({ + monitorId, + stepIndex, + location, + timestamp, + }); + + expect(params).toEqual({ + query: { + bool: { + filter: [ + { + range: { + '@timestamp': { + lte: '2021-10-31T19:47:52.392Z', + }, + }, + }, + { + term: { + 'monitor.id': monitorId, + }, + }, + { + term: { + 'synthetics.type': 'step/end', + }, + }, + { + term: { + 'synthetics.step.status': 'succeeded', + }, + }, + { + term: { + 'synthetics.step.index': stepIndex, + }, + }, + { + term: { + 'observer.geo.name': location, + }, + }, + ], + }, + }, + size: 1, + sort: [ + { + '@timestamp': { + order: 'desc', + }, + }, + ], + }); + }); + + it('formats ES params without location', () => { + const params = getLastSuccessfulStepParams({ + monitorId: 'my-monitor', + stepIndex: 1, + location: undefined, + timestamp: '2021-10-31T19:47:52.392Z', + }); + + expect(params).toEqual({ + query: { + bool: { + filter: [ + { + range: { + '@timestamp': { + lte: '2021-10-31T19:47:52.392Z', + }, + }, + }, + { + term: { + 'monitor.id': 'my-monitor', + }, + }, + { + term: { + 'synthetics.type': 'step/end', + }, + }, + { + term: { + 'synthetics.step.status': 'succeeded', + }, + }, + { + term: { + 'synthetics.step.index': 1, + }, + }, + ], + must_not: { + exists: { + field: 'observer.geo.name', + }, + }, + }, + }, + size: 1, + sort: [ + { + '@timestamp': { + order: 'desc', + }, + }, + ], + }); + }); + }); +}); diff --git a/x-pack/plugins/uptime/server/lib/requests/get_last_successful_step.ts b/x-pack/plugins/uptime/server/lib/requests/get_last_successful_step.ts index e096cdaa65b86..d6862b93c8cd4 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_last_successful_step.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_last_successful_step.ts @@ -13,13 +13,16 @@ export interface GetStepScreenshotParams { monitorId: string; timestamp: string; stepIndex: number; + location?: string; } -export const getStepLastSuccessfulStep: UMElasticsearchQueryFn< - GetStepScreenshotParams, - JourneyStep | null -> = async ({ uptimeEsClient, monitorId, stepIndex, timestamp }) => { - const lastSuccessCheckParams: estypes.SearchRequest['body'] = { +export const getLastSuccessfulStepParams = ({ + monitorId, + stepIndex, + timestamp, + location, +}: GetStepScreenshotParams): estypes.SearchRequest['body'] => { + return { size: 1, sort: [ { @@ -58,10 +61,40 @@ export const getStepLastSuccessfulStep: UMElasticsearchQueryFn< 'synthetics.step.index': stepIndex, }, }, + ...(location + ? [ + { + term: { + 'observer.geo.name': location, + }, + }, + ] + : []), ], + ...(!location + ? { + must_not: { + exists: { + field: 'observer.geo.name', + }, + }, + } + : {}), }, }, }; +}; + +export const getStepLastSuccessfulStep: UMElasticsearchQueryFn< + GetStepScreenshotParams, + JourneyStep | null +> = async ({ uptimeEsClient, monitorId, stepIndex, timestamp, location }) => { + const lastSuccessCheckParams = getLastSuccessfulStepParams({ + monitorId, + stepIndex, + timestamp, + location, + }); const { body: result } = await uptimeEsClient.search({ body: lastSuccessCheckParams }); diff --git a/x-pack/plugins/uptime/server/rest_api/synthetics/last_successful_step.ts b/x-pack/plugins/uptime/server/rest_api/synthetics/last_successful_step.ts index 5d1407a8679c8..81539459172cc 100644 --- a/x-pack/plugins/uptime/server/rest_api/synthetics/last_successful_step.ts +++ b/x-pack/plugins/uptime/server/rest_api/synthetics/last_successful_step.ts @@ -22,16 +22,18 @@ export const createLastSuccessfulStepRoute: UMRestApiRouteFactory = (libs: UMSer monitorId: schema.string(), stepIndex: schema.number(), timestamp: schema.string(), + location: schema.maybe(schema.string()), }), }, handler: async ({ uptimeEsClient, request, response }) => { - const { timestamp, monitorId, stepIndex } = request.query; + const { timestamp, monitorId, stepIndex, location } = request.query; const step: JourneyStep | null = await libs.requests.getStepLastSuccessfulStep({ uptimeEsClient, monitorId, stepIndex, timestamp, + location, }); if (step === null) { From 6e14338c58eb42d4960cac65441df315a8f47cf5 Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko Date: Tue, 2 Nov 2021 10:23:19 -0700 Subject: [PATCH 09/41] [Alerting] More telemetry for 8.0 based on Event Log data (#115318) * [Alerting] More telemetry for 8.0 based on Event Log data * fixed event log index mapping * fixed typecheck * fixed tests * added avg aggs * set size to 0 * fixed due to comments * fixed telemetry schema * fixed query * removed test data * added tests * fixed test * fixed query * added exection detalization by day * fixed test * fixed for rules * fixed schema * fixed schema Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/plugins/actions/server/plugin.ts | 3 +- .../server/usage/actions_telemetry.test.ts | 100 ++- .../actions/server/usage/actions_telemetry.ts | 182 ++++- .../server/usage/actions_usage_collector.ts | 12 + x-pack/plugins/actions/server/usage/task.ts | 40 +- x-pack/plugins/actions/server/usage/types.ts | 9 +- x-pack/plugins/alerting/server/plugin.ts | 8 +- .../server/usage/alerts_telemetry.test.ts | 76 +- .../alerting/server/usage/alerts_telemetry.ts | 187 ++++- .../server/usage/alerts_usage_collector.ts | 34 + x-pack/plugins/alerting/server/usage/task.ts | 37 +- x-pack/plugins/alerting/server/usage/types.ts | 7 + .../server/event_log_service.mock.ts | 1 + .../event_log/server/event_log_service.ts | 4 + x-pack/plugins/event_log/server/types.ts | 1 + .../schema/xpack_plugins.json | 756 ++++++++++++++++++ 16 files changed, 1434 insertions(+), 23 deletions(-) diff --git a/x-pack/plugins/actions/server/plugin.ts b/x-pack/plugins/actions/server/plugin.ts index 8531f4a2bb706..bbf00572935fa 100644 --- a/x-pack/plugins/actions/server/plugin.ts +++ b/x-pack/plugins/actions/server/plugin.ts @@ -268,7 +268,8 @@ export class ActionsPlugin implements Plugin { test('getTotalCount should replace first symbol . to __ for action types names', async () => { @@ -604,4 +604,102 @@ Object { } `); }); + + test('getExecutionsTotalCount', async () => { + const mockEsClient = elasticsearchClientMock.createClusterClient().asScoped().asInternalUser; + mockEsClient.search.mockReturnValueOnce( + // @ts-expect-error not full search response + elasticsearchClientMock.createSuccessTransportRequestPromise({ + aggregations: { + totalExecutions: { + byConnectorTypeId: { + value: { + connectorTypes: { + '.slack': 100, + '.server-log': 20, + }, + total: 120, + }, + }, + }, + failedExecutions: { + refs: { + byConnectorTypeId: { + value: { + connectorTypes: { + '.slack': 7, + }, + total: 7, + }, + }, + }, + }, + avgDuration: { value: 10 }, + avgDurationByType: { + doc_count: 216, + actionSavedObjects: { + doc_count: 108, + byTypeId: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: '.server-log', + doc_count: 99, + refs: { + doc_count: 99, + avgDuration: { + value: 919191.9191919192, + }, + }, + }, + { + key: '.email', + doc_count: 9, + refs: { + doc_count: 9, + avgDuration: { + value: 4.196666666666667e8, + }, + }, + }, + ], + }, + }, + }, + }, + }) + ); + + // for .slack connectors + mockEsClient.search.mockReturnValueOnce( + // @ts-expect-error not full search response + elasticsearchClientMock.createSuccessTransportRequestPromise({ + aggregations: { + avgDuration: { value: 10 }, + }, + }) + ); + const telemetry = await getExecutionsPerDayCount(mockEsClient, 'test'); + + expect(mockEsClient.search).toHaveBeenCalledTimes(1); + expect(telemetry).toStrictEqual({ + avgExecutionTime: 0, + avgExecutionTimeByType: { + '__server-log': 919191.9191919192, + __email: 419666666.6666667, + }, + + countByType: { + __slack: 100, + + '__server-log': 20, + }, + countFailed: 7, + countFailedByType: { + __slack: 7, + }, + countTotal: 120, + }); + }); }); diff --git a/x-pack/plugins/actions/server/usage/actions_telemetry.ts b/x-pack/plugins/actions/server/usage/actions_telemetry.ts index ab72352d460e3..d288611af5e21 100644 --- a/x-pack/plugins/actions/server/usage/actions_telemetry.ts +++ b/x-pack/plugins/actions/server/usage/actions_telemetry.ts @@ -379,4 +379,184 @@ function replaceFirstAndLastDotSymbols(strToReplace: string) { return hasLastSymbolDot ? `${appliedString.slice(0, -1)}__` : appliedString; } -// TODO: Implement executions count telemetry with eventLog, when it will write to index +export async function getExecutionsPerDayCount( + esClient: ElasticsearchClient, + eventLogIndex: string +): Promise<{ + countTotal: number; + countByType: Record; + countFailed: number; + countFailedByType: Record; + avgExecutionTime: number; + avgExecutionTimeByType: Record; +}> { + const scriptedMetric = { + scripted_metric: { + init_script: 'state.connectorTypes = [:]; state.total = 0;', + map_script: ` + if (doc['kibana.saved_objects.type'].value == 'action') { + String connectorType = doc['kibana.saved_objects.type_id'].value; + state.connectorTypes.put(connectorType, state.connectorTypes.containsKey(connectorType) ? state.connectorTypes.get(connectorType) + 1 : 1); + state.total++; + } + `, + // Combine script is executed per cluster, but we already have a key-value pair per cluster. + // Despite docs that say this is optional, this script can't be blank. + combine_script: 'return state', + // Reduce script is executed across all clusters, so we need to add up all the total from each cluster + // This also needs to account for having no data + reduce_script: ` + Map connectorTypes = [:]; + long total = 0; + for (state in states) { + if (state !== null) { + total += state.total; + for (String k : state.connectorTypes.keySet()) { + connectorTypes.put(k, connectorTypes.containsKey(k) ? connectorTypes.get(k) + state.connectorTypes.get(k) : state.connectorTypes.get(k)); + } + } + } + Map result = new HashMap(); + result.total = total; + result.connectorTypes = connectorTypes; + return result; + `, + }, + }; + + const { body: actionResults } = await esClient.search({ + index: eventLogIndex, + size: 0, + body: { + query: { + bool: { + filter: { + bool: { + must: [ + { + term: { 'event.action': 'execute' }, + }, + { + term: { 'event.provider': 'actions' }, + }, + { + range: { + '@timestamp': { + gte: 'now-1d', + }, + }, + }, + ], + }, + }, + }, + }, + aggs: { + totalExecutions: { + nested: { + path: 'kibana.saved_objects', + }, + aggs: { + byConnectorTypeId: scriptedMetric, + }, + }, + failedExecutions: { + filter: { + bool: { + filter: [ + { + term: { + 'event.outcome': 'failure', + }, + }, + ], + }, + }, + aggs: { + refs: { + nested: { + path: 'kibana.saved_objects', + }, + aggs: { + byConnectorTypeId: scriptedMetric, + }, + }, + }, + }, + avgDuration: { avg: { field: 'event.duration' } }, + avgDurationByType: { + nested: { + path: 'kibana.saved_objects', + }, + aggs: { + actionSavedObjects: { + filter: { term: { 'kibana.saved_objects.type': 'action' } }, + aggs: { + byTypeId: { + terms: { + field: 'kibana.saved_objects.type_id', + }, + aggs: { + refs: { + reverse_nested: {}, + aggs: { + avgDuration: { avg: { field: 'event.duration' } }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }); + + // @ts-expect-error aggegation type is not specified + const aggsExecutions = actionResults.aggregations.totalExecutions?.byConnectorTypeId.value; + // convert nanoseconds to milliseconds + const aggsAvgExecutionTime = Math.round( + // @ts-expect-error aggegation type is not specified + actionResults.aggregations.avgDuration.value / (1000 * 1000) + ); + const aggsFailedExecutions = + // @ts-expect-error aggegation type is not specified + actionResults.aggregations.failedExecutions?.refs?.byConnectorTypeId.value; + + const avgDurationByType = + // @ts-expect-error aggegation type is not specified + actionResults.aggregations.avgDurationByType?.actionSavedObjects?.byTypeId?.buckets; + + const avgExecutionTimeByType: Record = avgDurationByType.reduce( + // @ts-expect-error aggegation type is not specified + (res: Record, bucket) => { + res[replaceFirstAndLastDotSymbols(bucket.key)] = bucket?.refs.avgDuration.value; + return res; + }, + {} + ); + + return { + countTotal: aggsExecutions.total, + countByType: Object.entries(aggsExecutions.connectorTypes).reduce( + (res: Record, [key, value]) => { + // @ts-expect-error aggegation type is not specified + res[replaceFirstAndLastDotSymbols(key)] = value; + return res; + }, + {} + ), + countFailed: aggsFailedExecutions.total, + countFailedByType: Object.entries(aggsFailedExecutions.connectorTypes).reduce( + (res: Record, [key, value]) => { + // @ts-expect-error aggegation type is not specified + res[replaceFirstAndLastDotSymbols(key)] = value; + return res; + }, + {} + ), + avgExecutionTime: aggsAvgExecutionTime, + avgExecutionTimeByType, + }; +} diff --git a/x-pack/plugins/actions/server/usage/actions_usage_collector.ts b/x-pack/plugins/actions/server/usage/actions_usage_collector.ts index 9ba9d7390a7b6..3e690d18063d6 100644 --- a/x-pack/plugins/actions/server/usage/actions_usage_collector.ts +++ b/x-pack/plugins/actions/server/usage/actions_usage_collector.ts @@ -37,8 +37,14 @@ export function createActionsUsageCollector( }, }, count_active_by_type: byTypeSchema, + count_actions_executions_per_day: { type: 'long' }, + count_actions_executions_by_type_per_day: byTypeSchema, count_active_email_connectors_by_service_type: byServiceProviderTypeSchema, count_actions_namespaces: { type: 'long' }, + count_actions_executions_failed_per_day: { type: 'long' }, + count_actions_executions_failed_by_type_per_day: byTypeSchema, + avg_execution_time_per_day: { type: 'long' }, + avg_execution_time_by_type_per_day: byTypeSchema, }, fetch: async () => { try { @@ -60,6 +66,12 @@ export function createActionsUsageCollector( count_active_by_type: {}, count_active_email_connectors_by_service_type: {}, count_actions_namespaces: 0, + count_actions_executions_per_day: 0, + count_actions_executions_by_type_per_day: {}, + count_actions_executions_failed_per_day: 0, + count_actions_executions_failed_by_type_per_day: {}, + avg_execution_time_per_day: 0, + avg_execution_time_by_type_per_day: {}, }; } }, diff --git a/x-pack/plugins/actions/server/usage/task.ts b/x-pack/plugins/actions/server/usage/task.ts index bacb9e5f72571..5ddcbab4261d1 100644 --- a/x-pack/plugins/actions/server/usage/task.ts +++ b/x-pack/plugins/actions/server/usage/task.ts @@ -7,13 +7,14 @@ import { Logger, CoreSetup } from 'kibana/server'; import moment from 'moment'; +import { IEventLogService } from '../../../event_log/server'; import { RunContext, TaskManagerSetupContract, TaskManagerStartContract, } from '../../../task_manager/server'; import { PreConfiguredAction } from '../types'; -import { getTotalCount, getInUseTotalCount } from './actions_telemetry'; +import { getTotalCount, getInUseTotalCount, getExecutionsPerDayCount } from './actions_telemetry'; export const TELEMETRY_TASK_TYPE = 'actions_telemetry'; @@ -24,9 +25,17 @@ export function initializeActionsTelemetry( taskManager: TaskManagerSetupContract, core: CoreSetup, kibanaIndex: string, - preconfiguredActions: PreConfiguredAction[] + preconfiguredActions: PreConfiguredAction[], + eventLog: IEventLogService ) { - registerActionsTelemetryTask(logger, taskManager, core, kibanaIndex, preconfiguredActions); + registerActionsTelemetryTask( + logger, + taskManager, + core, + kibanaIndex, + preconfiguredActions, + eventLog + ); } export function scheduleActionsTelemetry(logger: Logger, taskManager: TaskManagerStartContract) { @@ -38,13 +47,20 @@ function registerActionsTelemetryTask( taskManager: TaskManagerSetupContract, core: CoreSetup, kibanaIndex: string, - preconfiguredActions: PreConfiguredAction[] + preconfiguredActions: PreConfiguredAction[], + eventLog: IEventLogService ) { taskManager.registerTaskDefinitions({ [TELEMETRY_TASK_TYPE]: { title: 'Actions usage fetch task', timeout: '5m', - createTaskRunner: telemetryTaskRunner(logger, core, kibanaIndex, preconfiguredActions), + createTaskRunner: telemetryTaskRunner( + logger, + core, + kibanaIndex, + preconfiguredActions, + eventLog + ), }, }); } @@ -66,10 +82,12 @@ export function telemetryTaskRunner( logger: Logger, core: CoreSetup, kibanaIndex: string, - preconfiguredActions: PreConfiguredAction[] + preconfiguredActions: PreConfiguredAction[], + eventLog: IEventLogService ) { return ({ taskInstance }: RunContext) => { const { state } = taskInstance; + const eventLogIndex = eventLog.getIndexPattern(); const getEsClient = () => core.getStartServices().then( ([ @@ -84,8 +102,9 @@ export function telemetryTaskRunner( return Promise.all([ getTotalCount(esClient, kibanaIndex, preconfiguredActions), getInUseTotalCount(esClient, kibanaIndex, undefined, preconfiguredActions), + getExecutionsPerDayCount(esClient, eventLogIndex), ]) - .then(([totalAggegations, totalInUse]) => { + .then(([totalAggegations, totalInUse, totalExecutionsPerDay]) => { return { state: { runs: (state.runs || 0) + 1, @@ -96,6 +115,13 @@ export function telemetryTaskRunner( count_active_alert_history_connectors: totalInUse.countByAlertHistoryConnectorType, count_active_email_connectors_by_service_type: totalInUse.countEmailByService, count_actions_namespaces: totalInUse.countNamespaces, + count_actions_executions_per_day: totalExecutionsPerDay.countTotal, + count_actions_executions_by_type_per_day: totalExecutionsPerDay.countByType, + count_actions_executions_failed_per_day: totalExecutionsPerDay.countFailed, + count_actions_executions_failed_by_type_per_day: + totalExecutionsPerDay.countFailedByType, + avg_execution_time_per_day: totalExecutionsPerDay.avgExecutionTime, + avg_execution_time_by_type_per_day: totalExecutionsPerDay.avgExecutionTimeByType, }, runAt: getNextMidnight(), }; diff --git a/x-pack/plugins/actions/server/usage/types.ts b/x-pack/plugins/actions/server/usage/types.ts index 52677b35ac75b..2d041b1ba0d0e 100644 --- a/x-pack/plugins/actions/server/usage/types.ts +++ b/x-pack/plugins/actions/server/usage/types.ts @@ -16,9 +16,12 @@ export interface ActionsUsage { count_active_by_type: Record; count_active_email_connectors_by_service_type: Record; count_actions_namespaces: number; - // TODO: Implement executions count telemetry with eventLog, when it will write to index - // executions_by_type: Record; - // executions_total: number; + count_actions_executions_per_day: number; + count_actions_executions_by_type_per_day: Record; + count_actions_executions_failed_per_day: number; + count_actions_executions_failed_by_type_per_day: Record; + avg_execution_time_per_day: number; + avg_execution_time_by_type_per_day: Record; } export const byTypeSchema: MakeSchemaFrom['count_by_type'] = { diff --git a/x-pack/plugins/alerting/server/plugin.ts b/x-pack/plugins/alerting/server/plugin.ts index 9834225e73723..f0703defbca3d 100644 --- a/x-pack/plugins/alerting/server/plugin.ts +++ b/x-pack/plugins/alerting/server/plugin.ts @@ -209,7 +209,13 @@ export class AlertingPlugin { usageCollection, core.getStartServices().then(([_, { taskManager }]) => taskManager) ); - initializeAlertingTelemetry(this.telemetryLogger, core, plugins.taskManager, kibanaIndex); + initializeAlertingTelemetry( + this.telemetryLogger, + core, + plugins.taskManager, + kibanaIndex, + this.eventLogService + ); } // Usage counter for telemetry diff --git a/x-pack/plugins/alerting/server/usage/alerts_telemetry.test.ts b/x-pack/plugins/alerting/server/usage/alerts_telemetry.test.ts index 03a96d19b8e8a..af08c8c75c144 100644 --- a/x-pack/plugins/alerting/server/usage/alerts_telemetry.test.ts +++ b/x-pack/plugins/alerting/server/usage/alerts_telemetry.test.ts @@ -7,7 +7,11 @@ // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { elasticsearchClientMock } from '../../../../../src/core/server/elasticsearch/client/mocks'; -import { getTotalCountAggregations, getTotalCountInUse } from './alerts_telemetry'; +import { + getTotalCountAggregations, + getTotalCountInUse, + getExecutionsPerDayCount, +} from './alerts_telemetry'; describe('alerts telemetry', () => { test('getTotalCountInUse should replace first "." symbol to "__" in alert types names', async () => { @@ -114,4 +118,74 @@ Object { } `); }); + + test('getTotalExecutionsCount should return execution aggregations for total count, count by rule type and number of failed executions', async () => { + const mockEsClient = elasticsearchClientMock.createClusterClient().asScoped().asInternalUser; + mockEsClient.search.mockReturnValue( + // @ts-expect-error @elastic/elasticsearch Aggregate only allows unknown values + elasticsearchClientMock.createSuccessTransportRequestPromise({ + aggregations: { + byRuleTypeId: { + value: { + ruleTypes: { + '.index-threshold': 2, + 'logs.alert.document.count': 1, + 'document.test.': 1, + }, + ruleTypesDuration: { + '.index-threshold': 2087868, + 'logs.alert.document.count': 1675765, + 'document.test.': 17687687, + }, + }, + }, + failuresByReason: { + value: { + reasons: { + unknown: { + '.index-threshold': 2, + 'logs.alert.document.count': 1, + 'document.test.': 1, + }, + }, + }, + }, + avgDuration: { value: 10 }, + }, + hits: { + hits: [], + }, + }) + ); + + const telemetry = await getExecutionsPerDayCount(mockEsClient, 'test'); + + expect(mockEsClient.search).toHaveBeenCalledTimes(1); + + expect(telemetry).toStrictEqual({ + avgExecutionTime: 0, + avgExecutionTimeByType: { + '__index-threshold': 1043934, + 'document.test__': 17687687, + 'logs.alert.document.count': 1675765, + }, + countByType: { + '__index-threshold': 2, + 'document.test__': 1, + 'logs.alert.document.count': 1, + }, + countFailuresByReason: { + unknown: 4, + }, + countFailuresByReasonByType: { + unknown: { + '.index-threshold': 2, + 'document.test.': 1, + 'logs.alert.document.count': 1, + }, + }, + countTotal: 4, + countTotalFailures: 4, + }); + }); }); diff --git a/x-pack/plugins/alerting/server/usage/alerts_telemetry.ts b/x-pack/plugins/alerting/server/usage/alerts_telemetry.ts index 7ff9538c1aa26..180ee4300f18c 100644 --- a/x-pack/plugins/alerting/server/usage/alerts_telemetry.ts +++ b/x-pack/plugins/alerting/server/usage/alerts_telemetry.ts @@ -38,6 +38,65 @@ const alertTypeMetric = { }, }; +const ruleTypeExecutionsMetric = { + scripted_metric: { + init_script: 'state.ruleTypes = [:]; state.ruleTypesDuration = [:];', + map_script: ` + String ruleType = doc['rule.category'].value; + long duration = doc['event.duration'].value / (1000 * 1000); + state.ruleTypes.put(ruleType, state.ruleTypes.containsKey(ruleType) ? state.ruleTypes.get(ruleType) + 1 : 1); + state.ruleTypesDuration.put(ruleType, state.ruleTypesDuration.containsKey(ruleType) ? state.ruleTypesDuration.get(ruleType) + duration : duration); + `, + // Combine script is executed per cluster, but we already have a key-value pair per cluster. + // Despite docs that say this is optional, this script can't be blank. + combine_script: 'return state', + // Reduce script is executed across all clusters, so we need to add up all the total from each cluster + // This also needs to account for having no data + reduce_script: ` + Map result = [:]; + for (Map m : states.toArray()) { + if (m !== null) { + for (String k : m.keySet()) { + result.put(k, result.containsKey(k) ? result.get(k) + m.get(k) : m.get(k)); + } + } + } + return result; + `, + }, +}; + +const ruleTypeFailureExecutionsMetric = { + scripted_metric: { + init_script: 'state.reasons = [:]', + map_script: ` + if (doc['event.outcome'].value == 'failure') { + String reason = doc['event.reason'].value; + String ruleType = doc['rule.category'].value; + Map ruleTypes = state.reasons.containsKey(reason) ? state.reasons.get(reason) : [:]; + ruleTypes.put(ruleType, ruleTypes.containsKey(ruleType) ? ruleTypes.get(ruleType) + 1 : 1); + state.reasons.put(reason, ruleTypes); + } + `, + // Combine script is executed per cluster, but we already have a key-value pair per cluster. + // Despite docs that say this is optional, this script can't be blank. + combine_script: 'return state', + // Reduce script is executed across all clusters, so we need to add up all the total from each cluster + // This also needs to account for having no data + reduce_script: ` + Map result = [:]; + for (Map m : states.toArray()) { + if (m !== null) { + for (String k : m.keySet()) { + result.put(k, result.containsKey(k) ? result.get(k) + m.get(k) : m.get(k)); + } + } + } + return result; + `, + }, +}; + export async function getTotalCountAggregations( esClient: ElasticsearchClient, kibanaInex: string @@ -260,4 +319,130 @@ function replaceFirstAndLastDotSymbols(strToReplace: string) { return hasLastSymbolDot ? `${appliedString.slice(0, -1)}__` : appliedString; } -// TODO: Implement executions count telemetry with eventLog, when it will write to index +export async function getExecutionsPerDayCount( + esClient: ElasticsearchClient, + eventLogIndex: string +) { + const { body: searchResult } = await esClient.search({ + index: eventLogIndex, + size: 0, + body: { + query: { + bool: { + filter: { + bool: { + must: [ + { + term: { 'event.action': 'execute' }, + }, + { + term: { 'event.provider': 'alerting' }, + }, + { + range: { + '@timestamp': { + gte: 'now-1d', + }, + }, + }, + ], + }, + }, + }, + }, + aggs: { + byRuleTypeId: ruleTypeExecutionsMetric, + failuresByReason: ruleTypeFailureExecutionsMetric, + avgDuration: { avg: { field: 'event.duration' } }, + }, + }, + }); + + const executionsAggregations = searchResult.aggregations as { + byRuleTypeId: { + value: { ruleTypes: Record; ruleTypesDuration: Record }; + }; + }; + + const aggsAvgExecutionTime = Math.round( + // @ts-expect-error aggegation type is not specified + // convert nanoseconds to milliseconds + searchResult.aggregations.avgDuration.value / (1000 * 1000) + ); + + const executionFailuresAggregations = searchResult.aggregations as { + failuresByReason: { value: { reasons: Record> } }; + }; + + return { + countTotal: Object.keys(executionsAggregations.byRuleTypeId.value.ruleTypes).reduce( + (total: number, key: string) => + parseInt(executionsAggregations.byRuleTypeId.value.ruleTypes[key], 10) + total, + 0 + ), + countByType: Object.keys(executionsAggregations.byRuleTypeId.value.ruleTypes).reduce( + // ES DSL aggregations are returned as `any` by esClient.search + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (obj: any, key: string) => ({ + ...obj, + [replaceFirstAndLastDotSymbols(key)]: + executionsAggregations.byRuleTypeId.value.ruleTypes[key], + }), + {} + ), + countTotalFailures: Object.keys( + executionFailuresAggregations.failuresByReason.value.reasons + ).reduce((total: number, reason: string) => { + const byRuleTypesRefs = executionFailuresAggregations.failuresByReason.value.reasons[reason]; + const countByRuleTypes = Object.keys(byRuleTypesRefs).reduce( + (totalByType, ruleType) => parseInt(byRuleTypesRefs[ruleType] + totalByType, 10), + 0 + ); + return countByRuleTypes + total; + }, 0), + countFailuresByReason: Object.keys( + executionFailuresAggregations.failuresByReason.value.reasons + ).reduce( + // ES DSL aggregations are returned as `any` by esClient.search + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (obj: any, reason: string) => { + const byRuleTypesRefs = + executionFailuresAggregations.failuresByReason.value.reasons[reason]; + const countByRuleTypes = Object.keys(byRuleTypesRefs).reduce( + (totalByType, ruleType) => parseInt(byRuleTypesRefs[ruleType] + totalByType, 10), + 0 + ); + return { + ...obj, + [replaceFirstAndLastDotSymbols(reason)]: countByRuleTypes, + }; + }, + {} + ), + countFailuresByReasonByType: Object.keys( + executionFailuresAggregations.failuresByReason.value.reasons + ).reduce( + // ES DSL aggregations are returned as `any` by esClient.search + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (obj: any, key: string) => ({ + ...obj, + [replaceFirstAndLastDotSymbols(key)]: + executionFailuresAggregations.failuresByReason.value.reasons[key], + }), + {} + ), + avgExecutionTime: aggsAvgExecutionTime, + avgExecutionTimeByType: Object.keys(executionsAggregations.byRuleTypeId.value.ruleTypes).reduce( + // ES DSL aggregations are returned as `any` by esClient.search + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (obj: any, key: string) => ({ + ...obj, + [replaceFirstAndLastDotSymbols(key)]: Math.round( + executionsAggregations.byRuleTypeId.value.ruleTypesDuration[key] / + parseInt(executionsAggregations.byRuleTypeId.value.ruleTypes[key], 10) + ), + }), + {} + ), + }; +} diff --git a/x-pack/plugins/alerting/server/usage/alerts_usage_collector.ts b/x-pack/plugins/alerting/server/usage/alerts_usage_collector.ts index e9405c51dbf15..e5b25ea75fc1c 100644 --- a/x-pack/plugins/alerting/server/usage/alerts_usage_collector.ts +++ b/x-pack/plugins/alerting/server/usage/alerts_usage_collector.ts @@ -50,6 +50,26 @@ const byTypeSchema: MakeSchemaFrom['count_by_type'] = { xpack__ml__anomaly_detection_jobs_health: { type: 'long' }, // eslint-disable-line @typescript-eslint/naming-convention }; +const byReasonSchema: MakeSchemaFrom['count_rules_executions_failured_by_reason_per_day'] = + { + // TODO: Find out an automated way to populate the keys or reformat these into an array (and change the Remote Telemetry indexer accordingly) + DYNAMIC_KEY: { type: 'long' }, + read: { type: 'long' }, + decrypt: { type: 'long' }, + license: { type: 'long' }, + unknown: { type: 'long' }, + }; + +const byReasonSchemaByType: MakeSchemaFrom['count_rules_executions_failured_by_reason_by_type_per_day'] = + { + // TODO: Find out an automated way to populate the keys or reformat these into an array (and change the Remote Telemetry indexer accordingly) + DYNAMIC_KEY: byTypeSchema, + read: byTypeSchema, + decrypt: byTypeSchema, + license: byTypeSchema, + unknown: byTypeSchema, + }; + export function createAlertsUsageCollector( usageCollection: UsageCollectionSetup, taskManager: Promise @@ -92,6 +112,13 @@ export function createAlertsUsageCollector( count_active_by_type: {}, count_by_type: {}, count_rules_namespaces: 0, + count_rules_executions_per_day: 0, + count_rules_executions_by_type_per_day: {}, + count_rules_executions_failured_per_day: 0, + count_rules_executions_failured_by_reason_per_day: {}, + count_rules_executions_failured_by_reason_by_type_per_day: {}, + avg_execution_time_per_day: 0, + avg_execution_time_by_type_per_day: {}, }; } }, @@ -117,6 +144,13 @@ export function createAlertsUsageCollector( count_active_by_type: byTypeSchema, count_by_type: byTypeSchema, count_rules_namespaces: { type: 'long' }, + count_rules_executions_per_day: { type: 'long' }, + count_rules_executions_by_type_per_day: byTypeSchema, + count_rules_executions_failured_per_day: { type: 'long' }, + count_rules_executions_failured_by_reason_per_day: byReasonSchema, + count_rules_executions_failured_by_reason_by_type_per_day: byReasonSchemaByType, + avg_execution_time_per_day: { type: 'long' }, + avg_execution_time_by_type_per_day: byTypeSchema, }, }); } diff --git a/x-pack/plugins/alerting/server/usage/task.ts b/x-pack/plugins/alerting/server/usage/task.ts index 9d39b3765cb5d..2fbd56c105c31 100644 --- a/x-pack/plugins/alerting/server/usage/task.ts +++ b/x-pack/plugins/alerting/server/usage/task.ts @@ -7,13 +7,18 @@ import { Logger, CoreSetup } from 'kibana/server'; import moment from 'moment'; +import { IEventLogService } from '../../../event_log/server'; import { RunContext, TaskManagerSetupContract, TaskManagerStartContract, } from '../../../task_manager/server'; -import { getTotalCountAggregations, getTotalCountInUse } from './alerts_telemetry'; +import { + getTotalCountAggregations, + getTotalCountInUse, + getExecutionsPerDayCount, +} from './alerts_telemetry'; export const TELEMETRY_TASK_TYPE = 'alerting_telemetry'; @@ -23,9 +28,10 @@ export function initializeAlertingTelemetry( logger: Logger, core: CoreSetup, taskManager: TaskManagerSetupContract, - kibanaIndex: string + kibanaIndex: string, + eventLog: IEventLogService ) { - registerAlertingTelemetryTask(logger, core, taskManager, kibanaIndex); + registerAlertingTelemetryTask(logger, core, taskManager, kibanaIndex, eventLog); } export function scheduleAlertingTelemetry(logger: Logger, taskManager?: TaskManagerStartContract) { @@ -38,13 +44,14 @@ function registerAlertingTelemetryTask( logger: Logger, core: CoreSetup, taskManager: TaskManagerSetupContract, - kibanaIndex: string + kibanaIndex: string, + eventLog: IEventLogService ) { taskManager.registerTaskDefinitions({ [TELEMETRY_TASK_TYPE]: { title: 'Alerting usage fetch task', timeout: '5m', - createTaskRunner: telemetryTaskRunner(logger, core, kibanaIndex), + createTaskRunner: telemetryTaskRunner(logger, core, kibanaIndex, eventLog), }, }); } @@ -62,9 +69,15 @@ async function scheduleTasks(logger: Logger, taskManager: TaskManagerStartContra } } -export function telemetryTaskRunner(logger: Logger, core: CoreSetup, kibanaIndex: string) { +export function telemetryTaskRunner( + logger: Logger, + core: CoreSetup, + kibanaIndex: string, + eventLog: IEventLogService +) { return ({ taskInstance }: RunContext) => { const { state } = taskInstance; + const eventLogIndex = eventLog.getIndexPattern(); const getEsClient = () => core.getStartServices().then( ([ @@ -80,8 +93,9 @@ export function telemetryTaskRunner(logger: Logger, core: CoreSetup, kibanaIndex return Promise.all([ getTotalCountAggregations(esClient, kibanaIndex), getTotalCountInUse(esClient, kibanaIndex), + getExecutionsPerDayCount(esClient, eventLogIndex), ]) - .then(([totalCountAggregations, totalInUse]) => { + .then(([totalCountAggregations, totalInUse, totalExecutions]) => { return { state: { runs: (state.runs || 0) + 1, @@ -90,6 +104,15 @@ export function telemetryTaskRunner(logger: Logger, core: CoreSetup, kibanaIndex count_active_total: totalInUse.countTotal, count_disabled_total: totalCountAggregations.count_total - totalInUse.countTotal, count_rules_namespaces: totalInUse.countNamespaces, + count_rules_executions_per_day: totalExecutions.countTotal, + count_rules_executions_by_type_per_day: totalExecutions.countByType, + count_rules_executions_failured_per_day: totalExecutions.countTotalFailures, + count_rules_executions_failured_by_reason_per_day: + totalExecutions.countFailuresByReason, + count_rules_executions_failured_by_reason_by_type_per_day: + totalExecutions.countFailuresByReasonByType, + avg_execution_time_per_day: totalExecutions.avgExecutionTime, + avg_execution_time_by_type_per_day: totalExecutions.avgExecutionTimeByType, }, runAt: getNextMidnight(), }; diff --git a/x-pack/plugins/alerting/server/usage/types.ts b/x-pack/plugins/alerting/server/usage/types.ts index 0e489893a1bbc..50d9b80c44b70 100644 --- a/x-pack/plugins/alerting/server/usage/types.ts +++ b/x-pack/plugins/alerting/server/usage/types.ts @@ -12,6 +12,13 @@ export interface AlertsUsage { count_by_type: Record; count_active_by_type: Record; count_rules_namespaces: number; + count_rules_executions_per_day: number; + count_rules_executions_by_type_per_day: Record; + count_rules_executions_failured_per_day: number; + count_rules_executions_failured_by_reason_per_day: Record; + count_rules_executions_failured_by_reason_by_type_per_day: Record>; + avg_execution_time_per_day: number; + avg_execution_time_by_type_per_day: Record; throttle_time: { min: number; avg: number; diff --git a/x-pack/plugins/event_log/server/event_log_service.mock.ts b/x-pack/plugins/event_log/server/event_log_service.mock.ts index a3ad81eb0e5a6..f43f3e025a7cf 100644 --- a/x-pack/plugins/event_log/server/event_log_service.mock.ts +++ b/x-pack/plugins/event_log/server/event_log_service.mock.ts @@ -17,6 +17,7 @@ const createEventLogServiceMock = () => { getProviderActions: jest.fn(), registerSavedObjectProvider: jest.fn(), getLogger: jest.fn().mockReturnValue(eventLoggerMock.create()), + getIndexPattern: jest.fn(), }; return mock; }; diff --git a/x-pack/plugins/event_log/server/event_log_service.ts b/x-pack/plugins/event_log/server/event_log_service.ts index 993631ed3ca8a..2cf22b0f20755 100644 --- a/x-pack/plugins/event_log/server/event_log_service.ts +++ b/x-pack/plugins/event_log/server/event_log_service.ts @@ -92,6 +92,10 @@ export class EventLogService implements IEventLogService { return this.savedObjectProviderRegistry.registerProvider(type, provider); } + getIndexPattern() { + return this.esContext.esNames.indexPattern; + } + getLogger(initialProperties: IEvent): IEventLogger { return new EventLogger({ esContext: this.esContext, diff --git a/x-pack/plugins/event_log/server/types.ts b/x-pack/plugins/event_log/server/types.ts index c50bed7e01dd5..6ffde7fd6dbe0 100644 --- a/x-pack/plugins/event_log/server/types.ts +++ b/x-pack/plugins/event_log/server/types.ts @@ -33,6 +33,7 @@ export interface IEventLogService { getProviderActions(): Map>; registerSavedObjectProvider(type: string, provider: SavedObjectProvider): void; getLogger(properties: IEvent): IEventLogger; + getIndexPattern(): string; } export interface IEventLogClientService { diff --git a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json index 2786cab4fe963..8ac619d479bef 100644 --- a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json +++ b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @@ -100,6 +100,49 @@ } } }, + "count_actions_executions_per_day": { + "type": "long" + }, + "count_actions_executions_by_type_per_day": { + "properties": { + "DYNAMIC_KEY": { + "type": "long" + }, + "__email": { + "type": "long" + }, + "__index": { + "type": "long" + }, + "__pagerduty": { + "type": "long" + }, + "__swimlane": { + "type": "long" + }, + "__server-log": { + "type": "long" + }, + "__slack": { + "type": "long" + }, + "__webhook": { + "type": "long" + }, + "__servicenow": { + "type": "long" + }, + "__jira": { + "type": "long" + }, + "__resilient": { + "type": "long" + }, + "__teams": { + "type": "long" + } + } + }, "count_active_email_connectors_by_service_type": { "properties": { "DYNAMIC_KEY": { @@ -127,6 +170,92 @@ }, "count_actions_namespaces": { "type": "long" + }, + "count_actions_executions_failed_per_day": { + "type": "long" + }, + "count_actions_executions_failed_by_type_per_day": { + "properties": { + "DYNAMIC_KEY": { + "type": "long" + }, + "__email": { + "type": "long" + }, + "__index": { + "type": "long" + }, + "__pagerduty": { + "type": "long" + }, + "__swimlane": { + "type": "long" + }, + "__server-log": { + "type": "long" + }, + "__slack": { + "type": "long" + }, + "__webhook": { + "type": "long" + }, + "__servicenow": { + "type": "long" + }, + "__jira": { + "type": "long" + }, + "__resilient": { + "type": "long" + }, + "__teams": { + "type": "long" + } + } + }, + "avg_execution_time_per_day": { + "type": "long" + }, + "avg_execution_time_by_type_per_day": { + "properties": { + "DYNAMIC_KEY": { + "type": "long" + }, + "__email": { + "type": "long" + }, + "__index": { + "type": "long" + }, + "__pagerduty": { + "type": "long" + }, + "__swimlane": { + "type": "long" + }, + "__server-log": { + "type": "long" + }, + "__slack": { + "type": "long" + }, + "__webhook": { + "type": "long" + }, + "__servicenow": { + "type": "long" + }, + "__jira": { + "type": "long" + }, + "__resilient": { + "type": "long" + }, + "__teams": { + "type": "long" + } + } } } }, @@ -352,6 +481,633 @@ }, "count_rules_namespaces": { "type": "long" + }, + "count_rules_executions_per_day": { + "type": "long" + }, + "count_rules_executions_by_type_per_day": { + "properties": { + "DYNAMIC_KEY": { + "type": "long" + }, + "__index-threshold": { + "type": "long" + }, + "__es-query": { + "type": "long" + }, + "transform_health": { + "type": "long" + }, + "apm__error_rate": { + "type": "long" + }, + "apm__transaction_error_rate": { + "type": "long" + }, + "apm__transaction_duration": { + "type": "long" + }, + "apm__transaction_duration_anomaly": { + "type": "long" + }, + "metrics__alert__threshold": { + "type": "long" + }, + "metrics__alert__inventory__threshold": { + "type": "long" + }, + "logs__alert__document__count": { + "type": "long" + }, + "monitoring_alert_cluster_health": { + "type": "long" + }, + "monitoring_alert_cpu_usage": { + "type": "long" + }, + "monitoring_alert_disk_usage": { + "type": "long" + }, + "monitoring_alert_elasticsearch_version_mismatch": { + "type": "long" + }, + "monitoring_alert_kibana_version_mismatch": { + "type": "long" + }, + "monitoring_alert_license_expiration": { + "type": "long" + }, + "monitoring_alert_logstash_version_mismatch": { + "type": "long" + }, + "monitoring_alert_nodes_changed": { + "type": "long" + }, + "siem__signals": { + "type": "long" + }, + "siem__notifications": { + "type": "long" + }, + "xpack__uptime__alerts__monitorStatus": { + "type": "long" + }, + "xpack__uptime__alerts__tls": { + "type": "long" + }, + "xpack__uptime__alerts__durationAnomaly": { + "type": "long" + }, + "__geo-containment": { + "type": "long" + }, + "xpack__ml__anomaly_detection_alert": { + "type": "long" + }, + "xpack__ml__anomaly_detection_jobs_health": { + "type": "long" + } + } + }, + "count_rules_executions_failured_per_day": { + "type": "long" + }, + "count_rules_executions_failured_by_reason_per_day": { + "properties": { + "DYNAMIC_KEY": { + "type": "long" + }, + "read": { + "type": "long" + }, + "decrypt": { + "type": "long" + }, + "license": { + "type": "long" + }, + "unknown": { + "type": "long" + } + } + }, + "count_rules_executions_failured_by_reason_by_type_per_day": { + "properties": { + "DYNAMIC_KEY": { + "properties": { + "DYNAMIC_KEY": { + "type": "long" + }, + "__index-threshold": { + "type": "long" + }, + "__es-query": { + "type": "long" + }, + "transform_health": { + "type": "long" + }, + "apm__error_rate": { + "type": "long" + }, + "apm__transaction_error_rate": { + "type": "long" + }, + "apm__transaction_duration": { + "type": "long" + }, + "apm__transaction_duration_anomaly": { + "type": "long" + }, + "metrics__alert__threshold": { + "type": "long" + }, + "metrics__alert__inventory__threshold": { + "type": "long" + }, + "logs__alert__document__count": { + "type": "long" + }, + "monitoring_alert_cluster_health": { + "type": "long" + }, + "monitoring_alert_cpu_usage": { + "type": "long" + }, + "monitoring_alert_disk_usage": { + "type": "long" + }, + "monitoring_alert_elasticsearch_version_mismatch": { + "type": "long" + }, + "monitoring_alert_kibana_version_mismatch": { + "type": "long" + }, + "monitoring_alert_license_expiration": { + "type": "long" + }, + "monitoring_alert_logstash_version_mismatch": { + "type": "long" + }, + "monitoring_alert_nodes_changed": { + "type": "long" + }, + "siem__signals": { + "type": "long" + }, + "siem__notifications": { + "type": "long" + }, + "xpack__uptime__alerts__monitorStatus": { + "type": "long" + }, + "xpack__uptime__alerts__tls": { + "type": "long" + }, + "xpack__uptime__alerts__durationAnomaly": { + "type": "long" + }, + "__geo-containment": { + "type": "long" + }, + "xpack__ml__anomaly_detection_alert": { + "type": "long" + }, + "xpack__ml__anomaly_detection_jobs_health": { + "type": "long" + } + } + }, + "read": { + "properties": { + "DYNAMIC_KEY": { + "type": "long" + }, + "__index-threshold": { + "type": "long" + }, + "__es-query": { + "type": "long" + }, + "transform_health": { + "type": "long" + }, + "apm__error_rate": { + "type": "long" + }, + "apm__transaction_error_rate": { + "type": "long" + }, + "apm__transaction_duration": { + "type": "long" + }, + "apm__transaction_duration_anomaly": { + "type": "long" + }, + "metrics__alert__threshold": { + "type": "long" + }, + "metrics__alert__inventory__threshold": { + "type": "long" + }, + "logs__alert__document__count": { + "type": "long" + }, + "monitoring_alert_cluster_health": { + "type": "long" + }, + "monitoring_alert_cpu_usage": { + "type": "long" + }, + "monitoring_alert_disk_usage": { + "type": "long" + }, + "monitoring_alert_elasticsearch_version_mismatch": { + "type": "long" + }, + "monitoring_alert_kibana_version_mismatch": { + "type": "long" + }, + "monitoring_alert_license_expiration": { + "type": "long" + }, + "monitoring_alert_logstash_version_mismatch": { + "type": "long" + }, + "monitoring_alert_nodes_changed": { + "type": "long" + }, + "siem__signals": { + "type": "long" + }, + "siem__notifications": { + "type": "long" + }, + "xpack__uptime__alerts__monitorStatus": { + "type": "long" + }, + "xpack__uptime__alerts__tls": { + "type": "long" + }, + "xpack__uptime__alerts__durationAnomaly": { + "type": "long" + }, + "__geo-containment": { + "type": "long" + }, + "xpack__ml__anomaly_detection_alert": { + "type": "long" + }, + "xpack__ml__anomaly_detection_jobs_health": { + "type": "long" + } + } + }, + "decrypt": { + "properties": { + "DYNAMIC_KEY": { + "type": "long" + }, + "__index-threshold": { + "type": "long" + }, + "__es-query": { + "type": "long" + }, + "transform_health": { + "type": "long" + }, + "apm__error_rate": { + "type": "long" + }, + "apm__transaction_error_rate": { + "type": "long" + }, + "apm__transaction_duration": { + "type": "long" + }, + "apm__transaction_duration_anomaly": { + "type": "long" + }, + "metrics__alert__threshold": { + "type": "long" + }, + "metrics__alert__inventory__threshold": { + "type": "long" + }, + "logs__alert__document__count": { + "type": "long" + }, + "monitoring_alert_cluster_health": { + "type": "long" + }, + "monitoring_alert_cpu_usage": { + "type": "long" + }, + "monitoring_alert_disk_usage": { + "type": "long" + }, + "monitoring_alert_elasticsearch_version_mismatch": { + "type": "long" + }, + "monitoring_alert_kibana_version_mismatch": { + "type": "long" + }, + "monitoring_alert_license_expiration": { + "type": "long" + }, + "monitoring_alert_logstash_version_mismatch": { + "type": "long" + }, + "monitoring_alert_nodes_changed": { + "type": "long" + }, + "siem__signals": { + "type": "long" + }, + "siem__notifications": { + "type": "long" + }, + "xpack__uptime__alerts__monitorStatus": { + "type": "long" + }, + "xpack__uptime__alerts__tls": { + "type": "long" + }, + "xpack__uptime__alerts__durationAnomaly": { + "type": "long" + }, + "__geo-containment": { + "type": "long" + }, + "xpack__ml__anomaly_detection_alert": { + "type": "long" + }, + "xpack__ml__anomaly_detection_jobs_health": { + "type": "long" + } + } + }, + "license": { + "properties": { + "DYNAMIC_KEY": { + "type": "long" + }, + "__index-threshold": { + "type": "long" + }, + "__es-query": { + "type": "long" + }, + "transform_health": { + "type": "long" + }, + "apm__error_rate": { + "type": "long" + }, + "apm__transaction_error_rate": { + "type": "long" + }, + "apm__transaction_duration": { + "type": "long" + }, + "apm__transaction_duration_anomaly": { + "type": "long" + }, + "metrics__alert__threshold": { + "type": "long" + }, + "metrics__alert__inventory__threshold": { + "type": "long" + }, + "logs__alert__document__count": { + "type": "long" + }, + "monitoring_alert_cluster_health": { + "type": "long" + }, + "monitoring_alert_cpu_usage": { + "type": "long" + }, + "monitoring_alert_disk_usage": { + "type": "long" + }, + "monitoring_alert_elasticsearch_version_mismatch": { + "type": "long" + }, + "monitoring_alert_kibana_version_mismatch": { + "type": "long" + }, + "monitoring_alert_license_expiration": { + "type": "long" + }, + "monitoring_alert_logstash_version_mismatch": { + "type": "long" + }, + "monitoring_alert_nodes_changed": { + "type": "long" + }, + "siem__signals": { + "type": "long" + }, + "siem__notifications": { + "type": "long" + }, + "xpack__uptime__alerts__monitorStatus": { + "type": "long" + }, + "xpack__uptime__alerts__tls": { + "type": "long" + }, + "xpack__uptime__alerts__durationAnomaly": { + "type": "long" + }, + "__geo-containment": { + "type": "long" + }, + "xpack__ml__anomaly_detection_alert": { + "type": "long" + }, + "xpack__ml__anomaly_detection_jobs_health": { + "type": "long" + } + } + }, + "unknown": { + "properties": { + "DYNAMIC_KEY": { + "type": "long" + }, + "__index-threshold": { + "type": "long" + }, + "__es-query": { + "type": "long" + }, + "transform_health": { + "type": "long" + }, + "apm__error_rate": { + "type": "long" + }, + "apm__transaction_error_rate": { + "type": "long" + }, + "apm__transaction_duration": { + "type": "long" + }, + "apm__transaction_duration_anomaly": { + "type": "long" + }, + "metrics__alert__threshold": { + "type": "long" + }, + "metrics__alert__inventory__threshold": { + "type": "long" + }, + "logs__alert__document__count": { + "type": "long" + }, + "monitoring_alert_cluster_health": { + "type": "long" + }, + "monitoring_alert_cpu_usage": { + "type": "long" + }, + "monitoring_alert_disk_usage": { + "type": "long" + }, + "monitoring_alert_elasticsearch_version_mismatch": { + "type": "long" + }, + "monitoring_alert_kibana_version_mismatch": { + "type": "long" + }, + "monitoring_alert_license_expiration": { + "type": "long" + }, + "monitoring_alert_logstash_version_mismatch": { + "type": "long" + }, + "monitoring_alert_nodes_changed": { + "type": "long" + }, + "siem__signals": { + "type": "long" + }, + "siem__notifications": { + "type": "long" + }, + "xpack__uptime__alerts__monitorStatus": { + "type": "long" + }, + "xpack__uptime__alerts__tls": { + "type": "long" + }, + "xpack__uptime__alerts__durationAnomaly": { + "type": "long" + }, + "__geo-containment": { + "type": "long" + }, + "xpack__ml__anomaly_detection_alert": { + "type": "long" + }, + "xpack__ml__anomaly_detection_jobs_health": { + "type": "long" + } + } + } + } + }, + "avg_execution_time_per_day": { + "type": "long" + }, + "avg_execution_time_by_type_per_day": { + "properties": { + "DYNAMIC_KEY": { + "type": "long" + }, + "__index-threshold": { + "type": "long" + }, + "__es-query": { + "type": "long" + }, + "transform_health": { + "type": "long" + }, + "apm__error_rate": { + "type": "long" + }, + "apm__transaction_error_rate": { + "type": "long" + }, + "apm__transaction_duration": { + "type": "long" + }, + "apm__transaction_duration_anomaly": { + "type": "long" + }, + "metrics__alert__threshold": { + "type": "long" + }, + "metrics__alert__inventory__threshold": { + "type": "long" + }, + "logs__alert__document__count": { + "type": "long" + }, + "monitoring_alert_cluster_health": { + "type": "long" + }, + "monitoring_alert_cpu_usage": { + "type": "long" + }, + "monitoring_alert_disk_usage": { + "type": "long" + }, + "monitoring_alert_elasticsearch_version_mismatch": { + "type": "long" + }, + "monitoring_alert_kibana_version_mismatch": { + "type": "long" + }, + "monitoring_alert_license_expiration": { + "type": "long" + }, + "monitoring_alert_logstash_version_mismatch": { + "type": "long" + }, + "monitoring_alert_nodes_changed": { + "type": "long" + }, + "siem__signals": { + "type": "long" + }, + "siem__notifications": { + "type": "long" + }, + "xpack__uptime__alerts__monitorStatus": { + "type": "long" + }, + "xpack__uptime__alerts__tls": { + "type": "long" + }, + "xpack__uptime__alerts__durationAnomaly": { + "type": "long" + }, + "__geo-containment": { + "type": "long" + }, + "xpack__ml__anomaly_detection_alert": { + "type": "long" + }, + "xpack__ml__anomaly_detection_jobs_health": { + "type": "long" + } + } } } }, From 0e3dea35a9b1e5def3f24fbddb7724a49a8316a1 Mon Sep 17 00:00:00 2001 From: Corey Robertson Date: Tue, 2 Nov 2021 13:38:37 -0400 Subject: [PATCH 10/41] Fix bug where cache is rebuilt incorrectly (#114105) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/plugins/canvas/public/lib/run_interpreter.ts | 8 +++++--- x-pack/plugins/canvas/public/state/actions/elements.js | 3 ++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/canvas/public/lib/run_interpreter.ts b/x-pack/plugins/canvas/public/lib/run_interpreter.ts index 6c10b82fae3fd..9633d91b8b8b2 100644 --- a/x-pack/plugins/canvas/public/lib/run_interpreter.ts +++ b/x-pack/plugins/canvas/public/lib/run_interpreter.ts @@ -19,11 +19,13 @@ interface Options { */ export async function interpretAst( ast: ExpressionAstExpression, - variables: Record + variables: Record, + input: ExpressionValue = null ): Promise { const context = { variables }; const { execute } = pluginServices.getServices().expressions; - return await execute(ast, null, context).getData().pipe(pluck('result')).toPromise(); + + return await execute(ast, input, context).getData().pipe(pluck('result')).toPromise(); } /** @@ -43,9 +45,9 @@ export async function runInterpreter( options: Options = {} ): Promise { const context = { variables }; - try { const { execute } = pluginServices.getServices().expressions; + const renderable = await execute(ast, input, context) .getData() .pipe(pluck('result')) diff --git a/x-pack/plugins/canvas/public/state/actions/elements.js b/x-pack/plugins/canvas/public/state/actions/elements.js index a8302cf094016..c8d322163b54f 100644 --- a/x-pack/plugins/canvas/public/state/actions/elements.js +++ b/x-pack/plugins/canvas/public/state/actions/elements.js @@ -111,7 +111,8 @@ export const fetchContext = createThunk( ...element.ast, chain: astChain, }, - variables + variables, + prevContextValue ).then((value) => { dispatch( args.setValue({ From c91e44ca8e9b6363069773236f8532bcd2915cbc Mon Sep 17 00:00:00 2001 From: Shahzad Date: Tue, 2 Nov 2021 18:44:53 +0100 Subject: [PATCH 11/41] [Uptime] Fix regression in logging queries on inspect (#117124) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/plugins/uptime/server/lib/lib.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/uptime/server/lib/lib.ts b/x-pack/plugins/uptime/server/lib/lib.ts index 894bf743499f9..fbd0494a3ca82 100644 --- a/x-pack/plugins/uptime/server/lib/lib.ts +++ b/x-pack/plugins/uptime/server/lib/lib.ts @@ -59,8 +59,6 @@ export function createUptimeESClient({ request?: KibanaRequest; savedObjectsClient: SavedObjectsClientContract | ISavedObjectsRepository; }) { - const { _inspect = false } = (request?.query as { _inspect: boolean }) ?? {}; - return { baseESClient: esClient, async search( @@ -101,10 +99,9 @@ export function createUptimeESClient({ startTime: startTimeNow, }) ); - } - - if (_inspect && request) { - debugESCall({ startTime, request, esError, operationName: 'search', params: esParams }); + if (request) { + debugESCall({ startTime, request, esError, operationName: 'search', params: esParams }); + } } if (esError) { @@ -129,8 +126,9 @@ export function createUptimeESClient({ } catch (e) { esError = e; } + const inspectableEsQueries = inspectableEsQueriesMap.get(request!); - if (_inspect && request) { + if (inspectableEsQueries && request) { debugESCall({ startTime, request, esError, operationName: 'count', params: esParams }); } From 03cebac5472f66e4c68183dd2c2a5d44f72ad057 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20S=C3=A1nchez?= Date: Tue, 2 Nov 2021 18:53:31 +0100 Subject: [PATCH 12/41] [Security solution] [Endpoint] Fixes for operator "match_any" in event filters card (#117136) * Fix translations and allow negative operators * UI fixes * Remove useCallbacks and update test --- .../artifact_entry_card.tsx | 16 +++-- .../components/criteria_conditions.tsx | 56 ++++++++++++----- .../components/translations.ts | 16 ++++- .../__snapshots__/index.test.tsx.snap | 60 +++++++++---------- 4 files changed, 97 insertions(+), 51 deletions(-) diff --git a/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/artifact_entry_card.tsx b/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/artifact_entry_card.tsx index d5f8c2dc74788..89d2f029e9538 100644 --- a/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/artifact_entry_card.tsx +++ b/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/artifact_entry_card.tsx @@ -80,16 +80,20 @@ export const ArtifactEntryCard = memo( data-test-subj={getTestId('subHeader')} /> - - {!hideDescription && ( - - {artifact.description} - + <> + + + {artifact.description} + + )} {!hideComments ? ( - + <> + + + ) : null} diff --git a/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/components/criteria_conditions.tsx b/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/components/criteria_conditions.tsx index 743eac7a15458..24244aad3ef99 100644 --- a/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/components/criteria_conditions.tsx +++ b/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/components/criteria_conditions.tsx @@ -6,7 +6,14 @@ */ import React, { memo, useCallback, useMemo } from 'react'; -import { CommonProps, EuiExpression, EuiToken, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { + CommonProps, + EuiExpression, + EuiToken, + EuiFlexGroup, + EuiFlexItem, + EuiBadge, +} from '@elastic/eui'; import styled from 'styled-components'; import { ListOperatorTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; import { @@ -21,6 +28,8 @@ import { CONDITION_OPERATOR_TYPE_MATCH_ANY, CONDITION_OPERATOR_TYPE_EXISTS, CONDITION_OPERATOR_TYPE_LIST, + CONDITION_OPERATOR_TYPE_NOT_MATCH_ANY, + CONDITION_OPERATOR_TYPE_NOT_MATCH, } from './translations'; import { ArtifactInfo, ArtifactInfoEntry } from '../types'; import { useTestIdGenerator } from '../../hooks/use_test_id_generator'; @@ -32,7 +41,7 @@ const OS_LABELS = Object.freeze({ windows: OS_WINDOWS, }); -const OPERATOR_TYPE_LABELS = Object.freeze({ +const OPERATOR_TYPE_LABELS_INCLUDED = Object.freeze({ [ListOperatorTypeEnum.NESTED]: CONDITION_OPERATOR_TYPE_NESTED, [ListOperatorTypeEnum.MATCH_ANY]: CONDITION_OPERATOR_TYPE_MATCH_ANY, [ListOperatorTypeEnum.MATCH]: CONDITION_OPERATOR_TYPE_MATCH, @@ -41,8 +50,13 @@ const OPERATOR_TYPE_LABELS = Object.freeze({ [ListOperatorTypeEnum.LIST]: CONDITION_OPERATOR_TYPE_LIST, }); +const OPERATOR_TYPE_LABELS_EXCLUDED = Object.freeze({ + [ListOperatorTypeEnum.MATCH_ANY]: CONDITION_OPERATOR_TYPE_NOT_MATCH_ANY, + [ListOperatorTypeEnum.MATCH]: CONDITION_OPERATOR_TYPE_NOT_MATCH, +}); + const EuiFlexGroupNested = styled(EuiFlexGroup)` - margin-left: ${({ theme }) => theme.eui.spacerSizes.l}; + margin-left: ${({ theme }) => theme.eui.spacerSizes.xl}; `; const EuiFlexItemNested = styled(EuiFlexItem)` @@ -67,11 +81,30 @@ export const CriteriaConditions = memo( .join(', '); }, [os]); + const getEntryValue = (type: string, value: string | string[]) => { + if (type === 'match_any' && Array.isArray(value)) { + return value.map((currentValue) => {currentValue}); + } + return value; + }; + + const getEntryOperator = (type: string, operator: string) => { + if (type === 'nested') return; + return operator === 'included' + ? OPERATOR_TYPE_LABELS_INCLUDED[type as keyof typeof OPERATOR_TYPE_LABELS_INCLUDED] ?? type + : OPERATOR_TYPE_LABELS_EXCLUDED[type as keyof typeof OPERATOR_TYPE_LABELS_EXCLUDED] ?? type; + }; + const getNestedEntriesContent = useCallback( (type: string, nestedEntries: ArtifactInfoEntry[]) => { if (type === 'nested' && nestedEntries.length) { return nestedEntries.map( - ({ field: nestedField, type: nestedType, value: nestedValue }) => { + ({ + field: nestedField, + type: nestedType, + value: nestedValue, + operator: nestedOperator, + }) => { return ( ( @@ -113,7 +143,7 @@ export const CriteriaConditions = memo( - {entries.map(({ field, type, value, entries: nestedEntries = [] }) => { + {entries.map(({ field, type, value, operator, entries: nestedEntries = [] }) => { return (
( color="subdued" /> {getNestedEntriesContent(type, nestedEntries)}
diff --git a/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/components/translations.ts b/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/components/translations.ts index b2c0edfb2b9eb..3290a52c1c37d 100644 --- a/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/components/translations.ts +++ b/x-pack/plugins/security_solution/public/management/components/artifact_entry_card/components/translations.ts @@ -54,6 +54,13 @@ export const CONDITION_OPERATOR_TYPE_MATCH = i18n.translate( } ); +export const CONDITION_OPERATOR_TYPE_NOT_MATCH = i18n.translate( + 'xpack.securitySolution.artifactCard.conditions.matchOperator.not', + { + defaultMessage: 'IS NOT', + } +); + export const CONDITION_OPERATOR_TYPE_WILDCARD = i18n.translate( 'xpack.securitySolution.artifactCard.conditions.wildcardOperator', { @@ -71,7 +78,14 @@ export const CONDITION_OPERATOR_TYPE_NESTED = i18n.translate( export const CONDITION_OPERATOR_TYPE_MATCH_ANY = i18n.translate( 'xpack.securitySolution.artifactCard.conditions.matchAnyOperator', { - defaultMessage: 'is any', + defaultMessage: 'is one of', + } +); + +export const CONDITION_OPERATOR_TYPE_NOT_MATCH_ANY = i18n.translate( + 'xpack.securitySolution.artifactCard.conditions.matchAnyOperator.not', + { + defaultMessage: 'is not one of', } ); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/__snapshots__/index.test.tsx.snap index ea5869f79275f..f22cc1179f0d3 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/__snapshots__/index.test.tsx.snap @@ -743,7 +743,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
Date: Tue, 2 Nov 2021 10:55:32 -0700 Subject: [PATCH 13/41] Exceptions export duplicates (#116698) ## Summary Addresses https://github.com/elastic/kibana/issues/116329 Removes duplicate exception lists on rule export when multiple rules reference the same list. --- .../response/exception_list_schema.mock.ts | 21 +++++++++++ .../server/scripts/check_env_variables.sh | 10 ----- .../exception_list_client.mock.ts | 9 +++-- .../rules/get_export_all.test.ts | 4 +- .../rules/get_export_rule_exceptions.test.ts | 37 +++++++++++++++---- .../rules/get_export_rule_exceptions.ts | 17 ++++++--- .../scripts/check_env_variables.sh | 10 ----- 7 files changed, 70 insertions(+), 38 deletions(-) diff --git a/x-pack/plugins/lists/common/schemas/response/exception_list_schema.mock.ts b/x-pack/plugins/lists/common/schemas/response/exception_list_schema.mock.ts index 42c35ba1a5d7a..eca17b4c835d6 100644 --- a/x-pack/plugins/lists/common/schemas/response/exception_list_schema.mock.ts +++ b/x-pack/plugins/lists/common/schemas/response/exception_list_schema.mock.ts @@ -16,6 +16,7 @@ import { import { DATE_NOW, DESCRIPTION, + DETECTION_TYPE, ELASTIC_USER, ENDPOINT_TYPE, IMMUTABLE, @@ -48,6 +49,26 @@ export const getExceptionListSchemaMock = (): ExceptionListSchema => ({ version: VERSION, }); +export const getDetectionsExceptionListSchemaMock = (): ExceptionListSchema => ({ + _version: _VERSION, + created_at: DATE_NOW, + created_by: USER, + description: DESCRIPTION, + id: '1', + immutable: IMMUTABLE, + list_id: 'exception_list_id', + meta: META, + name: 'Sample Exception List', + namespace_type: 'single', + os_types: ['linux'], + tags: ['user added string for a tag', 'malware'], + tie_breaker_id: TIE_BREAKER, + type: DETECTION_TYPE, + updated_at: DATE_NOW, + updated_by: 'user_name', + version: VERSION, +}); + export const getTrustedAppsListSchemaMock = (): ExceptionListSchema => { return { ...getExceptionListSchemaMock(), diff --git a/x-pack/plugins/lists/server/scripts/check_env_variables.sh b/x-pack/plugins/lists/server/scripts/check_env_variables.sh index 4df0e42adf9f3..df2354ed8398a 100755 --- a/x-pack/plugins/lists/server/scripts/check_env_variables.sh +++ b/x-pack/plugins/lists/server/scripts/check_env_variables.sh @@ -30,13 +30,3 @@ if [ -z "${KIBANA_URL}" ]; then echo "Set KIBANA_URL in your environment" exit 1 fi - -if [ -z "${TASK_MANAGER_INDEX}" ]; then - echo "Set TASK_MANAGER_INDEX in your environment" - exit 1 -fi - -if [ -z "${KIBANA_INDEX}" ]; then - echo "Set KIBANA_INDEX in your environment" - exit 1 -fi diff --git a/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.mock.ts b/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.mock.ts index f5f6a4f1f2d5a..a780080dabc83 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.mock.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.mock.ts @@ -11,6 +11,7 @@ import { getFoundExceptionListSchemaMock } from '../../../common/schemas/respons import { getFoundExceptionListItemSchemaMock } from '../../../common/schemas/response/found_exception_list_item_schema.mock'; import { getExceptionListItemSchemaMock } from '../../../common/schemas/response/exception_list_item_schema.mock'; import { + getDetectionsExceptionListSchemaMock, getExceptionListSchemaMock, getTrustedAppsListSchemaMock, } from '../../../common/schemas/response/exception_list_schema.mock'; @@ -31,10 +32,12 @@ export class ExceptionListClientMock extends ExceptionListClient { public createTrustedAppsList = jest.fn().mockResolvedValue(getTrustedAppsListSchemaMock()); public createEndpointList = jest.fn().mockResolvedValue(getExceptionListSchemaMock()); public exportExceptionListAndItems = jest.fn().mockResolvedValue({ - exportData: 'exportString', + exportData: `${JSON.stringify(getDetectionsExceptionListSchemaMock())}\n${JSON.stringify( + getExceptionListItemSchemaMock({ list_id: 'exception_list_id' }) + )}`, exportDetails: { - exported_exception_list_count: 0, - exported_exception_list_item_count: 0, + exported_exception_list_count: 1, + exported_exception_list_item_count: 1, missing_exception_list_item_count: 0, missing_exception_list_items: [], missing_exception_lists: [], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_all.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_all.test.ts index 80df4c94971cc..99f5f76be1a7c 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_all.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_all.test.ts @@ -101,8 +101,8 @@ describe.each([ exceptions_list: getListArrayMock(), }); expect(detailsJson).toEqual({ - exported_exception_list_count: 0, - exported_exception_list_item_count: 0, + exported_exception_list_count: 1, + exported_exception_list_item_count: 1, exported_rules_count: 1, missing_exception_list_item_count: 0, missing_exception_list_items: [], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_rule_exceptions.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_rule_exceptions.test.ts index dd7e59c74601c..614c0ae0a1281 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_rule_exceptions.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_rule_exceptions.test.ts @@ -8,15 +8,15 @@ import { ENDPOINT_LIST_ID } from '@kbn/securitysolution-list-constants'; import { getExceptionListClientMock } from '../../../../../lists/server/services/exception_lists/exception_list_client.mock'; +import { getDetectionsExceptionListSchemaMock } from '../../../../../lists/common/schemas/response/exception_list_schema.mock'; +import { getExceptionListItemSchemaMock } from '../../../../../lists/common/schemas/response/exception_list_item_schema.mock'; + import { getRuleExceptionsForExport, getExportableExceptions, getDefaultExportDetails, } from './get_export_rule_exceptions'; -import { - getListArrayMock, - getListMock, -} from '../../../../common/detection_engine/schemas/types/lists.mock'; +import { getListMock } from '../../../../common/detection_engine/schemas/types/lists.mock'; describe('get_export_rule_exceptions', () => { describe('getRuleExceptionsForExport', () => { @@ -36,7 +36,24 @@ describe('get_export_rule_exceptions', () => { getExceptionListClientMock() ); - expect(exportData).toEqual('exportString'); + expect(exportData).toEqual( + `${JSON.stringify(getDetectionsExceptionListSchemaMock())}\n${JSON.stringify( + getExceptionListItemSchemaMock({ list_id: 'exception_list_id' }) + )}` + ); + }); + + test('it does not return duplicate exception lists', async () => { + const { exportData } = await getRuleExceptionsForExport( + [getListMock(), getListMock()], + getExceptionListClientMock() + ); + + expect(exportData).toEqual( + `${JSON.stringify(getDetectionsExceptionListSchemaMock())}\n${JSON.stringify( + getExceptionListItemSchemaMock({ list_id: 'exception_list_id' }) + )}` + ); }); test('it does not return a global endpoint list', async () => { @@ -60,11 +77,15 @@ describe('get_export_rule_exceptions', () => { test('it returns stringified exception lists and items', async () => { // This rule has 2 exception lists tied to it const { exportData } = await getExportableExceptions( - getListArrayMock(), + [getListMock()], getExceptionListClientMock() ); - expect(exportData).toEqual('exportStringexportString'); + expect(exportData).toEqual( + `${JSON.stringify(getDetectionsExceptionListSchemaMock())}\n${JSON.stringify( + getExceptionListItemSchemaMock({ list_id: 'exception_list_id' }) + )}` + ); }); test('it throws error if error occurs in getting exceptions', async () => { @@ -72,7 +93,7 @@ describe('get_export_rule_exceptions', () => { exceptionsClient.exportExceptionListAndItems = jest.fn().mockRejectedValue(new Error('oops')); // This rule has 2 exception lists tied to it await expect(async () => { - await getExportableExceptions(getListArrayMock(), exceptionsClient); + await getExportableExceptions([getListMock()], exceptionsClient); }).rejects.toThrowErrorMatchingInlineSnapshot(`"oops"`); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_rule_exceptions.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_rule_exceptions.ts index 719649d35c0f0..6faf3fdfe6104 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_rule_exceptions.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/get_export_rule_exceptions.ts @@ -21,10 +21,17 @@ export const getRuleExceptionsForExport = async ( exceptions: ListArray, exceptionsListClient: ExceptionListClient | undefined ): Promise => { + const uniqueExceptionLists = new Set(); + if (exceptionsListClient != null) { - const exceptionsWithoutUnexportableLists = exceptions.filter( - ({ list_id: listId }) => !NON_EXPORTABLE_LIST_IDS.includes(listId) - ); + const exceptionsWithoutUnexportableLists = exceptions.filter((list) => { + if (!uniqueExceptionLists.has(list.id)) { + uniqueExceptionLists.add(list.id); + return !NON_EXPORTABLE_LIST_IDS.includes(list.list_id); + } else { + return false; + } + }); return getExportableExceptions(exceptionsWithoutUnexportableLists, exceptionsListClient); } else { return { exportData: '', exportDetails: getDefaultExportDetails() }; @@ -72,9 +79,9 @@ export const getExportableExceptions = async ( }; /** - * Creates promises of the rules and returns them. + * Creates promises of the exceptions to be exported and returns them. * @param exceptionsListClient Exception Lists client - * @param exceptions The rules to apply the update for + * @param exceptions The exceptions to be exported * @returns Promise of export ready exceptions. */ export const createPromises = ( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/check_env_variables.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/check_env_variables.sh index 4df0e42adf9f3..df2354ed8398a 100755 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/check_env_variables.sh +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/check_env_variables.sh @@ -30,13 +30,3 @@ if [ -z "${KIBANA_URL}" ]; then echo "Set KIBANA_URL in your environment" exit 1 fi - -if [ -z "${TASK_MANAGER_INDEX}" ]; then - echo "Set TASK_MANAGER_INDEX in your environment" - exit 1 -fi - -if [ -z "${KIBANA_INDEX}" ]; then - echo "Set KIBANA_INDEX in your environment" - exit 1 -fi From 47e8f783dc6da74a0f8cca81c02ed0349f540747 Mon Sep 17 00:00:00 2001 From: Bryan Clement Date: Tue, 2 Nov 2021 11:00:43 -0700 Subject: [PATCH 14/41] remove unused enrollment path (#117063) --- api_docs/fleet.json | 12 +----------- x-pack/plugins/fleet/common/constants/routes.ts | 1 - 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/api_docs/fleet.json b/api_docs/fleet.json index b951a5feea633..06c6bf2dbae32 100644 --- a/api_docs/fleet.json +++ b/api_docs/fleet.json @@ -19696,16 +19696,6 @@ "path": "x-pack/plugins/fleet/common/constants/routes.ts", "deprecated": false }, - { - "parentPluginId": "fleet", - "id": "def-common.AGENT_API_ROUTES.ENROLL_PATTERN", - "type": "string", - "tags": [], - "label": "ENROLL_PATTERN", - "description": [], - "path": "x-pack/plugins/fleet/common/constants/routes.ts", - "deprecated": false - }, { "parentPluginId": "fleet", "id": "def-common.AGENT_API_ROUTES.UNENROLL_PATTERN", @@ -21889,4 +21879,4 @@ } ] } -} \ No newline at end of file +} diff --git a/x-pack/plugins/fleet/common/constants/routes.ts b/x-pack/plugins/fleet/common/constants/routes.ts index 60795799bb32d..aa5e0dbcd5ed1 100644 --- a/x-pack/plugins/fleet/common/constants/routes.ts +++ b/x-pack/plugins/fleet/common/constants/routes.ts @@ -89,7 +89,6 @@ export const AGENT_API_ROUTES = { CHECKIN_PATTERN: `${API_ROOT}/agents/{agentId}/checkin`, ACKS_PATTERN: `${API_ROOT}/agents/{agentId}/acks`, ACTIONS_PATTERN: `${API_ROOT}/agents/{agentId}/actions`, - ENROLL_PATTERN: `${API_ROOT}/agents/enroll`, UNENROLL_PATTERN: `${API_ROOT}/agents/{agentId}/unenroll`, BULK_UNENROLL_PATTERN: `${API_ROOT}/agents/bulk_unenroll`, REASSIGN_PATTERN: `${API_ROOT}/agents/{agentId}/reassign`, From 6dbb314f76e6e1c84f33dd11960dfc3491471ed0 Mon Sep 17 00:00:00 2001 From: Tyler Smalley Date: Tue, 2 Nov 2021 11:02:05 -0700 Subject: [PATCH 15/41] [bazel] Set cache for build, not common (#117163) These settings are not valid outside build, and cause commands like shutdown to fail. Signed-off-by: Tyler Smalley --- .bazelrc.common | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.bazelrc.common b/.bazelrc.common index c401a90507982..0ad0c95fdcbbd 100644 --- a/.bazelrc.common +++ b/.bazelrc.common @@ -13,10 +13,10 @@ test --experimental_guard_against_concurrent_changes query --experimental_guard_against_concurrent_changes ## Cache action outputs on disk so they persist across output_base and bazel shutdown (eg. changing branches) -common --disk_cache=~/.bazel-cache/disk-cache +build --disk_cache=~/.bazel-cache/disk-cache ## Bazel repo cache settings -common --repository_cache=~/.bazel-cache/repository-cache +build --repository_cache=~/.bazel-cache/repository-cache # Bazel will create symlinks from the workspace directory to output artifacts. # Build results will be placed in a directory called "bazel-bin" From d9359219592dc5dc8b7e42f4061ed8f6c8409020 Mon Sep 17 00:00:00 2001 From: Vadim Yakhin Date: Tue, 2 Nov 2021 11:04:01 -0700 Subject: [PATCH 16/41] Update Workplace Search integration categories (#117036) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../custom_integrations/common/index.ts | 11 ++---- .../enterprise_search/server/integrations.ts | 34 +++++++++---------- 2 files changed, 20 insertions(+), 25 deletions(-) diff --git a/src/plugins/custom_integrations/common/index.ts b/src/plugins/custom_integrations/common/index.ts index 98148bb22c816..f00b4c39405d5 100755 --- a/src/plugins/custom_integrations/common/index.ts +++ b/src/plugins/custom_integrations/common/index.ts @@ -40,16 +40,11 @@ export const INTEGRATION_CATEGORY_DISPLAY = { web: 'Web', // Kibana added - communication: 'Communication', - customer_support: 'Customer Support', - document_storage: 'Document Storage', - enterprise_management: 'Enterprise Management', - knowledge_platform: 'Knowledge Platform', + communications: 'Communications', + file_storage: 'File storage', language_client: 'Language client', - project_management: 'Project Management', - software_development: 'Software Development', upload_file: 'Upload a file', - website_search: 'Website Search', + website_search: 'Website search', }; /** diff --git a/x-pack/plugins/enterprise_search/server/integrations.ts b/x-pack/plugins/enterprise_search/server/integrations.ts index eee5cdc3aaec3..633f5638cc05c 100644 --- a/x-pack/plugins/enterprise_search/server/integrations.ts +++ b/x-pack/plugins/enterprise_search/server/integrations.ts @@ -30,7 +30,7 @@ const workplaceSearchIntegrations: WorkplaceSearchIntegration[] = [ defaultMessage: 'Search over your files and folders stored on Box with Workplace Search.', } ), - categories: ['document_storage'], + categories: ['file_storage'], }, { id: 'confluence_cloud', @@ -47,7 +47,7 @@ const workplaceSearchIntegrations: WorkplaceSearchIntegration[] = [ 'Search over your organizational content on Confluence Cloud with Workplace Search.', } ), - categories: ['knowledge_platform'], + categories: ['productivity'], }, { id: 'confluence_server', @@ -64,7 +64,7 @@ const workplaceSearchIntegrations: WorkplaceSearchIntegration[] = [ 'Search over your organizational content on Confluence Server with Workplace Search.', } ), - categories: ['knowledge_platform'], + categories: ['productivity'], }, { id: 'dropbox', @@ -78,7 +78,7 @@ const workplaceSearchIntegrations: WorkplaceSearchIntegration[] = [ 'Search over your files and folders stored on Dropbox with Workplace Search.', } ), - categories: ['document_storage'], + categories: ['file_storage'], }, { id: 'github', @@ -91,7 +91,7 @@ const workplaceSearchIntegrations: WorkplaceSearchIntegration[] = [ defaultMessage: 'Search over your projects and repos on GitHub with Workplace Search.', } ), - categories: ['software_development'], + categories: ['productivity'], }, { id: 'github_enterprise_server', @@ -108,7 +108,7 @@ const workplaceSearchIntegrations: WorkplaceSearchIntegration[] = [ 'Search over your projects and repos on GitHub Enterprise Server with Workplace Search.', } ), - categories: ['software_development'], + categories: ['productivity'], }, { id: 'gmail', @@ -121,7 +121,7 @@ const workplaceSearchIntegrations: WorkplaceSearchIntegration[] = [ defaultMessage: 'Search over your emails managed by Gmail with Workplace Search.', } ), - categories: ['communication'], + categories: ['communications'], }, { id: 'google_drive', @@ -134,7 +134,7 @@ const workplaceSearchIntegrations: WorkplaceSearchIntegration[] = [ defaultMessage: 'Search over your documents on Google Drive with Workplace Search.', } ), - categories: ['document_storage'], + categories: ['file_storage'], }, { id: 'jira_cloud', @@ -147,7 +147,7 @@ const workplaceSearchIntegrations: WorkplaceSearchIntegration[] = [ defaultMessage: 'Search over your project workflow on Jira Cloud with Workplace Search.', } ), - categories: ['project_management'], + categories: ['productivity'], }, { id: 'jira_server', @@ -160,7 +160,7 @@ const workplaceSearchIntegrations: WorkplaceSearchIntegration[] = [ defaultMessage: 'Search over your project workflow on Jira Server with Workplace Search.', } ), - categories: ['project_management'], + categories: ['productivity'], }, { id: 'onedrive', @@ -173,7 +173,7 @@ const workplaceSearchIntegrations: WorkplaceSearchIntegration[] = [ defaultMessage: 'Search over your files stored on OneDrive with Workplace Search.', } ), - categories: ['document_storage'], + categories: ['file_storage'], uiInternalPath: '/app/enterprise_search/workplace_search/sources/add/one_drive', }, { @@ -187,7 +187,7 @@ const workplaceSearchIntegrations: WorkplaceSearchIntegration[] = [ defaultMessage: 'Search over your content on Salesforce with Workplace Search.', } ), - categories: ['crm'], + categories: ['productivity'], }, { id: 'salesforce_sandbox', @@ -203,7 +203,7 @@ const workplaceSearchIntegrations: WorkplaceSearchIntegration[] = [ defaultMessage: 'Search over your content on Salesforce Sandbox with Workplace Search.', } ), - categories: ['crm'], + categories: ['productivity'], }, { id: 'servicenow', @@ -216,7 +216,7 @@ const workplaceSearchIntegrations: WorkplaceSearchIntegration[] = [ defaultMessage: 'Search over your content on ServiceNow with Workplace Search.', } ), - categories: ['enterprise_management'], + categories: ['productivity'], }, { id: 'sharepoint_online', @@ -232,7 +232,7 @@ const workplaceSearchIntegrations: WorkplaceSearchIntegration[] = [ defaultMessage: 'Search over your files stored on SharePoint Online with Workplace Search.', } ), - categories: ['document_storage'], + categories: ['file_storage'], uiInternalPath: '/app/enterprise_search/workplace_search/sources/add/share_point', }, { @@ -246,7 +246,7 @@ const workplaceSearchIntegrations: WorkplaceSearchIntegration[] = [ defaultMessage: 'Search over your messages on Slack with Workplace Search.', } ), - categories: ['communication'], + categories: ['communications'], }, { id: 'zendesk', @@ -259,7 +259,7 @@ const workplaceSearchIntegrations: WorkplaceSearchIntegration[] = [ defaultMessage: 'Search over your tickets on Zendesk with Workplace Search.', } ), - categories: ['customer_support'], + categories: ['communications'], }, { id: 'custom_api_source', From 8d814f1c6c4e080096673f617a1cde811ea95fe6 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Tue, 2 Nov 2021 19:06:39 +0100 Subject: [PATCH 17/41] [ML] fix ticks, add custom colors inline (#117159) --- .../severity_control/severity_control.tsx | 13 ++++++------- .../components/severity_control/styles.scss | 18 ------------------ 2 files changed, 6 insertions(+), 25 deletions(-) delete mode 100644 x-pack/plugins/ml/public/application/components/severity_control/styles.scss diff --git a/x-pack/plugins/ml/public/application/components/severity_control/severity_control.tsx b/x-pack/plugins/ml/public/application/components/severity_control/severity_control.tsx index 7be72b8430233..4cc182988778d 100644 --- a/x-pack/plugins/ml/public/application/components/severity_control/severity_control.tsx +++ b/x-pack/plugins/ml/public/application/components/severity_control/severity_control.tsx @@ -16,7 +16,6 @@ import { EuiRangeProps, } from '@elastic/eui'; import { ANOMALY_THRESHOLD } from '../../../../common'; -import './styles.scss'; export interface SeveritySelectorProps { value: number | undefined; @@ -29,23 +28,23 @@ export const SeverityControl: FC = React.memo(({ value, o const levels: EuiRangeProps['levels'] = [ { min: ANOMALY_THRESHOLD.LOW, - max: ANOMALY_THRESHOLD.MINOR - 1, - color: 'success', + max: ANOMALY_THRESHOLD.MINOR, + color: '#8BC8FB', }, { min: ANOMALY_THRESHOLD.MINOR, - max: ANOMALY_THRESHOLD.MAJOR - 1, - color: 'primary', + max: ANOMALY_THRESHOLD.MAJOR, + color: '#FDEC25', }, { min: ANOMALY_THRESHOLD.MAJOR, max: ANOMALY_THRESHOLD.CRITICAL, - color: 'warning', + color: '#FBA740', }, { min: ANOMALY_THRESHOLD.CRITICAL, max: MAX_ANOMALY_SCORE, - color: 'danger', + color: '#FE5050', }, ]; diff --git a/x-pack/plugins/ml/public/application/components/severity_control/styles.scss b/x-pack/plugins/ml/public/application/components/severity_control/styles.scss deleted file mode 100644 index 9a5fa8f2b160a..0000000000000 --- a/x-pack/plugins/ml/public/application/components/severity_control/styles.scss +++ /dev/null @@ -1,18 +0,0 @@ -// Color overrides are required (https://github.com/elastic/eui/issues/4467) - -.mlSeverityControl { - .euiRangeLevel-- { - &success { - background-color: #8BC8FB; - } - &primary { - background-color: #FDEC25; - } - &warning { - background-color: #FBA740; - } - &danger { - background-color: #FE5050; - } - } -} From 3a6a94695d864a83d7f348398411830e11d447b0 Mon Sep 17 00:00:00 2001 From: Dominique Clarke Date: Tue, 2 Nov 2021 14:45:57 -0400 Subject: [PATCH 18/41] [Observability] [Exploratory View] adjust popover placement (#116471) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../exploratory_view/components/date_range_picker.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/date_range_picker.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/components/date_range_picker.tsx index 5529f28927028..32994b37fffe3 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/components/date_range_picker.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/components/date_range_picker.tsx @@ -79,8 +79,10 @@ export function DateRangePicker({ seriesId, series }: { seriesId: number; series return ( } endDateControl={ } /> From fd8a564392373a4d775287c726ed935a83eec31a Mon Sep 17 00:00:00 2001 From: Mark Hopkin Date: Tue, 2 Nov 2021 18:56:56 +0000 Subject: [PATCH 19/41] show upgrade title when no policy present (#117134) --- .../components/layout.tsx | 37 ++++++++++++++----- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/layout.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/layout.tsx index 3daf7fa545f24..b7c7d263d2675 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/layout.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/layout.tsx @@ -123,16 +123,33 @@ export const CreatePackagePolicyPageLayout: React.FunctionComponent<{ ); } - return isEdit ? ( - -

- -

-
- ) : ( + if (isEdit) { + return ( + +

+ +

+
+ ); + } + + if (isUpgrade) { + return ( + +

+ +

+
+ ); + } + + return (

Date: Tue, 2 Nov 2021 12:01:28 -0700 Subject: [PATCH 20/41] skip flaky suite (#116058) --- .../import_saved_objects_between_versions.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/functional/apps/saved_objects_management/import_saved_objects_between_versions.ts b/x-pack/test/functional/apps/saved_objects_management/import_saved_objects_between_versions.ts index 790909164b33d..4dce6bca8f67a 100644 --- a/x-pack/test/functional/apps/saved_objects_management/import_saved_objects_between_versions.ts +++ b/x-pack/test/functional/apps/saved_objects_management/import_saved_objects_between_versions.ts @@ -20,7 +20,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const retry = getService('retry'); - describe('Export import saved objects between versions', function () { + // Failing: See https://github.com/elastic/kibana/issues/116058 + describe.skip('Export import saved objects between versions', function () { before(async function () { await esArchiver.load('x-pack/test/functional/es_archives/logstash_functional'); await esArchiver.load('x-pack/test/functional/es_archives/getting_started/shakespeare'); From 5c73c0c120d43ddb9ef7c38545d480ab527f3b90 Mon Sep 17 00:00:00 2001 From: Scotty Bollinger Date: Tue, 2 Nov 2021 14:04:15 -0500 Subject: [PATCH 21/41] Update Role mappings modal copy (#117182) --- .../public/applications/shared/role_mapping/constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/constants.ts b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/constants.ts index d2229b428932f..0a99b0991f4ed 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/constants.ts @@ -382,7 +382,7 @@ export const INVITATION_PENDING_LABEL = i18n.translate( export const ROLE_MODAL_TEXT = i18n.translate('xpack.enterpriseSearch.roleMapping.roleModalText', { defaultMessage: - 'Removing a role mapping revokes access to any user corresponding to the mapping attributes, but may not take effect immediately for SAML-governed roles. Users with an active SAML session will retain access until it expires.', + 'Removing a role mapping could revoke access to the currently logged-in user. Before proceeding, verify that the currently logged-in user has the appropriate access level via a different role mapping to avoid undesired behavior. This action may not take effect immediately for SAML-governed roles. Users with an active SAML session will retain access until it expires.', }); export const USER_MODAL_TITLE = (username: string) => From f5e9a075e20be13d5a3b0d0e80019dcd339640ed Mon Sep 17 00:00:00 2001 From: Andrea Del Rio Date: Tue, 2 Nov 2021 12:33:40 -0700 Subject: [PATCH 22/41] [Design] Swap button styles in header and KQL search bar (#117062) --- .../data/public/ui/query_string_input/query_bar_top_row.tsx | 1 + .../top_nav_menu/__snapshots__/top_nav_menu_item.test.tsx.snap | 1 + .../navigation/public/top_nav_menu/top_nav_menu_item.tsx | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx b/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx index f71a3d3b0686a..90db5abe418b7 100644 --- a/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx +++ b/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx @@ -231,6 +231,7 @@ export default function QueryBarTopRow(props: QueryBarTopRowProps) { isDisabled={isDateRangeInvalid} isLoading={props.isLoading} onClick={onClickSubmitButton} + fill={false} data-test-subj="querySubmitButton" /> ); diff --git a/src/plugins/navigation/public/top_nav_menu/__snapshots__/top_nav_menu_item.test.tsx.snap b/src/plugins/navigation/public/top_nav_menu/__snapshots__/top_nav_menu_item.test.tsx.snap index 155377e5ea335..570699aa0c0e2 100644 --- a/src/plugins/navigation/public/top_nav_menu/__snapshots__/top_nav_menu_item.test.tsx.snap +++ b/src/plugins/navigation/public/top_nav_menu/__snapshots__/top_nav_menu_item.test.tsx.snap @@ -2,6 +2,7 @@ exports[`TopNavMenu Should render emphasized item which should be clickable 1`] = ` + {upperFirst(props.label || props.id!)} ) : ( From 2a74c291c8211eda18fdcb42e6a3c5c2fcb8e6be Mon Sep 17 00:00:00 2001 From: Spencer Date: Tue, 2 Nov 2021 14:02:43 -0600 Subject: [PATCH 23/41] [docs] fix direct branch injection into docs links (#116794) * [docs] fix direct branch injection into docs links * add one more todo * correct fallback branch Co-authored-by: spalger Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../server/deprecations/deprecations.test.ts | 17 +++++++++++++---- x-pack/plugins/apm/server/deprecations/index.ts | 4 +++- .../server/deprecations/reporting_role.ts | 7 +++++-- .../security/server/config_deprecations.ts | 13 ++++++++++--- .../server/deprecations/kibana_user_role.ts | 15 ++++++++++++--- .../server/lib/log_health_metrics.ts | 5 ++++- 6 files changed, 47 insertions(+), 14 deletions(-) diff --git a/x-pack/plugins/apm/server/deprecations/deprecations.test.ts b/x-pack/plugins/apm/server/deprecations/deprecations.test.ts index 43e8140fb9b3c..8ab632deec809 100644 --- a/x-pack/plugins/apm/server/deprecations/deprecations.test.ts +++ b/x-pack/plugins/apm/server/deprecations/deprecations.test.ts @@ -5,6 +5,8 @@ * 2.0. */ +import { kibanaPackageJson } from '@kbn/dev-utils'; + import { GetDeprecationsContext } from '../../../../../src/core/server'; import { CloudSetup } from '../../../cloud/server'; import { getDeprecations } from './'; @@ -19,7 +21,7 @@ const deprecationContext = { describe('getDeprecations', () => { describe('when fleet is disabled', () => { it('returns no deprecations', async () => { - const deprecationsCallback = getDeprecations({ branch: 'master' }); + const deprecationsCallback = getDeprecations({ branch: 'main' }); const deprecations = await deprecationsCallback(deprecationContext); expect(deprecations).toEqual([]); }); @@ -28,7 +30,7 @@ describe('getDeprecations', () => { describe('when running on cloud with legacy apm-server', () => { it('returns deprecations', async () => { const deprecationsCallback = getDeprecations({ - branch: 'master', + branch: 'main', cloudSetup: { isCloudEnabled: true } as unknown as CloudSetup, fleet: { start: () => ({ @@ -38,13 +40,20 @@ describe('getDeprecations', () => { }); const deprecations = await deprecationsCallback(deprecationContext); expect(deprecations).not.toEqual([]); + // TODO: remove when docs support "main" + if (kibanaPackageJson.branch === 'main') { + for (const { documentationUrl } of deprecations) { + expect(documentationUrl).toMatch(/\/master\//); + expect(documentationUrl).not.toMatch(/\/main\//); + } + } }); }); describe('when running on cloud with fleet', () => { it('returns no deprecations', async () => { const deprecationsCallback = getDeprecations({ - branch: 'master', + branch: 'main', cloudSetup: { isCloudEnabled: true } as unknown as CloudSetup, fleet: { start: () => ({ @@ -60,7 +69,7 @@ describe('getDeprecations', () => { describe('when running on prem', () => { it('returns no deprecations', async () => { const deprecationsCallback = getDeprecations({ - branch: 'master', + branch: 'main', cloudSetup: { isCloudEnabled: false } as unknown as CloudSetup, fleet: { start: () => ({ agentPolicyService: { get: () => undefined } }), diff --git a/x-pack/plugins/apm/server/deprecations/index.ts b/x-pack/plugins/apm/server/deprecations/index.ts index 76c90270abb8f..39e282e76d9a6 100644 --- a/x-pack/plugins/apm/server/deprecations/index.ts +++ b/x-pack/plugins/apm/server/deprecations/index.ts @@ -38,6 +38,8 @@ export function getDeprecations({ const isCloudEnabled = !!cloudSetup?.isCloudEnabled; const hasCloudAgentPolicy = !isEmpty(cloudAgentPolicy); + // TODO: remove when docs support "main" + const docBranch = branch === 'main' ? 'master' : branch; if (isCloudEnabled && !hasCloudAgentPolicy) { deprecations.push({ @@ -48,7 +50,7 @@ export function getDeprecations({ defaultMessage: 'Running the APM Server binary directly is considered a legacy option and is deprecated since 7.16. Switch to APM Server managed by an Elastic Agent instead. Read our documentation to learn more.', }), - documentationUrl: `https://www.elastic.co/guide/en/apm/server/${branch}/apm-integration.html`, + documentationUrl: `https://www.elastic.co/guide/en/apm/server/${docBranch}/apm-integration.html`, level: 'warning', correctiveActions: { manualSteps: [ diff --git a/x-pack/plugins/reporting/server/deprecations/reporting_role.ts b/x-pack/plugins/reporting/server/deprecations/reporting_role.ts index e4575f9875315..355a83c13a37e 100644 --- a/x-pack/plugins/reporting/server/deprecations/reporting_role.ts +++ b/x-pack/plugins/reporting/server/deprecations/reporting_role.ts @@ -19,8 +19,11 @@ import { ReportingCore } from '../'; import { deprecations } from '../lib/deprecations'; const REPORTING_USER_ROLE_NAME = 'reporting_user'; -const getDocumentationUrl = (branch: string) => - `https://www.elastic.co/guide/en/kibana/${branch}/kibana-privileges.html`; +const getDocumentationUrl = (branch: string) => { + // TODO: remove when docs support "main" + const docBranch = branch === 'main' ? 'master' : branch; + return `https://www.elastic.co/guide/en/kibana/${docBranch}/kibana-privileges.html`; +}; interface ExtraDependencies { reportingCore: ReportingCore; diff --git a/x-pack/plugins/security/server/config_deprecations.ts b/x-pack/plugins/security/server/config_deprecations.ts index 3a71dbb28add2..8b778950036b5 100644 --- a/x-pack/plugins/security/server/config_deprecations.ts +++ b/x-pack/plugins/security/server/config_deprecations.ts @@ -34,6 +34,8 @@ export const securityConfigDeprecationProvider: ConfigDeprecationProvider = ({ // Deprecation warning for the old array-based format of `xpack.security.authc.providers`. (settings, _fromPath, addDeprecation, { branch }) => { if (Array.isArray(settings?.xpack?.security?.authc?.providers)) { + // TODO: remove when docs support "main" + const docsBranch = branch === 'main' ? 'master' : 'main'; addDeprecation({ configPath: 'xpack.security.authc.providers', title: i18n.translate('xpack.security.deprecations.authcProvidersTitle', { @@ -43,7 +45,7 @@ export const securityConfigDeprecationProvider: ConfigDeprecationProvider = ({ defaultMessage: 'Use the new object format instead of an array of provider types.', }), level: 'warning', - documentationUrl: `https://www.elastic.co/guide/en/kibana/${branch}/security-settings-kb.html#authentication-security-settings`, + documentationUrl: `https://www.elastic.co/guide/en/kibana/${docsBranch}/security-settings-kb.html#authentication-security-settings`, correctiveActions: { manualSteps: [ i18n.translate('xpack.security.deprecations.authcProviders.manualSteps1', { @@ -59,6 +61,9 @@ export const securityConfigDeprecationProvider: ConfigDeprecationProvider = ({ } }, (settings, _fromPath, addDeprecation, { branch }) => { + // TODO: remove when docs support "main" + const docsBranch = branch === 'main' ? 'master' : 'main'; + const hasProviderType = (providerType: string) => { const providers = settings?.xpack?.security?.authc?.providers; if (Array.isArray(providers)) { @@ -86,7 +91,7 @@ export const securityConfigDeprecationProvider: ConfigDeprecationProvider = ({ values: { tokenProvider }, }), level: 'warning', - documentationUrl: `https://www.elastic.co/guide/en/kibana/${branch}/security-settings-kb.html#authentication-security-settings`, + documentationUrl: `https://www.elastic.co/guide/en/kibana/${docsBranch}/security-settings-kb.html#authentication-security-settings`, correctiveActions: { manualSteps: [ i18n.translate('xpack.security.deprecations.basicAndTokenProviders.manualSteps1', { @@ -100,6 +105,8 @@ export const securityConfigDeprecationProvider: ConfigDeprecationProvider = ({ } }, (settings, _fromPath, addDeprecation, { branch }) => { + // TODO: remove when docs support "main" + const docsBranch = branch === 'main' ? 'master' : 'main'; const samlProviders = (settings?.xpack?.security?.authc?.providers?.saml ?? {}) as Record< string, any @@ -119,7 +126,7 @@ export const securityConfigDeprecationProvider: ConfigDeprecationProvider = ({ defaultMessage: 'This setting is no longer used.', }), level: 'warning', - documentationUrl: `https://www.elastic.co/guide/en/kibana/${branch}/security-settings-kb.html#authentication-security-settings`, + documentationUrl: `https://www.elastic.co/guide/en/kibana/${docsBranch}/security-settings-kb.html#authentication-security-settings`, correctiveActions: { manualSteps: [ i18n.translate('xpack.security.deprecations.maxRedirectURLSize.manualSteps1', { diff --git a/x-pack/plugins/security/server/deprecations/kibana_user_role.ts b/x-pack/plugins/security/server/deprecations/kibana_user_role.ts index ba32446611a62..9746597aa95b8 100644 --- a/x-pack/plugins/security/server/deprecations/kibana_user_role.ts +++ b/x-pack/plugins/security/server/deprecations/kibana_user_role.ts @@ -98,13 +98,16 @@ async function getUsersDeprecations( return []; } + // TODO: remove when docs support "main" + const docsBranch = packageInfo.branch === 'main' ? 'master' : packageInfo.branch; + return [ { title: getDeprecationTitle(), message: getDeprecationMessage(), level: 'warning', deprecationType: 'feature', - documentationUrl: `https://www.elastic.co/guide/en/elasticsearch/reference/${packageInfo.branch}/built-in-roles.html`, + documentationUrl: `https://www.elastic.co/guide/en/elasticsearch/reference/${docsBranch}/built-in-roles.html`, correctiveActions: { api: { method: 'POST', @@ -159,13 +162,16 @@ async function getRoleMappingsDeprecations( return []; } + // TODO: remove when docs support "main" + const docsBranch = packageInfo.branch === 'main' ? 'master' : packageInfo.branch; + return [ { title: getDeprecationTitle(), message: getDeprecationMessage(), level: 'warning', deprecationType: 'feature', - documentationUrl: `https://www.elastic.co/guide/en/elasticsearch/reference/${packageInfo.branch}/built-in-roles.html`, + documentationUrl: `https://www.elastic.co/guide/en/elasticsearch/reference/${docsBranch}/built-in-roles.html`, correctiveActions: { api: { method: 'POST', @@ -193,6 +199,9 @@ async function getRoleMappingsDeprecations( function deprecationError(packageInfo: PackageInfo, error: Error): DeprecationsDetails[] { const title = getDeprecationTitle(); + // TODO: remove when docs support "main" + const docsBranch = packageInfo.branch === 'main' ? 'master' : packageInfo.branch; + if (getErrorStatusCode(error) === 403) { return [ { @@ -202,7 +211,7 @@ function deprecationError(packageInfo: PackageInfo, error: Error): DeprecationsD message: i18n.translate('xpack.security.deprecations.kibanaUser.forbiddenErrorMessage', { defaultMessage: 'You do not have enough permissions to fix this deprecation.', }), - documentationUrl: `https://www.elastic.co/guide/en/kibana/${packageInfo.branch}/xpack-security.html#_required_permissions_7`, + documentationUrl: `https://www.elastic.co/guide/en/kibana/${docsBranch}/xpack-security.html#_required_permissions_7`, correctiveActions: { manualSteps: [ i18n.translate( diff --git a/x-pack/plugins/task_manager/server/lib/log_health_metrics.ts b/x-pack/plugins/task_manager/server/lib/log_health_metrics.ts index d541ffb5684da..5d513c645a862 100644 --- a/x-pack/plugins/task_manager/server/lib/log_health_metrics.ts +++ b/x-pack/plugins/task_manager/server/lib/log_health_metrics.ts @@ -46,7 +46,10 @@ export function logHealthMetrics( } const message = `Latest Monitored Stats: ${JSON.stringify(monitoredHealth)}`; - const docLink = `https://www.elastic.co/guide/en/kibana/${kibanaPackageJson.branch}/task-manager-health-monitoring.html`; + // TODO: remove when docs support "main" + const docsBranch = kibanaPackageJson.branch === 'main' ? 'master' : 'main'; + + const docLink = `https://www.elastic.co/guide/en/kibana/${docsBranch}/task-manager-health-monitoring.html`; const detectedProblemMessage = `Task Manager detected a degradation in performance. This is usually temporary, and Kibana can recover automatically. If the problem persists, check the docs for troubleshooting information: ${docLink} .`; if (enabled) { const driftInSeconds = (monitoredHealth.stats.runtime?.value.drift.p99 ?? 0) / 1000; From 8143901f5975309e0c1e0a45f592f918541becf2 Mon Sep 17 00:00:00 2001 From: liza-mae Date: Tue, 2 Nov 2021 14:29:27 -0600 Subject: [PATCH 24/41] Fix reporting api tests for cloud (#116515) * Fix reporting api tests for cloud * Add default value Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/test/reporting_api_integration/services/scenarios.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/x-pack/test/reporting_api_integration/services/scenarios.ts b/x-pack/test/reporting_api_integration/services/scenarios.ts index e39a3e2e5954b..807ea7457ad45 100644 --- a/x-pack/test/reporting_api_integration/services/scenarios.ts +++ b/x-pack/test/reporting_api_integration/services/scenarios.ts @@ -132,7 +132,11 @@ export function createScenarios({ getService }: Pick { + const generateCsv = async ( + job: JobParamsCSV, + username = 'elastic', + password = process.env.TEST_KIBANA_PASS || 'changeme' + ) => { const jobParams = rison.encode(job as object as RisonValue); return await supertestWithoutAuth .post(`/api/reporting/generate/csv_searchsource`) From 2f8dfba4cdc0d6855406d5bb8543e51ca72a3929 Mon Sep 17 00:00:00 2001 From: Tim Sullivan Date: Tue, 2 Nov 2021 13:53:09 -0700 Subject: [PATCH 25/41] [Reporting] Log TM health and drift stats prior to reporting tests (#117013) * [Reporting] Log TM health and drift stats prior to reporting tests Closes https://github.com/elastic/kibana/issues/114946 * re-skip flaky test * rename method --- .../reporting_and_security/index.ts | 1 + .../reporting_without_security/index.ts | 6 +++++- .../services/scenarios.ts | 15 +++++++++++++++ .../reporting_and_deprecated_security/index.ts | 2 ++ .../reporting_and_security/index.ts | 1 + .../reporting_without_security/index.ts | 6 ++++++ 6 files changed, 30 insertions(+), 1 deletion(-) diff --git a/x-pack/test/reporting_api_integration/reporting_and_security/index.ts b/x-pack/test/reporting_api_integration/reporting_and_security/index.ts index f6654ff5a6b1d..6ea6de3482501 100644 --- a/x-pack/test/reporting_api_integration/reporting_and_security/index.ts +++ b/x-pack/test/reporting_api_integration/reporting_and_security/index.ts @@ -14,6 +14,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { before(async () => { const reportingAPI = getService('reportingAPI'); + await reportingAPI.logTaskManagerHealth(); await reportingAPI.createDataAnalystRole(); await reportingAPI.createTestReportingUserRole(); await reportingAPI.createDataAnalyst(); diff --git a/x-pack/test/reporting_api_integration/reporting_without_security/index.ts b/x-pack/test/reporting_api_integration/reporting_without_security/index.ts index 81ca3e05e4dd0..258ae814f5789 100644 --- a/x-pack/test/reporting_api_integration/reporting_without_security/index.ts +++ b/x-pack/test/reporting_api_integration/reporting_without_security/index.ts @@ -8,8 +8,12 @@ import { FtrProviderContext } from '../ftr_provider_context'; // eslint-disable-next-line import/no-default-export -export default function ({ loadTestFile }: FtrProviderContext) { +export default function ({ loadTestFile, getService }: FtrProviderContext) { describe('Reporting API Integration Tests with Security disabled', function () { + before(async () => { + const reportingAPI = getService('reportingAPI'); + await reportingAPI.logTaskManagerHealth(); + }); this.tags('ciGroup13'); loadTestFile(require.resolve('./job_apis_csv')); loadTestFile(require.resolve('./job_apis_csv_deprecated')); diff --git a/x-pack/test/reporting_api_integration/services/scenarios.ts b/x-pack/test/reporting_api_integration/services/scenarios.ts index 807ea7457ad45..a596b61ea00d1 100644 --- a/x-pack/test/reporting_api_integration/services/scenarios.ts +++ b/x-pack/test/reporting_api_integration/services/scenarios.ts @@ -36,6 +36,20 @@ export function createScenarios({ getService }: Pick { + // Check task manager health for analyzing test failures. See https://github.com/elastic/kibana/issues/114946 + const tmHealth = await supertest.get(`/api/task_manager/_health`); + const driftValues = tmHealth.body?.stats?.runtime?.value; + + log.info(`Task Manager status: "${tmHealth.body?.status}"`); + log.info(`Task Manager overall drift rankings: "${JSON.stringify(driftValues?.drift)}"`); + log.info( + `Task Manager drift rankings for "report:execute": "${JSON.stringify( + driftValues?.drift_by_type?.['report:execute'] + )}"` + ); + }; + const initEcommerce = async () => { await esArchiver.load('x-pack/test/functional/es_archives/reporting/ecommerce'); await kibanaServer.importExport.load(ecommerceSOPath); @@ -205,6 +219,7 @@ export function createScenarios({ getService }: Pick { + const reportingAPI = context.getService('reportingAPI'); + await reportingAPI.logTaskManagerHealth(); await createDataAnalystRole(); await createDataAnalyst(); await createReportingUser(); diff --git a/x-pack/test/reporting_functional/reporting_and_security/index.ts b/x-pack/test/reporting_functional/reporting_and_security/index.ts index be0e76a28bd0b..22057c9be77dc 100644 --- a/x-pack/test/reporting_functional/reporting_and_security/index.ts +++ b/x-pack/test/reporting_functional/reporting_and_security/index.ts @@ -14,6 +14,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { before(async () => { const reportingFunctional = getService('reportingFunctional'); + await reportingFunctional.logTaskManagerHealth(); await reportingFunctional.createDataAnalystRole(); await reportingFunctional.createDataAnalyst(); await reportingFunctional.createTestReportingUserRole(); diff --git a/x-pack/test/reporting_functional/reporting_without_security/index.ts b/x-pack/test/reporting_functional/reporting_without_security/index.ts index d1801b7e3e2e6..fecc0e97daac0 100644 --- a/x-pack/test/reporting_functional/reporting_without_security/index.ts +++ b/x-pack/test/reporting_functional/reporting_without_security/index.ts @@ -11,6 +11,12 @@ import { FtrProviderContext } from '../ftr_provider_context'; export default function ({ loadTestFile, getService }: FtrProviderContext) { describe('Reporting Functional Tests with Security disabled', function () { this.tags('ciGroup2'); + + before(async () => { + const reportingAPI = getService('reportingAPI'); + await reportingAPI.logTaskManagerHealth(); + }); + loadTestFile(require.resolve('./management')); }); } From 2928c5f0ac5523ca161b4c80781864641bb48447 Mon Sep 17 00:00:00 2001 From: Dominique Clarke Date: Tue, 2 Nov 2021 16:54:29 -0400 Subject: [PATCH 26/41] [Exploratory View] Adjust labels for percentile calculation and document loaded metric (#116704) * adjust labels * fix types Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../configurations/constants/constants.ts | 22 +++++++++++++++++-- .../synthetics/kpi_over_time_config.ts | 4 ++-- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/constants.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/constants.ts index c12e67bc9b1ae..aac5ac7136d7a 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/constants.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/constants.ts @@ -21,6 +21,7 @@ import { BROWSER_VERSION_LABEL, CLS_LABEL, CORE_WEB_VITALS_LABEL, + DCL_LABEL, DEVICE_DISTRIBUTION_LABEL, DEVICE_LABEL, ENVIRONMENT_LABEL, @@ -50,8 +51,18 @@ import { PAGE_LOAD_TIME_LABEL, LABELS_FIELD, STEP_NAME_LABEL, + STEP_DURATION_LABEL, } from './labels'; -import { SYNTHETICS_STEP_NAME } from './field_names/synthetics'; +import { + MONITOR_DURATION_US, + SYNTHETICS_CLS, + SYNTHETICS_DCL, + SYNTHETICS_DOCUMENT_ONLOAD, + SYNTHETICS_FCP, + SYNTHETICS_LCP, + SYNTHETICS_STEP_DURATION, + SYNTHETICS_STEP_NAME, +} from './field_names/synthetics'; export const DEFAULT_TIME = { from: 'now-1h', to: 'now' }; @@ -73,12 +84,19 @@ export const FieldLabels: Record = { [TBT_FIELD]: TBT_LABEL, [FID_FIELD]: FID_LABEL, [CLS_FIELD]: CLS_LABEL, + + [SYNTHETICS_CLS]: CLS_LABEL, + [SYNTHETICS_DCL]: DCL_LABEL, + [SYNTHETICS_STEP_DURATION]: STEP_DURATION_LABEL, + [SYNTHETICS_LCP]: LCP_LABEL, + [SYNTHETICS_FCP]: FCP_LABEL, + [SYNTHETICS_DOCUMENT_ONLOAD]: PAGE_LOAD_TIME_LABEL, [TRANSACTION_TIME_TO_FIRST_BYTE]: BACKEND_TIME_LABEL, [TRANSACTION_DURATION]: PAGE_LOAD_TIME_LABEL, 'monitor.id': MONITOR_ID_LABEL, 'monitor.status': MONITOR_STATUS_LABEL, - 'monitor.duration.us': MONITORS_DURATION_LABEL, + [MONITOR_DURATION_US]: MONITORS_DURATION_LABEL, [SYNTHETICS_STEP_NAME]: STEP_NAME_LABEL, 'agent.hostname': AGENT_HOST_LABEL, diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/kpi_over_time_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/kpi_over_time_config.ts index e548ec2714e14..63bd7e0cf3e81 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/kpi_over_time_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/kpi_over_time_config.ts @@ -16,13 +16,13 @@ import { import { CLS_LABEL, DCL_LABEL, - DOCUMENT_ONLOAD_LABEL, DOWN_LABEL, FCP_LABEL, LCP_LABEL, MONITORS_DURATION_LABEL, STEP_DURATION_LABEL, UP_LABEL, + PAGE_LOAD_TIME_LABEL, } from '../constants/labels'; import { MONITOR_DURATION_US, @@ -128,7 +128,7 @@ export function getSyntheticsKPIConfig({ indexPattern }: ConfigProps): SeriesCon columnFilters: getStepMetricColumnFilter(SYNTHETICS_DCL), }, { - label: DOCUMENT_ONLOAD_LABEL, + label: PAGE_LOAD_TIME_LABEL, field: SYNTHETICS_DOCUMENT_ONLOAD, id: SYNTHETICS_DOCUMENT_ONLOAD, columnType: OPERATION_COLUMN, From 9acedc8406b934d61f1c9be234ef6de4780d843a Mon Sep 17 00:00:00 2001 From: Spencer Date: Tue, 2 Nov 2021 15:19:42 -0600 Subject: [PATCH 27/41] [watcher] fix invalid import (#116907) Co-authored-by: spalger Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../{calc_es_interval.js => calc_es_interval.ts} | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) rename x-pack/plugins/watcher/public/legacy/{calc_es_interval.js => calc_es_interval.ts} (83%) diff --git a/x-pack/plugins/watcher/public/legacy/calc_es_interval.js b/x-pack/plugins/watcher/public/legacy/calc_es_interval.ts similarity index 83% rename from x-pack/plugins/watcher/public/legacy/calc_es_interval.js rename to x-pack/plugins/watcher/public/legacy/calc_es_interval.ts index 29f0f0f56d38d..cae88b797ea4f 100644 --- a/x-pack/plugins/watcher/public/legacy/calc_es_interval.js +++ b/x-pack/plugins/watcher/public/legacy/calc_es_interval.ts @@ -7,7 +7,7 @@ import dateMath from '@elastic/datemath'; -import { parseEsInterval } from './index'; +import { parseEsInterval } from './parse_es_interval'; const unitsDesc = dateMath.unitsDesc; const largeMax = unitsDesc.indexOf('M'); @@ -17,10 +17,9 @@ const largeMax = unitsDesc.indexOf('M'); * compatible expression, and provide * associated metadata * - * @param {moment.duration} duration - * @return {object} + * @param duration */ -export function convertDurationToNormalizedEsInterval(duration) { +export function convertDurationToNormalizedEsInterval(duration: moment.Duration) { for (let i = 0; i < unitsDesc.length; i++) { const unit = unitsDesc[i]; const val = duration.as(unit); @@ -35,7 +34,7 @@ export function convertDurationToNormalizedEsInterval(duration) { return { value: val, - unit: unit, + unit, expression: val + unit, }; } @@ -49,7 +48,7 @@ export function convertDurationToNormalizedEsInterval(duration) { }; } -export function convertIntervalToEsInterval(interval) { +export function convertIntervalToEsInterval(interval: string) { const { value, unit } = parseEsInterval(interval); return { value, From 488f112f47dbf492c7e252d459a4a202d6924098 Mon Sep 17 00:00:00 2001 From: Tim Sullivan Date: Tue, 2 Nov 2021 18:27:52 -0700 Subject: [PATCH 28/41] [Reporting/Tests] Consolidate test archives, move kbn objects to kbn_archiver (#116528) * remove unused * remove kibana objects from reporting es_archives * import objects using kibanaServer for tests * consolidate ecommerce_kibana_spaces * self-review * fix nanos test * fix loading of reporting/ecommerce_kibana_spaces * fix csv snapshots * fix more csv tests * archive rename * consolidate canvas_disallowed_url archive * clean up snapshots * fix CSV tests * polish * remove unused * Update x-pack/test/reporting_api_integration/reporting_and_security/network_policy.ts --- x-pack/test/accessibility/apps/reporting.ts | 9 +- .../apps/dashboard/reporting/download_csv.ts | 3 + .../canvas_disallowed_url/data.json.gz | Bin 883 -> 0 bytes .../canvas_disallowed_url/mappings.json | 2185 -------------- .../ecommerce_kibana_spaces/data.json | 83 - .../reporting/hugedata/data.json.gz | Bin 33885 -> 31900 bytes .../reporting/hugedata/mappings.json | 2523 ----------------- .../es_archives/reporting/logs/data.json.gz | Bin 1375 -> 0 bytes .../es_archives/reporting/logs/mappings.json | 263 -- .../reporting/multi_index/data.json.gz | Bin 619 -> 0 bytes .../reporting/multi_index/mappings.json | 92 - .../reporting/multi_index_kibana/data.json.gz | Bin 455 -> 0 bytes .../multi_index_kibana/mappings.json | 2027 ------------- .../es_archives/reporting/nanos/data.json | 25 + .../es_archives/reporting/nanos/data.json.gz | Bin 863 -> 0 bytes .../es_archives/reporting/nanos/mappings.json | 1028 ------- .../es_archives/reporting/sales/data.json.gz | Bin 1762 -> 1004 bytes .../es_archives/reporting/sales/mappings.json | 264 -- .../kbn_archiver/reporting/ecommerce.json | 51 + .../fixtures/kbn_archiver/reporting/logs.json | 441 +++ .../__snapshots__/download_csv_dashboard.snap | 126 +- .../download_csv_dashboard.ts | 303 +- .../generate_csv_discover_deprecated.ts | 7 +- .../ilm_migration_apis.ts | 7 +- .../reporting_and_security/network_policy.ts | 6 +- .../reporting_and_security/spaces.ts | 13 +- .../job_apis_csv.ts | 4 +- .../job_apis_csv_deprecated.ts | 7 +- .../services/scenarios.ts | 13 + 29 files changed, 766 insertions(+), 8714 deletions(-) delete mode 100644 x-pack/test/functional/es_archives/reporting/canvas_disallowed_url/data.json.gz delete mode 100644 x-pack/test/functional/es_archives/reporting/canvas_disallowed_url/mappings.json delete mode 100644 x-pack/test/functional/es_archives/reporting/hugedata/mappings.json delete mode 100644 x-pack/test/functional/es_archives/reporting/logs/data.json.gz delete mode 100644 x-pack/test/functional/es_archives/reporting/logs/mappings.json delete mode 100644 x-pack/test/functional/es_archives/reporting/multi_index/data.json.gz delete mode 100644 x-pack/test/functional/es_archives/reporting/multi_index/mappings.json delete mode 100644 x-pack/test/functional/es_archives/reporting/multi_index_kibana/data.json.gz delete mode 100644 x-pack/test/functional/es_archives/reporting/multi_index_kibana/mappings.json create mode 100644 x-pack/test/functional/es_archives/reporting/nanos/data.json delete mode 100644 x-pack/test/functional/es_archives/reporting/nanos/data.json.gz create mode 100644 x-pack/test/functional/fixtures/kbn_archiver/reporting/logs.json diff --git a/x-pack/test/accessibility/apps/reporting.ts b/x-pack/test/accessibility/apps/reporting.ts index bccb650fa08ca..91356ef85972b 100644 --- a/x-pack/test/accessibility/apps/reporting.ts +++ b/x-pack/test/accessibility/apps/reporting.ts @@ -16,7 +16,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const supertestWithoutAuth = getService('supertestWithoutAuth'); const reporting = getService('reporting'); - const esArchiver = getService('esArchiver'); const security = getService('security'); describe('Reporting', () => { @@ -33,17 +32,13 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }; before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/reporting/logs'); - await esArchiver.load('x-pack/test/functional/es_archives/logstash_functional'); - + await reporting.initLogs(); await createReportingUser(); await reporting.loginReportingUser(); }); after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/reporting/logs'); - await esArchiver.unload('x-pack/test/functional/es_archives/logstash_functional'); - + await reporting.teardownLogs(); await deleteReportingUser(); }); diff --git a/x-pack/test/functional/apps/dashboard/reporting/download_csv.ts b/x-pack/test/functional/apps/dashboard/reporting/download_csv.ts index 79ddaea13dfa5..4ee61811e5f85 100644 --- a/x-pack/test/functional/apps/dashboard/reporting/download_csv.ts +++ b/x-pack/test/functional/apps/dashboard/reporting/download_csv.ts @@ -18,6 +18,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const log = getService('log'); const testSubjects = getService('testSubjects'); const kibanaServer = getService('kibanaServer'); + const reportingAPI = getService('reporting'); const filterBar = getService('filterBar'); const find = getService('find'); const retry = getService('retry'); @@ -124,9 +125,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('Field Formatters and Scripted Fields', () => { before(async () => { + await reportingAPI.initLogs(); await esArchiver.load('x-pack/test/functional/es_archives/reporting/hugedata'); }); after(async () => { + await reportingAPI.teardownLogs(); await esArchiver.unload('x-pack/test/functional/es_archives/reporting/hugedata'); }); diff --git a/x-pack/test/functional/es_archives/reporting/canvas_disallowed_url/data.json.gz b/x-pack/test/functional/es_archives/reporting/canvas_disallowed_url/data.json.gz deleted file mode 100644 index c434eee5dd8d35749fce7d082a802c159e70af14..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 883 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˾C_ zVojy7lQE09P4q(~{`MlsUtD`DAfvVhX`MzyIa$j^wsN~&eSnB$PK8tep>mzP>lak8QUNlL{=N=dEG0G{)(-vKFavbu{D26iSCafci*wp&6< zsD1`dVhxige*kTyTIBqcux6-@|HCa*5k`$Hi=hiJhPR)9F~n|Yxn0XfYl-R7t8Y!; z#J?J0;I+t2%pZWQfS0K_y;3lCK+7GBCR)pg7l@?`6g$=`4=1d~pT3Y&C?zwAzm3E) z@a1*(GX7@5&*56rdFg$eh*HUAqozTf%uR9BF#$Gne|`kohgO$3ClGr<TIigI*%LSAilcEcSS(OPs<&O3WkJ9!NYiZ1^0$0(P*tfKA;3%QsK43Xp zGd5UNGJ1~03_e%Nitwfe);Z0Y9$wd9bf4^jxE^sY~m0Hz=Bq3@a!c)5LJ)rWrl?|0}t1JR-L@gBMDJKo^SyrWvY11WD~~e8mP+NSNBbU* z@Gk5ImpgOW*N)ei`J--d`Ge6EW{VVSW}c98BaGUCupx5PSG;ET{Vul7N@cDUix4m3 zU+R$L2(b28^lKe?BhipLS|tvpoi$w^@zoSZEZ7Q(L?W~%ONh5lrAla>(s;kjzIrSc ze^@=zW!?&VAmw!`^XT{*dahd){iVa3y|aw|VvFCl7IT190UIQRV`*-6PIKnVFC8Ag zoQza#xQkY6K*+SA@i$N;|8CP&RfmnvY0z|m%j;VvT&2prT9SpZT$C_`GOhvN2QDXV zu;2nnN%TbIY-S9Fw98kjROvYcz6)H=fA66QS1C2MRFHq749c4!78R~@K8ao6a()!e zU|i;0*k7rBiCljlFw6{Arcz7Eh_}UItV531{*~&dKek@2usm#ym!7{NoBc9&wc@^y zRg5K3E3zS0m2UHYcc4dnAUhz|;S*^f#zhbiuzQi{-YHM|`UuiF#dRmdI;4I3e*t!l zdgz+?Fa9gQ?!137&H#rJT8@nYzyyfe1yZfk)jje5fNO!zTqk?TNatO zrW3?cW$rDe6qBY(Iie`q+-tgLf0>VqOjMcVOX*k(C{NcgY#DLBOBp~xgp_kj#H^mb zh@=$SLx#RL#x;Fm>|(8parpxnN&_hxOVN}RhfU{RR;U<{G`1G{6fQ8Jdj!`a?}{5S zU7%HOce@%^8Pmm-Qp2X`bgG95&f%pCH0lq)k|#wdO@5U5q+77YHEz0ee+K<%nX?f8 zJAA58hjDqU3tY~;bpThcasaUutk~28h{Mh~^g+wXwT_&NGCLL*J0%7{P!iXWL7Tr~ zaXN`O>N+!j;DSfmHK8z>S6*d@2+LzXz&eDZ1=vdEbRWPvM5GDW64#*qipc4&8V3#M zDg(PHE1juIWZnj(y2f(Oe^ALUE4(!Ra`c6i5eX6CPpv>BrD>ynBhQ(m?}L~(Bo7c< zV*zi+>(__hr^CfIn8i?&tzp@)rreamQ6;SB>>Vud0A#-Z5F+!422wW05S2&lq^0|l zD#s3Bd6T^fRz+fnsDzxEg(+vXhKvtVFRZ*_Xkt~GT)@CuIn4*Hf03#>)>%=wz*o_J zC|l%a8+C)r?OX?NEpsetGj)F5csXG8z;OtfpjKuP3Wd<3@uaeGg=g4Ey)!^M4;cJxpHN5DUzf#0vUbup#w2 zIdg3$nB9nF?s&7Qe{bLU&oVa>e^_CC91GWqm8k?_VxF2lHv4>1DE5KO8;(bit)a;k z1hM(|qIMe$v=%uRh27wCk%>dN);Kc!1Fgx_Yq#M(T8M;M#LTD$*;l_ElTc+N&R|}h z;BuyW8?IFbmNvL1D-iSVWJF;pGQBHa#c94ag`tj`?CJrOfAfQA00k&ppGA~(BQr1U zaDm3SPT{AQr`hzBEwDa>Ys_>}C#ZbUXG68dv!X=b)0cl=CQnxGHdvH_Noc}W3X~ke zZBo}5!zmH?#PH0H!OaFEVWn1T44JDP$Qg^0utr_u>;#vOU>(4<$b^!h_4}vGWUSbN z`$)wYWHxD=e{m+fb*nK-g478pUvf8rI^MzruaSk+FH&0Gq5aB^H?9!Bx)ph@rFDyO?0c;R=FWVZ*9J6#Dw3uLm)> z##_F84qrb_{usV~G9z%6Vdfq{IWxNj)FMO7TljtIe;`Qd48KpNB4upAg$-~C9SM>$ zrZr@u4X;>DXABl$V0<8ma2exNGsauyo87tF_TSpsmw&q7ho8Q5?TCsjU+hcS7PTAFn<)ae&;pO$PBfOUA67E^d->LT| zFr!m18lO2)4eTx<;q5CdjS?{Hf|NH5Eu>%@e?f{uTT3iT45G&LPx>@=5KVG>l@WkB zMqr!>D%X)gi9djWAH~^HTnAvKfF$z7S!81?73H~XV97mOj_Uxd$YnOjpiSHa>2I+r zL#DR&D4gGT{$y0!5E_)+FyzE_5SKu=87mCQ?$`?~ZxmXsi6R$sL!~AfyCo{;wp8hn ze{-Vn^V7rZ*~!fYlVYv#02I5S=4zJmVat%21amAfO-&4Jn>a>wh5vj)7EoUR{nS7xcKY&ZckWlGsMw(2HX*Y@~S_3eW1% zYxH>!lwOxDSBGGEWi|!VHvE|SIg((@M4JK3L<{&b0gKc_l7_wf*=#_E;>gEP%n~3V zC{I!%K+xuCZuE~8HNr(;E-FcmsK@~*kOP%}<2+VCjqB)YX|wWQD8BmFEGjKOQyX?- z8+~)5PUcx_;26=>0hZ>d zlTEx&!!m_?H{aHIdp^VCXrW`Cu%sH}kyX8I*bs^22{_8MVZHu{7*Pafwq>JW0r3 znSP~}NKaz>Djl3*>aOE%fn#+c2DrYX-WdCoCSjCntywzx+dGZKM^nH>m#q>a3wZ&= zBska5>IqkOd#EeX=xslS%+TJrogihebh@^X)Ykj=>;rVO{*P%sj=3f%aZy(`NWBM9 zTQ~EAb1AYy!wB^;@~x9IjQ^S&k`{4vQqGXF;f8mMcRcv#&z2|@OsO6K2-U#Xcp))n z+6Jwu$o^g5bMi)ML)8~c^xE!$s7jD!Dh5+r(@gl8n-8e_)&GpUYVW&z+2EdU_tzdn z!EW#!OZynM=YmKR);YJ~LD~?^HN?qi1y%|n5y>;Oq8F7iH zb~=j9IfKAWLlDcvn`tRvz?`|HnGl;3y4qN(%+n%dZZdNN5~FX!kB%?hm)#=~SJ=?b|Tv2+w}%2PIK8i!NJRPq_i*v*-^x;vgktN-I|(jh8o29dTkzyV8U)$={>z>~%sd%W9}PgO-&Gw=owWeS#joaui*t_oVP-;BvBw zmF{t~;dT%(?^_tEvLSH&%y_DMg;40_?%n-9Txhj^&DN}vFh&ZYfamLDH}$lE641Lw zAbzq`O`NEPqWNw{)b^QSMt}9}1-pGp?u!A#pB=p}*+OGN`ku;oy%h4HAIKN2?}RSU z_II?vCQ)gC)w?gBO2Xv6Ea$GRIj!wL0QLpd=~cA)g~7?=o8l=NQo5&9zvPaIR}X@z zArC?&pA%jmU#xOiN{BAI56y%MR-5Mk=IkUZWgWfo!Z6=LH@_%YLcf!&A!=)26WHZp z3g!*>^*BGld|4sJ`e%XnU3@wzyS*`I{(?Hmtp}SFP&r28k4m7*g4MdPSJ+RBmF=lp zkQi(7L-euY@GBYue~d)Su^z)OSKq~RO3J2vqFoRV+Q1>!=9|()_8dWZ=VR6Vp@oYz zxgp>7G3ph}`)znyK5fclb0%0e&BBlG4eNghbzgGx0-GbaY+TPibG_}xLZghNm^d_>oeDMJtq3kXP9w-l4o0{MD->FbV$AxENQd5Al2FJItbDS3G0`oVY<|mF5ov6?6BtpuSoEGM8#l(f;MP)BemBN~ZHC}Ok8#IsgQNNEiO z%ytUXm%1%#nwQ%!73)R*3iGy0#Ut43=IYaQ?`$A*cp%VNGNrDF5WEn3l<>YePQ@7d z*=%o-w5rykM6#IB>lKQJ+5T-)TX|VIMR0K1HL`O;fa-b#R`A{JGZuNC{lXKY4DT$kSTgb>xz#TBnn+^dkT=pbw?eqt6S#Qa0zL`Faxrr` zi}|Wplnf)m*kxS^?~EiL_5roa^d^r99S{oV5Pfc~ZSO&OgA)#0s9$L{X@d1gNQ(;O z=evlRaZotuWS$5hk~&4oPA&cpSPJe2qOUfq>~7XN34c^9N;yT&<{72^pf8gmyS<%H zt(rW-_d|E&?`*T)8TLqbpUTzbVcq%4yGmhgW;+4l6|2-(YR~iW&5**Pur6+M~ElTM+7#lMwg+qH!gf1&~?&QYfrL zN{e%q-##n#_HvH0iRQ3;F8DQo_RBsJk(9(0)HG%>LgcKMa`MV$>?VA=U>y&>yNDLU zT`C*gx1Nn<821L8bD>1*$t|MQru&T5MkiqUvTqj4hRbYnGev*TS%L_80^GUOdci_4 z<8>K{M?V;T`|8nI-u=Hefcs6i=50G`NFx;)|K7rzU9Tkg@6MZ}na*g*7*FGCk=L+& zPjk9iyc%uSFEzU{B3)RJ9a58RGCR;E4ww*i6(7Je4W0jpXTXiX<^hf@JQn2IDZS2l z=ah3DK5c?vWgCfsz_-D0K`O+Y$K#?=DiYEAQTl;fFtQ zBs{;f39REPNfnjZ#N_UVa~h4U=O2Ssu=)!kmuFQrmXNud{oxlH}Cs*m9fLNqH22< zQjuOTW)(ia4!T`vK%6bhds$fCf_f!rpNh?!&Co>eoG7Khvw-{AjX?pCZM;xE09EJF z(&w&FtFrqrQpsa^#UpD}X%UmdL0hNP^XJ_batH(n0o z#(VvcRSq;}#c=kR59bfyJ=))pdne&5+ket&lK>>V>dpIBYN6#^x9Ymg}s>}mSh()mGFqe!Y53FT5@)GwR(L!W7XK>N_i<0 zmISUIbYfl)9@A;*nW*#*9&1V#-34TJk7aNv7O+lZh7vZTrkW?geuV!^S;QrBIj1^)FeaH)#)xwfGu4NiL-tzuo;qzIuKb*cAUJGo~3dqf*u zi0RyTzF@z6BZtqN!1{VnDxaypz3_o2vYss{`q98GOl360*8MCzN34JSLlwbly6oXzFN3lKXHK+z(KJrZ;&Hb z7Fli=N7ihBb8FgSY1xyk7yqQte!Q-D;bJ*xIln9o#F#nY@cs1QXj{OT4K= z^?0eqe8(!8bofI>?ef&>p=`2QLSyfe6o;vZVy|JmDC_cC{`aL2LhPjnBf?8ARVchZ zXq&*`q|*1|Z7l5b%E!CWhQMWfo3+Qoe}^G{4A$*dPn9W@rvHKYH41(V4Zxgb)`TV7 z@5uBGwfLAzQQ6+?;u?{Q@V2D+WN23Fl(;ySYc~(Y&LyH4;xauQ*N}G{ zlK?kio>!HohboPp(9Nf~!dW#uL~Lb^wLSLP#kx1SqKW~=@=f?@Hzn5LP@yK=GHE;e zviGjW)NRUPa5*-1C*;7H5>OM7O@6FeEVhB%PXfjQP54O@5LXf_o$@d8F&2;$#;b;1 zHiE`jM_!YO3zinJ{nCC>ggyf6($^$W3(5RniqA3m=IzyKzdK@oLN^-mli>b z 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 diff --git a/x-pack/test/functional/es_archives/reporting/hugedata/mappings.json b/x-pack/test/functional/es_archives/reporting/hugedata/mappings.json deleted file mode 100644 index d1cb75c1f5150..0000000000000 --- a/x-pack/test/functional/es_archives/reporting/hugedata/mappings.json +++ /dev/null @@ -1,2523 +0,0 @@ -{ - "type": "index", - "value": { - "aliases": { - ".kibana": { - } - }, - "index": ".kibana_1", - "mappings": { - "dynamic": "strict", - "properties": { - "action": { - "properties": { - "actionTypeId": { - "type": "keyword" - }, - "config": { - "enabled": false, - "type": "object" - }, - "name": { - "fields": { - "keyword": { - "type": "keyword" - } - }, - "type": "text" - }, - "secrets": { - "type": "binary" - } - } - }, - "action_task_params": { - "properties": { - "actionId": { - "type": "keyword" - }, - "apiKey": { - "type": "binary" - }, - "params": { - "enabled": false, - "type": "object" - } - } - }, - "alert": { - "properties": { - "actions": { - "properties": { - "actionRef": { - "type": "keyword" - }, - "actionTypeId": { - "type": "keyword" - }, - "group": { - "type": "keyword" - }, - "params": { - "enabled": false, - "type": "object" - } - }, - "type": "nested" - }, - "alertTypeId": { - "type": "keyword" - }, - "apiKey": { - "type": "binary" - }, - "apiKeyOwner": { - "type": "keyword" - }, - "consumer": { - "type": "keyword" - }, - "createdAt": { - "type": "date" - }, - "createdBy": { - "type": "keyword" - }, - "enabled": { - "type": "boolean" - }, - "executionStatus": { - "properties": { - "error": { - "properties": { - "message": { - "type": "keyword" - }, - "reason": { - "type": "keyword" - } - } - }, - "lastExecutionDate": { - "type": "date" - }, - "status": { - "type": "keyword" - } - } - }, - "meta": { - "properties": { - "versionApiKeyLastmodified": { - "type": "keyword" - } - } - }, - "muteAll": { - "type": "boolean" - }, - "mutedInstanceIds": { - "type": "keyword" - }, - "name": { - "fields": { - "keyword": { - "type": "keyword" - } - }, - "type": "text" - }, - "notifyWhen": { - "type": "keyword" - }, - "params": { - "enabled": false, - "type": "object" - }, - "schedule": { - "properties": { - "interval": { - "type": "keyword" - } - } - }, - "scheduledTaskId": { - "type": "keyword" - }, - "tags": { - "type": "keyword" - }, - "throttle": { - "type": "keyword" - }, - "updatedAt": { - "type": "date" - }, - "updatedBy": { - "type": "keyword" - } - } - }, - "api_key_pending_invalidation": { - "properties": { - "apiKeyId": { - "type": "keyword" - }, - "createdAt": { - "type": "date" - } - } - }, - "apm-indices": { - "properties": { - "error": { - "type": "keyword" - }, - "metric": { - "type": "keyword" - }, - "onboarding": { - "type": "keyword" - }, - "sourcemap": { - "type": "keyword" - }, - "span": { - "type": "keyword" - }, - "transaction": { - "type": "keyword" - } - } - }, - "apm-telemetry": { - "dynamic": "false", - "type": "object" - }, - "app_search_telemetry": { - "dynamic": "false", - "type": "object" - }, - "application_usage_daily": { - "dynamic": "false", - "properties": { - "timestamp": { - "type": "date" - } - } - }, - "application_usage_totals": { - "dynamic": "false", - "type": "object" - }, - "application_usage_transactional": { - "dynamic": "false", - "type": "object" - }, - "canvas-element": { - "dynamic": "false", - "properties": { - "@created": { - "type": "date" - }, - "@timestamp": { - "type": "date" - }, - "content": { - "type": "text" - }, - "help": { - "type": "text" - }, - "image": { - "type": "text" - }, - "name": { - "fields": { - "keyword": { - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "canvas-workpad": { - "dynamic": "false", - "properties": { - "@created": { - "type": "date" - }, - "@timestamp": { - "type": "date" - }, - "name": { - "fields": { - "keyword": { - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "canvas-workpad-template": { - "dynamic": "false", - "properties": { - "help": { - "fields": { - "keyword": { - "type": "keyword" - } - }, - "type": "text" - }, - "name": { - "fields": { - "keyword": { - "type": "keyword" - } - }, - "type": "text" - }, - "tags": { - "fields": { - "keyword": { - "type": "keyword" - } - }, - "type": "text" - }, - "template_key": { - "type": "keyword" - } - } - }, - "cases": { - "properties": { - "closed_at": { - "type": "date" - }, - "closed_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - }, - "connector": { - "properties": { - "fields": { - "properties": { - "key": { - "type": "text" - }, - "value": { - "type": "text" - } - } - }, - "id": { - "type": "keyword" - }, - "name": { - "type": "text" - }, - "type": { - "type": "keyword" - } - } - }, - "created_at": { - "type": "date" - }, - "created_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - }, - "description": { - "type": "text" - }, - "external_service": { - "properties": { - "connector_id": { - "type": "keyword" - }, - "connector_name": { - "type": "keyword" - }, - "external_id": { - "type": "keyword" - }, - "external_title": { - "type": "text" - }, - "external_url": { - "type": "text" - }, - "pushed_at": { - "type": "date" - }, - "pushed_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - } - } - }, - "settings": { - "properties": { - "syncAlerts": { - "type": "boolean" - } - } - }, - "status": { - "type": "keyword" - }, - "tags": { - "type": "keyword" - }, - "title": { - "type": "keyword" - }, - "updated_at": { - "type": "date" - }, - "updated_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - } - } - }, - "cases-comments": { - "properties": { - "alertId": { - "type": "keyword" - }, - "comment": { - "type": "text" - }, - "created_at": { - "type": "date" - }, - "created_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - }, - "index": { - "type": "keyword" - }, - "pushed_at": { - "type": "date" - }, - "pushed_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - }, - "type": { - "type": "keyword" - }, - "updated_at": { - "type": "date" - }, - "updated_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - } - } - }, - "cases-configure": { - "properties": { - "closure_type": { - "type": "keyword" - }, - "connector": { - "properties": { - "fields": { - "properties": { - "key": { - "type": "text" - }, - "value": { - "type": "text" - } - } - }, - "id": { - "type": "keyword" - }, - "name": { - "type": "text" - }, - "type": { - "type": "keyword" - } - } - }, - "created_at": { - "type": "date" - }, - "created_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - }, - "updated_at": { - "type": "date" - }, - "updated_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - } - } - }, - "cases-connector-mappings": { - "properties": { - "mappings": { - "properties": { - "action_type": { - "type": "keyword" - }, - "source": { - "type": "keyword" - }, - "target": { - "type": "keyword" - } - } - } - } - }, - "cases-user-actions": { - "properties": { - "action": { - "type": "keyword" - }, - "action_at": { - "type": "date" - }, - "action_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - }, - "action_field": { - "type": "keyword" - }, - "new_value": { - "type": "text" - }, - "old_value": { - "type": "text" - } - } - }, - "config": { - "dynamic": "false", - "properties": { - "buildNum": { - "type": "keyword" - } - } - }, - "core-usage-stats": { - "dynamic": "false", - "type": "object" - }, - "dashboard": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "doc_values": false, - "index": false, - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "index": false, - "type": "text" - } - } - }, - "optionsJSON": { - "index": false, - "type": "text" - }, - "panelsJSON": { - "index": false, - "type": "text" - }, - "refreshInterval": { - "properties": { - "display": { - "doc_values": false, - "index": false, - "type": "keyword" - }, - "pause": { - "doc_values": false, - "index": false, - "type": "boolean" - }, - "section": { - "doc_values": false, - "index": false, - "type": "integer" - }, - "value": { - "doc_values": false, - "index": false, - "type": "integer" - } - } - }, - "timeFrom": { - "doc_values": false, - "index": false, - "type": "keyword" - }, - "timeRestore": { - "doc_values": false, - "index": false, - "type": "boolean" - }, - "timeTo": { - "doc_values": false, - "index": false, - "type": "keyword" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "endpoint:user-artifact": { - "properties": { - "body": { - "type": "binary" - }, - "compressionAlgorithm": { - "index": false, - "type": "keyword" - }, - "created": { - "index": false, - "type": "date" - }, - "decodedSha256": { - "index": false, - "type": "keyword" - }, - "decodedSize": { - "index": false, - "type": "long" - }, - "encodedSha256": { - "type": "keyword" - }, - "encodedSize": { - "index": false, - "type": "long" - }, - "encryptionAlgorithm": { - "index": false, - "type": "keyword" - }, - "identifier": { - "type": "keyword" - } - } - }, - "endpoint:user-artifact-manifest": { - "properties": { - "created": { - "index": false, - "type": "date" - }, - "ids": { - "index": false, - "type": "keyword" - }, - "schemaVersion": { - "type": "keyword" - }, - "semanticVersion": { - "index": false, - "type": "keyword" - } - } - }, - "enterprise_search_telemetry": { - "dynamic": "false", - "type": "object" - }, - "epm-packages": { - "properties": { - "es_index_patterns": { - "enabled": false, - "type": "object" - }, - "install_source": { - "type": "keyword" - }, - "install_started_at": { - "type": "date" - }, - "install_status": { - "type": "keyword" - }, - "install_version": { - "type": "keyword" - }, - "installed_es": { - "properties": { - "id": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - }, - "type": "nested" - }, - "installed_kibana": { - "properties": { - "id": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - }, - "type": "nested" - }, - "internal": { - "type": "boolean" - }, - "name": { - "type": "keyword" - }, - "package_assets": { - "properties": { - "id": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - }, - "type": "nested" - }, - "removable": { - "type": "boolean" - }, - "version": { - "type": "keyword" - } - } - }, - "epm-packages-assets": { - "properties": { - "asset_path": { - "type": "keyword" - }, - "data_base64": { - "type": "binary" - }, - "data_utf8": { - "index": false, - "type": "text" - }, - "install_source": { - "type": "keyword" - }, - "media_type": { - "type": "keyword" - }, - "package_name": { - "type": "keyword" - }, - "package_version": { - "type": "keyword" - } - } - }, - "exception-list": { - "properties": { - "_tags": { - "type": "keyword" - }, - "comments": { - "properties": { - "comment": { - "type": "keyword" - }, - "created_at": { - "type": "keyword" - }, - "created_by": { - "type": "keyword" - }, - "id": { - "type": "keyword" - }, - "updated_at": { - "type": "keyword" - }, - "updated_by": { - "type": "keyword" - } - } - }, - "created_at": { - "type": "keyword" - }, - "created_by": { - "type": "keyword" - }, - "description": { - "type": "keyword" - }, - "entries": { - "properties": { - "entries": { - "properties": { - "field": { - "type": "keyword" - }, - "operator": { - "type": "keyword" - }, - "type": { - "type": "keyword" - }, - "value": { - "fields": { - "text": { - "type": "text" - } - }, - "type": "keyword" - } - } - }, - "field": { - "type": "keyword" - }, - "list": { - "properties": { - "id": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - } - }, - "operator": { - "type": "keyword" - }, - "type": { - "type": "keyword" - }, - "value": { - "fields": { - "text": { - "type": "text" - } - }, - "type": "keyword" - } - } - }, - "immutable": { - "type": "boolean" - }, - "item_id": { - "type": "keyword" - }, - "list_id": { - "type": "keyword" - }, - "list_type": { - "type": "keyword" - }, - "meta": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "os_types": { - "type": "keyword" - }, - "tags": { - "type": "keyword" - }, - "tie_breaker_id": { - "type": "keyword" - }, - "type": { - "type": "keyword" - }, - "updated_by": { - "type": "keyword" - }, - "version": { - "type": "keyword" - } - } - }, - "exception-list-agnostic": { - "properties": { - "_tags": { - "type": "keyword" - }, - "comments": { - "properties": { - "comment": { - "type": "keyword" - }, - "created_at": { - "type": "keyword" - }, - "created_by": { - "type": "keyword" - }, - "id": { - "type": "keyword" - }, - "updated_at": { - "type": "keyword" - }, - "updated_by": { - "type": "keyword" - } - } - }, - "created_at": { - "type": "keyword" - }, - "created_by": { - "type": "keyword" - }, - "description": { - "type": "keyword" - }, - "entries": { - "properties": { - "entries": { - "properties": { - "field": { - "type": "keyword" - }, - "operator": { - "type": "keyword" - }, - "type": { - "type": "keyword" - }, - "value": { - "fields": { - "text": { - "type": "text" - } - }, - "type": "keyword" - } - } - }, - "field": { - "type": "keyword" - }, - "list": { - "properties": { - "id": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - } - }, - "operator": { - "type": "keyword" - }, - "type": { - "type": "keyword" - }, - "value": { - "fields": { - "text": { - "type": "text" - } - }, - "type": "keyword" - } - } - }, - "immutable": { - "type": "boolean" - }, - "item_id": { - "type": "keyword" - }, - "list_id": { - "type": "keyword" - }, - "list_type": { - "type": "keyword" - }, - "meta": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "os_types": { - "type": "keyword" - }, - "tags": { - "type": "keyword" - }, - "tie_breaker_id": { - "type": "keyword" - }, - "type": { - "type": "keyword" - }, - "updated_by": { - "type": "keyword" - }, - "version": { - "type": "keyword" - } - } - }, - "file-upload-telemetry": { - "properties": { - "filesUploadedTotalCount": { - "type": "long" - } - } - }, - "file-upload-usage-collection-telemetry": { - "properties": { - "file_upload": { - "properties": { - "index_creation_count": { - "type": "long" - } - } - } - } - }, - "fleet-agent-actions": { - "properties": { - "ack_data": { - "type": "text" - }, - "agent_id": { - "type": "keyword" - }, - "created_at": { - "type": "date" - }, - "data": { - "type": "binary" - }, - "policy_id": { - "type": "keyword" - }, - "policy_revision": { - "type": "integer" - }, - "sent_at": { - "type": "date" - }, - "type": { - "type": "keyword" - } - } - }, - "fleet-agent-events": { - "properties": { - "action_id": { - "type": "keyword" - }, - "agent_id": { - "type": "keyword" - }, - "data": { - "type": "text" - }, - "message": { - "type": "text" - }, - "payload": { - "type": "text" - }, - "policy_id": { - "type": "keyword" - }, - "stream_id": { - "type": "keyword" - }, - "subtype": { - "type": "keyword" - }, - "timestamp": { - "type": "date" - }, - "type": { - "type": "keyword" - } - } - }, - "fleet-agents": { - "properties": { - "access_api_key_id": { - "type": "keyword" - }, - "active": { - "type": "boolean" - }, - "current_error_events": { - "index": false, - "type": "text" - }, - "default_api_key": { - "type": "binary" - }, - "default_api_key_id": { - "type": "keyword" - }, - "enrolled_at": { - "type": "date" - }, - "last_checkin": { - "type": "date" - }, - "last_checkin_status": { - "type": "keyword" - }, - "last_updated": { - "type": "date" - }, - "local_metadata": { - "type": "flattened" - }, - "packages": { - "type": "keyword" - }, - "policy_id": { - "type": "keyword" - }, - "policy_revision": { - "type": "integer" - }, - "type": { - "type": "keyword" - }, - "unenrolled_at": { - "type": "date" - }, - "unenrollment_started_at": { - "type": "date" - }, - "updated_at": { - "type": "date" - }, - "upgrade_started_at": { - "type": "date" - }, - "upgraded_at": { - "type": "date" - }, - "user_provided_metadata": { - "type": "flattened" - }, - "version": { - "type": "keyword" - } - } - }, - "fleet-enrollment-api-keys": { - "properties": { - "active": { - "type": "boolean" - }, - "api_key": { - "type": "binary" - }, - "api_key_id": { - "type": "keyword" - }, - "created_at": { - "type": "date" - }, - "expire_at": { - "type": "date" - }, - "name": { - "type": "keyword" - }, - "policy_id": { - "type": "keyword" - }, - "type": { - "type": "keyword" - }, - "updated_at": { - "type": "date" - } - } - }, - "graph-workspace": { - "properties": { - "description": { - "type": "text" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "legacyIndexPatternRef": { - "index": false, - "type": "text" - }, - "numLinks": { - "type": "integer" - }, - "numVertices": { - "type": "integer" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - }, - "wsState": { - "type": "text" - } - } - }, - "index-pattern": { - "dynamic": "false", - "properties": { - "title": { - "type": "text" - }, - "type": { - "type": "keyword" - } - } - }, - "infrastructure-ui-source": { - "dynamic": "false", - "type": "object" - }, - "ingest-agent-policies": { - "properties": { - "description": { - "type": "text" - }, - "is_default": { - "type": "boolean" - }, - "is_managed": { - "type": "boolean" - }, - "monitoring_enabled": { - "index": false, - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "namespace": { - "type": "keyword" - }, - "package_policies": { - "type": "keyword" - }, - "revision": { - "type": "integer" - }, - "status": { - "type": "keyword" - }, - "updated_at": { - "type": "date" - }, - "updated_by": { - "type": "keyword" - } - } - }, - "ingest-outputs": { - "properties": { - "ca_sha256": { - "index": false, - "type": "keyword" - }, - "config": { - "type": "flattened" - }, - "config_yaml": { - "type": "text" - }, - "fleet_enroll_password": { - "type": "binary" - }, - "fleet_enroll_username": { - "type": "binary" - }, - "hosts": { - "type": "keyword" - }, - "is_default": { - "type": "boolean" - }, - "name": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - } - }, - "ingest-package-policies": { - "properties": { - "created_at": { - "type": "date" - }, - "created_by": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "enabled": { - "type": "boolean" - }, - "inputs": { - "enabled": false, - "properties": { - "compiled_input": { - "type": "flattened" - }, - "config": { - "type": "flattened" - }, - "enabled": { - "type": "boolean" - }, - "streams": { - "properties": { - "compiled_stream": { - "type": "flattened" - }, - "config": { - "type": "flattened" - }, - "data_stream": { - "properties": { - "dataset": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - } - }, - "enabled": { - "type": "boolean" - }, - "id": { - "type": "keyword" - }, - "vars": { - "type": "flattened" - } - }, - "type": "nested" - }, - "type": { - "type": "keyword" - }, - "vars": { - "type": "flattened" - } - }, - "type": "nested" - }, - "name": { - "type": "keyword" - }, - "namespace": { - "type": "keyword" - }, - "output_id": { - "type": "keyword" - }, - "package": { - "properties": { - "name": { - "type": "keyword" - }, - "title": { - "type": "keyword" - }, - "version": { - "type": "keyword" - } - } - }, - "policy_id": { - "type": "keyword" - }, - "revision": { - "type": "integer" - }, - "updated_at": { - "type": "date" - }, - "updated_by": { - "type": "keyword" - } - } - }, - "ingest_manager_settings": { - "properties": { - "agent_auto_upgrade": { - "type": "keyword" - }, - "has_seen_add_data_notice": { - "index": false, - "type": "boolean" - }, - "kibana_ca_sha256": { - "type": "keyword" - }, - "kibana_urls": { - "type": "keyword" - }, - "package_auto_upgrade": { - "type": "keyword" - } - } - }, - "inventory-view": { - "dynamic": "false", - "type": "object" - }, - "kql-telemetry": { - "properties": { - "optInCount": { - "type": "long" - }, - "optOutCount": { - "type": "long" - } - } - }, - "legacy-url-alias": { - "dynamic": "false", - "type": "object" - }, - "lens": { - "properties": { - "description": { - "type": "text" - }, - "expression": { - "doc_values": false, - "index": false, - "type": "keyword" - }, - "state": { - "type": "flattened" - }, - "title": { - "type": "text" - }, - "visualizationType": { - "type": "keyword" - } - } - }, - "lens-ui-telemetry": { - "properties": { - "count": { - "type": "integer" - }, - "date": { - "type": "date" - }, - "name": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - } - }, - "map": { - "properties": { - "bounds": { - "dynamic": "false", - "type": "object" - }, - "description": { - "type": "text" - }, - "layerListJSON": { - "type": "text" - }, - "mapStateJSON": { - "type": "text" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "maps-telemetry": { - "enabled": false, - "type": "object" - }, - "metrics-explorer-view": { - "dynamic": "false", - "type": "object" - }, - "ml-job": { - "properties": { - "datafeed_id": { - "fields": { - "keyword": { - "type": "keyword" - } - }, - "type": "text" - }, - "job_id": { - "fields": { - "keyword": { - "type": "keyword" - } - }, - "type": "text" - }, - "type": { - "type": "keyword" - } - } - }, - "ml-telemetry": { - "dynamic": "false", - "type": "object" - }, - "monitoring-telemetry": { - "properties": { - "reportedClusterUuids": { - "type": "keyword" - } - } - }, - "namespace": { - "type": "keyword" - }, - "namespaces": { - "type": "keyword" - }, - "originId": { - "type": "keyword" - }, - "query": { - "properties": { - "description": { - "type": "text" - }, - "filters": { - "enabled": false, - "type": "object" - }, - "query": { - "properties": { - "language": { - "type": "keyword" - }, - "query": { - "index": false, - "type": "keyword" - } - } - }, - "timefilter": { - "enabled": false, - "type": "object" - }, - "title": { - "type": "text" - } - } - }, - "references": { - "properties": { - "id": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - }, - "type": "nested" - }, - "sample-data-telemetry": { - "properties": { - "installCount": { - "type": "long" - }, - "unInstallCount": { - "type": "long" - } - } - }, - "search": { - "properties": { - "columns": { - "doc_values": false, - "index": false, - "type": "keyword" - }, - "description": { - "type": "text" - }, - "grid": { - "enabled": false, - "type": "object" - }, - "hits": { - "doc_values": false, - "index": false, - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "index": false, - "type": "text" - } - } - }, - "pre712": { - "type": "boolean" - }, - "sort": { - "doc_values": false, - "index": false, - "type": "keyword" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "search-session": { - "properties": { - "appId": { - "type": "keyword" - }, - "created": { - "type": "date" - }, - "expires": { - "type": "date" - }, - "idMapping": { - "enabled": false, - "type": "object" - }, - "initialState": { - "enabled": false, - "type": "object" - }, - "name": { - "type": "keyword" - }, - "persisted": { - "type": "boolean" - }, - "restoreState": { - "enabled": false, - "type": "object" - }, - "sessionId": { - "type": "keyword" - }, - "status": { - "type": "keyword" - }, - "touched": { - "type": "date" - }, - "urlGeneratorId": { - "type": "keyword" - } - } - }, - "search-telemetry": { - "dynamic": "false", - "type": "object" - }, - "security-solution-signals-migration": { - "properties": { - "created": { - "index": false, - "type": "date" - }, - "createdBy": { - "index": false, - "type": "text" - }, - "destinationIndex": { - "index": false, - "type": "keyword" - }, - "error": { - "index": false, - "type": "text" - }, - "sourceIndex": { - "type": "keyword" - }, - "status": { - "index": false, - "type": "keyword" - }, - "taskId": { - "index": false, - "type": "keyword" - }, - "updated": { - "index": false, - "type": "date" - }, - "updatedBy": { - "index": false, - "type": "text" - }, - "version": { - "type": "long" - } - } - }, - "server": { - "dynamic": "false", - "type": "object" - }, - "siem-detection-engine-rule-actions": { - "properties": { - "actions": { - "properties": { - "action_type_id": { - "type": "keyword" - }, - "group": { - "type": "keyword" - }, - "id": { - "type": "keyword" - }, - "params": { - "enabled": false, - "type": "object" - } - } - }, - "alertThrottle": { - "type": "keyword" - }, - "ruleAlertId": { - "type": "keyword" - }, - "ruleThrottle": { - "type": "keyword" - } - } - }, - "siem-detection-engine-rule-status": { - "properties": { - "alertId": { - "type": "keyword" - }, - "bulkCreateTimeDurations": { - "type": "float" - }, - "gap": { - "type": "text" - }, - "lastFailureAt": { - "type": "date" - }, - "lastFailureMessage": { - "type": "text" - }, - "lastLookBackDate": { - "type": "date" - }, - "lastSuccessAt": { - "type": "date" - }, - "lastSuccessMessage": { - "type": "text" - }, - "searchAfterTimeDurations": { - "type": "float" - }, - "status": { - "type": "keyword" - }, - "statusDate": { - "type": "date" - } - } - }, - "siem-ui-timeline": { - "properties": { - "columns": { - "properties": { - "aggregatable": { - "type": "boolean" - }, - "category": { - "type": "keyword" - }, - "columnHeaderType": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "example": { - "type": "text" - }, - "id": { - "type": "keyword" - }, - "indexes": { - "type": "keyword" - }, - "name": { - "type": "text" - }, - "placeholder": { - "type": "text" - }, - "searchable": { - "type": "boolean" - }, - "type": { - "type": "keyword" - } - } - }, - "created": { - "type": "date" - }, - "createdBy": { - "type": "text" - }, - "dataProviders": { - "properties": { - "and": { - "properties": { - "enabled": { - "type": "boolean" - }, - "excluded": { - "type": "boolean" - }, - "id": { - "type": "keyword" - }, - "kqlQuery": { - "type": "text" - }, - "name": { - "type": "text" - }, - "queryMatch": { - "properties": { - "displayField": { - "type": "text" - }, - "displayValue": { - "type": "text" - }, - "field": { - "type": "text" - }, - "operator": { - "type": "text" - }, - "value": { - "type": "text" - } - } - }, - "type": { - "type": "text" - } - } - }, - "enabled": { - "type": "boolean" - }, - "excluded": { - "type": "boolean" - }, - "id": { - "type": "keyword" - }, - "kqlQuery": { - "type": "text" - }, - "name": { - "type": "text" - }, - "queryMatch": { - "properties": { - "displayField": { - "type": "text" - }, - "displayValue": { - "type": "text" - }, - "field": { - "type": "text" - }, - "operator": { - "type": "text" - }, - "value": { - "type": "text" - } - } - }, - "type": { - "type": "text" - } - } - }, - "dateRange": { - "properties": { - "end": { - "type": "date" - }, - "start": { - "type": "date" - } - } - }, - "description": { - "type": "text" - }, - "eventType": { - "type": "keyword" - }, - "excludedRowRendererIds": { - "type": "text" - }, - "favorite": { - "properties": { - "favoriteDate": { - "type": "date" - }, - "fullName": { - "type": "text" - }, - "keySearch": { - "type": "text" - }, - "userName": { - "type": "text" - } - } - }, - "filters": { - "properties": { - "exists": { - "type": "text" - }, - "match_all": { - "type": "text" - }, - "meta": { - "properties": { - "alias": { - "type": "text" - }, - "controlledBy": { - "type": "text" - }, - "disabled": { - "type": "boolean" - }, - "field": { - "type": "text" - }, - "formattedValue": { - "type": "text" - }, - "index": { - "type": "keyword" - }, - "key": { - "type": "keyword" - }, - "negate": { - "type": "boolean" - }, - "params": { - "type": "text" - }, - "type": { - "type": "keyword" - }, - "value": { - "type": "text" - } - } - }, - "missing": { - "type": "text" - }, - "query": { - "type": "text" - }, - "range": { - "type": "text" - }, - "script": { - "type": "text" - } - } - }, - "indexNames": { - "type": "text" - }, - "kqlMode": { - "type": "keyword" - }, - "kqlQuery": { - "properties": { - "filterQuery": { - "properties": { - "kuery": { - "properties": { - "expression": { - "type": "text" - }, - "kind": { - "type": "keyword" - } - } - }, - "serializedQuery": { - "type": "text" - } - } - } - } - }, - "savedQueryId": { - "type": "keyword" - }, - "sort": { - "dynamic": "false", - "properties": { - "columnId": { - "type": "keyword" - }, - "columnType": { - "type": "keyword" - }, - "sortDirection": { - "type": "keyword" - } - } - }, - "status": { - "type": "keyword" - }, - "templateTimelineId": { - "type": "text" - }, - "templateTimelineVersion": { - "type": "integer" - }, - "timelineType": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "updated": { - "type": "date" - }, - "updatedBy": { - "type": "text" - } - } - }, - "siem-ui-timeline-note": { - "properties": { - "created": { - "type": "date" - }, - "createdBy": { - "type": "text" - }, - "eventId": { - "type": "keyword" - }, - "note": { - "type": "text" - }, - "timelineId": { - "type": "keyword" - }, - "updated": { - "type": "date" - }, - "updatedBy": { - "type": "text" - } - } - }, - "siem-ui-timeline-pinned-event": { - "properties": { - "created": { - "type": "date" - }, - "createdBy": { - "type": "text" - }, - "eventId": { - "type": "keyword" - }, - "timelineId": { - "type": "keyword" - }, - "updated": { - "type": "date" - }, - "updatedBy": { - "type": "text" - } - } - }, - "space": { - "properties": { - "_reserved": { - "type": "boolean" - }, - "color": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "disabledFeatures": { - "type": "keyword" - }, - "imageUrl": { - "index": false, - "type": "text" - }, - "initials": { - "type": "keyword" - }, - "name": { - "fields": { - "keyword": { - "ignore_above": 2048, - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "spaces-usage-stats": { - "dynamic": "false", - "type": "object" - }, - "tag": { - "properties": { - "color": { - "type": "text" - }, - "description": { - "type": "text" - }, - "name": { - "type": "text" - } - } - }, - "telemetry": { - "properties": { - "allowChangingOptInStatus": { - "type": "boolean" - }, - "enabled": { - "type": "boolean" - }, - "lastReported": { - "type": "date" - }, - "lastVersionChecked": { - "type": "keyword" - }, - "reportFailureCount": { - "type": "integer" - }, - "reportFailureVersion": { - "type": "keyword" - }, - "sendUsageFrom": { - "type": "keyword" - }, - "userHasSeenNotice": { - "type": "boolean" - } - } - }, - "type": { - "type": "keyword" - }, - "ui-counter": { - "properties": { - "count": { - "type": "integer" - } - } - }, - "ui-metric": { - "properties": { - "count": { - "type": "integer" - } - } - }, - "updated_at": { - "type": "date" - }, - "upgrade-assistant-reindex-operation": { - "properties": { - "errorMessage": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - }, - "indexName": { - "type": "keyword" - }, - "lastCompletedStep": { - "type": "long" - }, - "locked": { - "type": "date" - }, - "newIndexName": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - }, - "reindexOptions": { - "properties": { - "openAndClose": { - "type": "boolean" - }, - "queueSettings": { - "properties": { - "queuedAt": { - "type": "long" - }, - "startedAt": { - "type": "long" - } - } - } - } - }, - "reindexTaskId": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - }, - "reindexTaskPercComplete": { - "type": "float" - }, - "runningReindexCount": { - "type": "integer" - }, - "status": { - "type": "integer" - } - } - }, - "upgrade-assistant-telemetry": { - "properties": { - "features": { - "properties": { - "deprecation_logging": { - "properties": { - "enabled": { - "null_value": true, - "type": "boolean" - } - } - } - } - }, - "ui_open": { - "properties": { - "cluster": { - "null_value": 0, - "type": "long" - }, - "indices": { - "null_value": 0, - "type": "long" - }, - "overview": { - "null_value": 0, - "type": "long" - } - } - }, - "ui_reindex": { - "properties": { - "close": { - "null_value": 0, - "type": "long" - }, - "open": { - "null_value": 0, - "type": "long" - }, - "start": { - "null_value": 0, - "type": "long" - }, - "stop": { - "null_value": 0, - "type": "long" - } - } - } - } - }, - "uptime-dynamic-settings": { - "dynamic": "false", - "type": "object" - }, - "url": { - "properties": { - "accessCount": { - "type": "long" - }, - "accessDate": { - "type": "date" - }, - "createDate": { - "type": "date" - }, - "url": { - "fields": { - "keyword": { - "ignore_above": 2048, - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "user-action": { - "dynamic": "false", - "type": "object" - }, - "visualization": { - "properties": { - "description": { - "type": "text" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "index": false, - "type": "text" - } - } - }, - "savedSearchRefName": { - "doc_values": false, - "index": false, - "type": "keyword" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "index": false, - "type": "text" - }, - "version": { - "type": "integer" - }, - "visState": { - "index": false, - "type": "text" - } - } - }, - "workplace_search_telemetry": { - "dynamic": "false", - "type": "object" - } - } - }, - "settings": { - "index": { - "auto_expand_replicas": "0-1", - "number_of_replicas": "0", - "number_of_shards": "1" - } - } - } -} - diff --git a/x-pack/test/functional/es_archives/reporting/logs/data.json.gz b/x-pack/test/functional/es_archives/reporting/logs/data.json.gz deleted file mode 100644 index dbd8f6f8e2e765a7d0186a8348beb80341d57d25..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1375 zcmV-l1)%yLiwFqjcuidZ17u-zVJ>QOZ*Bn1Tg`6cHW0q&DFmUXA{JyjiPt(MXm^1Y zZ5CJuEs!`cXo;5CP(MpjNxXsIq(D!7zdk~Tlq^fO7fl*(hKB&rkQ@$&oNtE1@zasx zxN2EYH*nl258Y!;xF@{SlqdLrPZsG`mPPcjR}iJB$O4|nQW2T-zBfahx?Okl?%14U zWKEZODZ&jmXNyy-0h$(!@fhUy331(!G zJf5cZVIV6iJcNG#5*If`K-6B5TJs)Uwb0 zr+LKS@)+TIU&F#4<&=WkBMh7!$rPRGcJ!;!XJ}IBQhe?1fw*ZV&w;EMS&)!odgHG+ z0)2cf$t@r%XI3Tjx)QXB&lX`#`7bp*KOKf{(FRvA<4DZ{SqtW~rj}mjMOsW#yg7@MI zvZs_HM1C^EKbuYPZ4Ihnqr6UTBR{X$XRRQI1rOX{u;^(f>-MJoEQtM@ZBzbu&rQ*~ zy=xNO_NfiqL9e52`!r)i=I~rUTS-OIt%trhe7l-2cQOob{Q6qm3bD&nOljJJiG8(c z_LO{D2YuGU^7<}YlylBxg8f81+b;EZNBVDdboBK6GJ8ph2$LWhki32;ihBJ%9rwoLFzlT~@BIGRkkT<7y}a_iV(vPfn)_;%Kyxpg3Av|{Gcnzq-@URnzObyw-T2HuqS!q29Kb z$y_MIsl#gTF4m&9__P__Qz7dW^jqXgkwzOI=YP;p{R*z3tG7m-6QkyzCm%139^j}a z5W907zh7{^!HRmS4dHW1^&3?HBRI+8(nLpK40jd1)nn>T-4%Q<7+2a%0{!ah8Xg&q4Z1bP(HNb8Nh>?Nqsx6^h&iC9VL}UM;2*=Z|{>|ElkA*j<|eTTd>0))(Z%H&Z1!%Xx4m&*rL!CV)-y3c{dlJ*sgp>0c11)N1=R007==tRnyb diff --git a/x-pack/test/functional/es_archives/reporting/logs/mappings.json b/x-pack/test/functional/es_archives/reporting/logs/mappings.json deleted file mode 100644 index 2e1873e43ffcc..0000000000000 --- a/x-pack/test/functional/es_archives/reporting/logs/mappings.json +++ /dev/null @@ -1,263 +0,0 @@ -{ - "type": "index", - "value": { - "aliases": { - ".kibana": {} - }, - "index": ".kibana_1", - "mappings": { - "properties": { - "config": { - "dynamic": "true", - "properties": { - "buildNum": { - "type": "keyword" - } - } - }, - "dashboard": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "optionsJSON": { - "type": "text" - }, - "panelsJSON": { - "type": "text" - }, - "refreshInterval": { - "properties": { - "display": { - "type": "keyword" - }, - "pause": { - "type": "boolean" - }, - "section": { - "type": "integer" - }, - "value": { - "type": "integer" - } - } - }, - "timeFrom": { - "type": "keyword" - }, - "timeRestore": { - "type": "boolean" - }, - "timeTo": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "graph-workspace": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "numLinks": { - "type": "integer" - }, - "numVertices": { - "type": "integer" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - }, - "wsState": { - "type": "text" - } - } - }, - "index-pattern": { - "dynamic": "strict", - "properties": { - "fieldFormatMap": { - "type": "text" - }, - "fields": { - "type": "text" - }, - "intervalName": { - "type": "keyword" - }, - "notExpandable": { - "type": "boolean" - }, - "sourceFilters": { - "type": "text" - }, - "timeFieldName": { - "type": "keyword" - }, - "title": { - "type": "text" - } - } - }, - "search": { - "dynamic": "strict", - "properties": { - "columns": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "sort": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "server": { - "dynamic": "strict", - "properties": { - "uuid": { - "type": "keyword" - } - } - }, - "space": { - "properties": { - "_reserved": { - "type": "boolean" - }, - "color": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "initials": { - "type": "keyword" - }, - "name": { - "fields": { - "keyword": { - "ignore_above": 2048, - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "spaceId": { - "type": "keyword" - }, - "type": { - "type": "keyword" - }, - "url": { - "dynamic": "strict", - "properties": { - "accessCount": { - "type": "long" - }, - "accessDate": { - "type": "date" - }, - "createDate": { - "type": "date" - }, - "url": { - "fields": { - "keyword": { - "ignore_above": 2048, - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "visualization": { - "dynamic": "strict", - "properties": { - "description": { - "type": "text" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "savedSearchId": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - }, - "visState": { - "type": "text" - } - } - } - } - }, - "settings": { - "index": { - "number_of_replicas": "1", - "number_of_shards": "1" - } - } - } -} \ No newline at end of file diff --git a/x-pack/test/functional/es_archives/reporting/multi_index/data.json.gz b/x-pack/test/functional/es_archives/reporting/multi_index/data.json.gz deleted file mode 100644 index bb0e05d632f54f278a3427176104a8e05575097d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 619 zcmV-x0+jt9iwFpk>GNI!17u-zVJ>QOZ*Bm!l}&HjKoExS{0hXmNX7{SI2BqFEJ3N# z^aH9MCiYlf{ISUP#$KWP_s+UBVJOg4bq)ya>({g1XJ&S`jb^iz>kYPs&6X$K)*B-{ zK%|Var3Ed8XPz!^zm0oSXB-56d)dF74?5S2%5EHqhov#)nB`g9vO2$?WKyN>b1YKc zdXQJ!*_Lg!tzO%{yt6Nc-R{sDtah)F4U#+~m-QsLQYCq+&71G%&%Oj=6YcwMO^Oqs z#sHoyBrWd+fG%~}WM4?OE(Q6t)(SIbbW)O4CLv%S zBytU;JvJKKe@IPGdulp^zozDD(CZw_&Ukt*J0Efo8`Kgsuf60~;WA2JVnCPp`OF!R(=?)pd6`!CTv7wY@{r0`9vv+q|oW1NE@AK{+~e;-Uv7e F002xHGbR84 diff --git a/x-pack/test/functional/es_archives/reporting/multi_index/mappings.json b/x-pack/test/functional/es_archives/reporting/multi_index/mappings.json deleted file mode 100644 index f28ffce8ce3ce..0000000000000 --- a/x-pack/test/functional/es_archives/reporting/multi_index/mappings.json +++ /dev/null @@ -1,92 +0,0 @@ -{ - "type": "index", - "value": { - "aliases": { - }, - "index": "tests-001", - "mappings": { - "properties": { - "@date": { - "type": "date" - }, - "ants": { - "type": "integer" - }, - "country": { - "type": "keyword" - }, - "name": { - "type": "keyword" - } - } - }, - "settings": { - "index": { - "number_of_replicas": "0", - "number_of_shards": "1" - } - } - } -} - -{ - "type": "index", - "value": { - "aliases": { - }, - "index": "tests-002", - "mappings": { - "properties": { - "@date": { - "type": "date" - }, - "ants": { - "type": "integer" - }, - "country": { - "type": "keyword" - }, - "name": { - "type": "keyword" - } - } - }, - "settings": { - "index": { - "number_of_replicas": "0", - "number_of_shards": "1" - } - } - } -} - -{ - "type": "index", - "value": { - "aliases": { - }, - "index": "tests-003", - "mappings": { - "properties": { - "@date": { - "type": "date" - }, - "ants": { - "type": "integer" - }, - "country": { - "type": "keyword" - }, - "name": { - "type": "keyword" - } - } - }, - "settings": { - "index": { - "number_of_replicas": "0", - "number_of_shards": "1" - } - } - } -} diff --git a/x-pack/test/functional/es_archives/reporting/multi_index_kibana/data.json.gz b/x-pack/test/functional/es_archives/reporting/multi_index_kibana/data.json.gz deleted file mode 100644 index a6330916d62f77c02dc978c12b512d0b21aa2a0f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 455 zcmV;&0XY62iwFp`>GNI!17u-zVJ>QOZ*Bn1mBCKqFc60CeTv9O)K*bpi^z!s;>Zbc zpsmo8I7G4ke?0zVCo}s|k_f-sqR0}VtQ2Dw-l3>i z*@sD(YQ?TL3O^@X@E*xz4vhn^t$|_!g>Hs%dD6u4qUoDngMn6ewj%kJIq79RF@m+x zSSZI?7W<_zP~uW#OL42fhtYT$xubMc&^-pt1#!`;s~}5T86U(njGZLC^{B#h1BFAD z5JJ(eAt>eZ$>{B{c|WJ@|!`tELmg0`GN+_gv&30xsA2SlYW0 zzK9OD7>DjcG~S^N5~a>5cAqCC7hc^S(r+)~dODw`-?I>IkkClvezR!Q)zNM{WH;T> xuC@%WUchtEES;s3bUv9~J{sP`@7La-e005p0;OPJW diff --git a/x-pack/test/functional/es_archives/reporting/multi_index_kibana/mappings.json b/x-pack/test/functional/es_archives/reporting/multi_index_kibana/mappings.json deleted file mode 100644 index 69c6cbc3b46b5..0000000000000 --- a/x-pack/test/functional/es_archives/reporting/multi_index_kibana/mappings.json +++ /dev/null @@ -1,2027 +0,0 @@ -{ - "type": "index", - "value": { - "aliases": { - ".kibana": { - } - }, - "index": ".kibana_1", - "mappings": { - "_meta": { - "migrationMappingPropertyHashes": { - "action": "6e96ac5e648f57523879661ea72525b7", - "action_task_params": "a9d49f184ee89641044be0ca2950fa3a", - "alert": "7b44fba6773e37c806ce290ea9b7024e", - "apm-indices": "9bb9b2bf1fa636ed8619cbab5ce6a1dd", - "apm-telemetry": "3525d7c22c42bc80f5e6e9cb3f2b26a2", - "application_usage_totals": "c897e4310c5f24b07caaff3db53ae2c1", - "application_usage_transactional": "965839e75f809fefe04f92dc4d99722a", - "canvas-element": "7390014e1091044523666d97247392fc", - "canvas-workpad": "b0a1706d356228dbdcb4a17e6b9eb231", - "cases": "32aa96a6d3855ddda53010ae2048ac22", - "cases-comments": "c2061fb929f585df57425102fa928b4b", - "cases-configure": "42711cbb311976c0687853f4c1354572", - "cases-user-actions": "32277330ec6b721abe3b846cfd939a71", - "config": "ae24d22d5986d04124cc6568f771066f", - "dashboard": "d00f614b29a80360e1190193fd333bab", - "file-upload-telemetry": "0ed4d3e1983d1217a30982630897092e", - "graph-workspace": "cd7ba1330e6682e9cc00b78850874be1", - "index-pattern": "66eccb05066c5a89924f48a9e9736499", - "kql-telemetry": "d12a98a6f19a2d273696597547e064ee", - "lens": "d33c68a69ff1e78c9888dedd2164ac22", - "lens-ui-telemetry": "509bfa5978586998e05f9e303c07a327", - "map": "4a05b35c3a3a58fbc72dd0202dc3487f", - "maps": "bfd39d88aadadb4be597ea984d433dbe", - "migrationVersion": "4a1746014a75ade3a714e1db5763276f", - "ml-telemetry": "257fd1d4b4fdbb9cb4b8a3b27da201e9", - "namespace": "2f4316de49999235636386fe51dc06c1", - "namespaces": "2f4316de49999235636386fe51dc06c1", - "query": "11aaeb7f5f7fa5bb43f25e18ce26e7d9", - "references": "7997cf5a56cc02bdc9c93361bde732b0", - "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4", - "search": "181661168bbadd1eff5902361e2a0d5c", - "telemetry": "36a616f7026dfa617d6655df850fe16d", - "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", - "type": "2f4316de49999235636386fe51dc06c1", - "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", - "updated_at": "00da57df13e94e9d98437d13ace4bfe0", - "upgrade-assistant-reindex-operation": "296a89039fc4260292be36b1b005d8f2", - "upgrade-assistant-telemetry": "56702cec857e0a9dacfb696655b4ff7b", - "uptime-dynamic-settings": "fcdb453a30092f022f2642db29523d80", - "url": "b675c3be8d76ecf029294d51dc7ec65d", - "visualization": "52d7a13ad68a150c4525b292d23e12cc" - } - }, - "dynamic": "strict", - "properties": { - "action": { - "properties": { - "actionTypeId": { - "type": "keyword" - }, - "config": { - "enabled": false, - "type": "object" - }, - "name": { - "fields": { - "keyword": { - "type": "keyword" - } - }, - "type": "text" - }, - "secrets": { - "type": "binary" - } - } - }, - "action_task_params": { - "properties": { - "actionId": { - "type": "keyword" - }, - "apiKey": { - "type": "binary" - }, - "params": { - "enabled": false, - "type": "object" - } - } - }, - "alert": { - "properties": { - "actions": { - "properties": { - "actionRef": { - "type": "keyword" - }, - "actionTypeId": { - "type": "keyword" - }, - "group": { - "type": "keyword" - }, - "params": { - "enabled": false, - "type": "object" - } - }, - "type": "nested" - }, - "alertTypeId": { - "type": "keyword" - }, - "apiKey": { - "type": "binary" - }, - "apiKeyOwner": { - "type": "keyword" - }, - "consumer": { - "type": "keyword" - }, - "createdAt": { - "type": "date" - }, - "createdBy": { - "type": "keyword" - }, - "enabled": { - "type": "boolean" - }, - "muteAll": { - "type": "boolean" - }, - "mutedInstanceIds": { - "type": "keyword" - }, - "name": { - "fields": { - "keyword": { - "type": "keyword" - } - }, - "type": "text" - }, - "params": { - "enabled": false, - "type": "object" - }, - "schedule": { - "properties": { - "interval": { - "type": "keyword" - } - } - }, - "scheduledTaskId": { - "type": "keyword" - }, - "tags": { - "type": "keyword" - }, - "throttle": { - "type": "keyword" - }, - "updatedBy": { - "type": "keyword" - } - } - }, - "apm-indices": { - "properties": { - "error": { - "type": "keyword" - }, - "metric": { - "type": "keyword" - }, - "onboarding": { - "type": "keyword" - }, - "sourcemap": { - "type": "keyword" - }, - "span": { - "type": "keyword" - }, - "transaction": { - "type": "keyword" - } - } - }, - "apm-telemetry": { - "properties": { - "agents": { - "properties": { - "dotnet": { - "properties": { - "agent": { - "properties": { - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "service": { - "properties": { - "framework": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "language": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "runtime": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - } - } - }, - "go": { - "properties": { - "agent": { - "properties": { - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "service": { - "properties": { - "framework": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "language": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "runtime": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - } - } - }, - "java": { - "properties": { - "agent": { - "properties": { - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "service": { - "properties": { - "framework": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "language": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "runtime": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - } - } - }, - "js-base": { - "properties": { - "agent": { - "properties": { - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "service": { - "properties": { - "framework": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "language": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "runtime": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - } - } - }, - "nodejs": { - "properties": { - "agent": { - "properties": { - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "service": { - "properties": { - "framework": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "language": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "runtime": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - } - } - }, - "python": { - "properties": { - "agent": { - "properties": { - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "service": { - "properties": { - "framework": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "language": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "runtime": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - } - } - }, - "ruby": { - "properties": { - "agent": { - "properties": { - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "service": { - "properties": { - "framework": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "language": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "runtime": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - } - } - }, - "rum-js": { - "properties": { - "agent": { - "properties": { - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "service": { - "properties": { - "framework": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "language": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - }, - "runtime": { - "properties": { - "composite": { - "ignore_above": 1024, - "type": "keyword" - }, - "name": { - "ignore_above": 1024, - "type": "keyword" - }, - "version": { - "ignore_above": 1024, - "type": "keyword" - } - } - } - } - } - } - } - } - }, - "cardinality": { - "properties": { - "transaction": { - "properties": { - "name": { - "properties": { - "all_agents": { - "properties": { - "1d": { - "type": "long" - } - } - }, - "rum": { - "properties": { - "1d": { - "type": "long" - } - } - } - } - } - } - }, - "user_agent": { - "properties": { - "original": { - "properties": { - "all_agents": { - "properties": { - "1d": { - "type": "long" - } - } - }, - "rum": { - "properties": { - "1d": { - "type": "long" - } - } - } - } - } - } - } - } - }, - "counts": { - "properties": { - "agent_configuration": { - "properties": { - "all": { - "type": "long" - } - } - }, - "error": { - "properties": { - "1d": { - "type": "long" - }, - "all": { - "type": "long" - } - } - }, - "max_error_groups_per_service": { - "properties": { - "1d": { - "type": "long" - } - } - }, - "max_transaction_groups_per_service": { - "properties": { - "1d": { - "type": "long" - } - } - }, - "metric": { - "properties": { - "1d": { - "type": "long" - }, - "all": { - "type": "long" - } - } - }, - "onboarding": { - "properties": { - "1d": { - "type": "long" - }, - "all": { - "type": "long" - } - } - }, - "services": { - "properties": { - "1d": { - "type": "long" - } - } - }, - "sourcemap": { - "properties": { - "1d": { - "type": "long" - }, - "all": { - "type": "long" - } - } - }, - "span": { - "properties": { - "1d": { - "type": "long" - }, - "all": { - "type": "long" - } - } - }, - "traces": { - "properties": { - "1d": { - "type": "long" - } - } - }, - "transaction": { - "properties": { - "1d": { - "type": "long" - }, - "all": { - "type": "long" - } - } - } - } - }, - "has_any_services": { - "type": "boolean" - }, - "indices": { - "properties": { - "all": { - "properties": { - "total": { - "properties": { - "docs": { - "properties": { - "count": { - "type": "long" - } - } - }, - "store": { - "properties": { - "size_in_bytes": { - "type": "long" - } - } - } - } - } - } - }, - "shards": { - "properties": { - "total": { - "type": "long" - } - } - } - } - }, - "integrations": { - "properties": { - "ml": { - "properties": { - "all_jobs_count": { - "type": "long" - } - } - } - } - }, - "retainment": { - "properties": { - "error": { - "properties": { - "ms": { - "type": "long" - } - } - }, - "metric": { - "properties": { - "ms": { - "type": "long" - } - } - }, - "onboarding": { - "properties": { - "ms": { - "type": "long" - } - } - }, - "span": { - "properties": { - "ms": { - "type": "long" - } - } - }, - "transaction": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - }, - "services_per_agent": { - "properties": { - "dotnet": { - "null_value": 0, - "type": "long" - }, - "go": { - "null_value": 0, - "type": "long" - }, - "java": { - "null_value": 0, - "type": "long" - }, - "js-base": { - "null_value": 0, - "type": "long" - }, - "nodejs": { - "null_value": 0, - "type": "long" - }, - "python": { - "null_value": 0, - "type": "long" - }, - "ruby": { - "null_value": 0, - "type": "long" - }, - "rum-js": { - "null_value": 0, - "type": "long" - } - } - }, - "tasks": { - "properties": { - "agent_configuration": { - "properties": { - "took": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - }, - "agents": { - "properties": { - "took": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - }, - "cardinality": { - "properties": { - "took": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - }, - "groupings": { - "properties": { - "took": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - }, - "indices_stats": { - "properties": { - "took": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - }, - "integrations": { - "properties": { - "took": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - }, - "processor_events": { - "properties": { - "took": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - }, - "services": { - "properties": { - "took": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - }, - "versions": { - "properties": { - "took": { - "properties": { - "ms": { - "type": "long" - } - } - } - } - } - } - }, - "version": { - "properties": { - "apm_server": { - "properties": { - "major": { - "type": "long" - }, - "minor": { - "type": "long" - }, - "patch": { - "type": "long" - } - } - } - } - } - } - }, - "application_usage_totals": { - "properties": { - "appId": { - "type": "keyword" - }, - "minutesOnScreen": { - "type": "float" - }, - "numberOfClicks": { - "type": "long" - } - } - }, - "application_usage_transactional": { - "properties": { - "appId": { - "type": "keyword" - }, - "minutesOnScreen": { - "type": "float" - }, - "numberOfClicks": { - "type": "long" - }, - "timestamp": { - "type": "date" - } - } - }, - "canvas-element": { - "dynamic": "false", - "properties": { - "@created": { - "type": "date" - }, - "@timestamp": { - "type": "date" - }, - "content": { - "type": "text" - }, - "help": { - "type": "text" - }, - "image": { - "type": "text" - }, - "name": { - "fields": { - "keyword": { - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "canvas-workpad": { - "dynamic": "false", - "properties": { - "@created": { - "type": "date" - }, - "@timestamp": { - "type": "date" - }, - "name": { - "fields": { - "keyword": { - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "cases": { - "properties": { - "closed_at": { - "type": "date" - }, - "closed_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - }, - "connector_id": { - "type": "keyword" - }, - "created_at": { - "type": "date" - }, - "created_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - }, - "description": { - "type": "text" - }, - "external_service": { - "properties": { - "connector_id": { - "type": "keyword" - }, - "connector_name": { - "type": "keyword" - }, - "external_id": { - "type": "keyword" - }, - "external_title": { - "type": "text" - }, - "external_url": { - "type": "text" - }, - "pushed_at": { - "type": "date" - }, - "pushed_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - } - } - }, - "status": { - "type": "keyword" - }, - "tags": { - "type": "keyword" - }, - "title": { - "type": "keyword" - }, - "updated_at": { - "type": "date" - }, - "updated_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - } - } - }, - "cases-comments": { - "properties": { - "comment": { - "type": "text" - }, - "created_at": { - "type": "date" - }, - "created_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - }, - "pushed_at": { - "type": "date" - }, - "pushed_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - }, - "updated_at": { - "type": "date" - }, - "updated_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - } - } - }, - "cases-configure": { - "properties": { - "closure_type": { - "type": "keyword" - }, - "connector_id": { - "type": "keyword" - }, - "connector_name": { - "type": "keyword" - }, - "created_at": { - "type": "date" - }, - "created_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - }, - "updated_at": { - "type": "date" - }, - "updated_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - } - } - }, - "cases-user-actions": { - "properties": { - "action": { - "type": "keyword" - }, - "action_at": { - "type": "date" - }, - "action_by": { - "properties": { - "email": { - "type": "keyword" - }, - "full_name": { - "type": "keyword" - }, - "username": { - "type": "keyword" - } - } - }, - "action_field": { - "type": "keyword" - }, - "new_value": { - "type": "text" - }, - "old_value": { - "type": "text" - } - } - }, - "config": { - "dynamic": "true", - "properties": { - "buildNum": { - "type": "keyword" - }, - "defaultIndex": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "dashboard": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "optionsJSON": { - "type": "text" - }, - "panelsJSON": { - "type": "text" - }, - "refreshInterval": { - "properties": { - "display": { - "type": "keyword" - }, - "pause": { - "type": "boolean" - }, - "section": { - "type": "integer" - }, - "value": { - "type": "integer" - } - } - }, - "timeFrom": { - "type": "keyword" - }, - "timeRestore": { - "type": "boolean" - }, - "timeTo": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "file-upload-telemetry": { - "properties": { - "filesUploadedTotalCount": { - "type": "long" - } - } - }, - "graph-workspace": { - "properties": { - "description": { - "type": "text" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "numLinks": { - "type": "integer" - }, - "numVertices": { - "type": "integer" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - }, - "wsState": { - "type": "text" - } - } - }, - "index-pattern": { - "properties": { - "fieldFormatMap": { - "type": "text" - }, - "fields": { - "type": "text" - }, - "intervalName": { - "type": "keyword" - }, - "notExpandable": { - "type": "boolean" - }, - "sourceFilters": { - "type": "text" - }, - "timeFieldName": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "type": { - "type": "keyword" - }, - "typeMeta": { - "type": "keyword" - } - } - }, - "kql-telemetry": { - "properties": { - "optInCount": { - "type": "long" - }, - "optOutCount": { - "type": "long" - } - } - }, - "lens": { - "properties": { - "description": { - "type": "text" - }, - "expression": { - "index": false, - "type": "keyword" - }, - "state": { - "type": "flattened" - }, - "title": { - "type": "text" - }, - "visualizationType": { - "type": "keyword" - } - } - }, - "lens-ui-telemetry": { - "properties": { - "count": { - "type": "integer" - }, - "date": { - "type": "date" - }, - "name": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - } - }, - "map": { - "properties": { - "description": { - "type": "text" - }, - "layerListJSON": { - "type": "text" - }, - "mapStateJSON": { - "type": "text" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "maps": { - "properties": { - "attributesPerMap": { - "properties": { - "dataSourcesCount": { - "properties": { - "avg": { - "type": "long" - }, - "max": { - "type": "long" - }, - "min": { - "type": "long" - } - } - }, - "emsVectorLayersCount": { - "dynamic": "true", - "type": "object" - }, - "layerTypesCount": { - "dynamic": "true", - "type": "object" - }, - "layersCount": { - "properties": { - "avg": { - "type": "long" - }, - "max": { - "type": "long" - }, - "min": { - "type": "long" - } - } - } - } - }, - "indexPatternsWithGeoFieldCount": { - "type": "long" - }, - "indexPatternsWithGeoPointFieldCount": { - "type": "long" - }, - "indexPatternsWithGeoShapeFieldCount": { - "type": "long" - }, - "mapsTotalCount": { - "type": "long" - }, - "settings": { - "properties": { - "showMapVisualizationTypes": { - "type": "boolean" - } - } - }, - "timeCaptured": { - "type": "date" - } - } - }, - "migrationVersion": { - "dynamic": "true", - "properties": { - "config": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - }, - "index-pattern": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "ml-telemetry": { - "properties": { - "file_data_visualizer": { - "properties": { - "index_creation_count": { - "type": "long" - } - } - } - } - }, - "namespace": { - "type": "keyword" - }, - "namespaces": { - "type": "keyword" - }, - "query": { - "properties": { - "description": { - "type": "text" - }, - "filters": { - "enabled": false, - "type": "object" - }, - "query": { - "properties": { - "language": { - "type": "keyword" - }, - "query": { - "index": false, - "type": "keyword" - } - } - }, - "timefilter": { - "enabled": false, - "type": "object" - }, - "title": { - "type": "text" - } - } - }, - "references": { - "properties": { - "id": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - }, - "type": "nested" - }, - "sample-data-telemetry": { - "properties": { - "installCount": { - "type": "long" - }, - "unInstallCount": { - "type": "long" - } - } - }, - "search": { - "properties": { - "columns": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "sort": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "telemetry": { - "properties": { - "allowChangingOptInStatus": { - "type": "boolean" - }, - "enabled": { - "type": "boolean" - }, - "lastReported": { - "type": "date" - }, - "lastVersionChecked": { - "type": "keyword" - }, - "reportFailureCount": { - "type": "integer" - }, - "reportFailureVersion": { - "type": "keyword" - }, - "sendUsageFrom": { - "type": "keyword" - }, - "userHasSeenNotice": { - "type": "boolean" - } - } - }, - "tsvb-validation-telemetry": { - "properties": { - "failedRequests": { - "type": "long" - } - } - }, - "type": { - "type": "keyword" - }, - "ui-metric": { - "properties": { - "count": { - "type": "integer" - } - } - }, - "updated_at": { - "type": "date" - }, - "upgrade-assistant-reindex-operation": { - "properties": { - "errorMessage": { - "type": "keyword" - }, - "indexName": { - "type": "keyword" - }, - "lastCompletedStep": { - "type": "integer" - }, - "locked": { - "type": "date" - }, - "newIndexName": { - "type": "keyword" - }, - "reindexOptions": { - "properties": { - "openAndClose": { - "type": "boolean" - }, - "queueSettings": { - "properties": { - "queuedAt": { - "type": "long" - }, - "startedAt": { - "type": "long" - } - } - } - } - }, - "reindexTaskId": { - "type": "keyword" - }, - "reindexTaskPercComplete": { - "type": "float" - }, - "runningReindexCount": { - "type": "integer" - }, - "status": { - "type": "integer" - } - } - }, - "upgrade-assistant-telemetry": { - "properties": { - "features": { - "properties": { - "deprecation_logging": { - "properties": { - "enabled": { - "null_value": true, - "type": "boolean" - } - } - } - } - }, - "ui_open": { - "properties": { - "cluster": { - "null_value": 0, - "type": "long" - }, - "indices": { - "null_value": 0, - "type": "long" - }, - "overview": { - "null_value": 0, - "type": "long" - } - } - }, - "ui_reindex": { - "properties": { - "close": { - "null_value": 0, - "type": "long" - }, - "open": { - "null_value": 0, - "type": "long" - }, - "start": { - "null_value": 0, - "type": "long" - }, - "stop": { - "null_value": 0, - "type": "long" - } - } - } - } - }, - "uptime-dynamic-settings": { - "properties": { - "certAgeThreshold": { - "type": "long" - }, - "certExpirationThreshold": { - "type": "long" - }, - "heartbeatIndices": { - "type": "keyword" - } - } - }, - "url": { - "properties": { - "accessCount": { - "type": "long" - }, - "accessDate": { - "type": "date" - }, - "createDate": { - "type": "date" - }, - "url": { - "fields": { - "keyword": { - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "visualization": { - "properties": { - "description": { - "type": "text" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "savedSearchRefName": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - }, - "visState": { - "type": "text" - } - } - } - } - }, - "settings": { - "index": { - "auto_expand_replicas": "0-1", - "number_of_replicas": "0", - "number_of_shards": "1" - } - } - } -} diff --git a/x-pack/test/functional/es_archives/reporting/nanos/data.json b/x-pack/test/functional/es_archives/reporting/nanos/data.json new file mode 100644 index 0000000000000..02a56e95dd1f6 --- /dev/null +++ b/x-pack/test/functional/es_archives/reporting/nanos/data.json @@ -0,0 +1,25 @@ +{ + "type": "doc", + "value": { + "id": "1", + "index": "nanos", + "source": { + "date": "2015-01-01T12:10:30", + "message": "Hello 1" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "2", + "index": "nanos", + "source": { + "date": "2015-01-01T12:10:30.123456789Z", + "message": "Hello 2" + }, + "type": "_doc" + } +} diff --git a/x-pack/test/functional/es_archives/reporting/nanos/data.json.gz b/x-pack/test/functional/es_archives/reporting/nanos/data.json.gz deleted file mode 100644 index 2811c495aae2d2f7e9dd330661fd9f5f76850ef4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 863 zcmV-l1EBmLiwFo|YCT>617u-zVJ>QOZ*Bm^R$Xt}Fcf{~S44Rx$PUmnd8^b`+oV!y zpgs%`GRaNInh)kUY*od7pFffimv#*m+oT|pWBdBtbA9jibI&kL_Ou1lGt40O&AtG3 zBq;^*%s=>N9Eedr&%!wJE(d6UtR zwR~UOF)4&VA@iZ&Hs%$&Y=&zQDw+*mZUe#~SP>>hZG>G5ITmtfHulC2e+Jlgzp3r2 z%^F)Pp(uy;y?w9u++AuZtF@6G;;^+IQJ&ZlQ21-W1)7WNlX1ql?4##tmWC}texmb`g2ami6X zFEhq5#NP{_##_009A^{)Vg*VUE#KU?hqtM1<)m1rprSJiX5S2O)V;TiN$@ zhC-CCB-Pyi!n5#m3OdiMOxHjqb_$&ue8H!NI}?)UOJ~#ub?NGXQ?)x2S;eqZkkyR; z5o48aZITV5QEaDSnFU&W_-pr z^JiJr=E9UCFBaYOG+HJaKThbA)jDy8#Y*MGl7JJf<+ z)j;Ffus=vr-3&Hyv`P4nT_wpOD|ag12jE8u^hweq!SYeYh_sFX#7Badoq&+#x$ zm}qId8ab?G@JZroriE-lB4oQhQOZ*Bn1nN4roI1q;K`4vLWOJRnjNXn-++1(ah z6a`u}UpK~P*4|a5K$6?0LH~P6W4A#;(Q)Ik6Tm)*rYyYVybotacL!N^INaX2!%22H zYo~|5g@I4DUWSo7f8j3|v+(Q3cYnP5`0C%IFZ21;$(g+V%UHLX9%eK%_a#i~ZSDI1 zNBef!P4EB5kGz=KVf82uSTYa{ii-0|ffIoj|J~-NUAv)MO#M6|P4Q#2vDd46KPqk`qPn%u{sU!WHaBm0tPh;zEuODQE zDs6qvN4)=V{9|r7`MGyW?0>Hx85aS4N&jmc(f``I?6_qvIwChV##!-LVEqYh`{r`R0`R(72czVkN z0~3&!w0r>&A9=5B?P2%H?ngDb7qb8^>v{o$;KwCk18&nDM7CpSJA#$}*h()eiPNP)NPhuiFxe^N6n8n++u z^!^XIUWLUP|7#UfZ<8Y*eXXYjd9rs-4*>tc3W_uUxZ)$l-r#QLuE~zuZg5{?{W332 zLC(JmdjIQi>SLMqzmEX^s1VqJ1up3QuRQ_-|C0VUV9c}8Pmch_xTyfr2q+7PQ-BS6 z9ihuvlDGIS=mDTYwU06l03}5n0ULEWWo|%|L(tMwK!sL6$aT#=5!XIXf)jAExuqo&_Tb!44wflAZ%{l?{kA2XL$mUf=cS9)gA(f|v9VkRcWD5KOoI zqHM literal 1762 zcmV<81|9hyiwFpsk4;?w17u-zVJ>QOZ*Bn1Sy^w}I1ql%uMmWNj0BRpC_inQUfbO^ zi$qP@94Kgsw$;j#S4Y}3@PA)Y@*#ziH^V ztA|{0OQM2H?%4x-3r&rRw2=5o z5$8+<+4sy(!wGW)dmNA`qg+Hu>W@f2;;-dt!DVDPOzDv1O=(I=@HS1xuM_{AJ0Y75 zO%Lm;d<%o7B3Yh>@vxx^*o7+PDi3wxo_sm#WPXx9IHtH5_i6fMb>_kc`pm^^D^$i_ zQ=_`@8C0tDcp)b?rfhFt%85yUR`ul(*X#-AljP^j39B5lCuOq2XBi=Ds?$%Bh?00u z2X&*?vRWSIQJFP5ve-BrrX&xO_?)I0``6_7hxD)7y2Y~crn#`W+`1{zuymHKw@8VV zPkRoe2BipS#=vzo2&t>}F{PT_cWqz}&;UEm(=+612Uc72g`BdUSMr`LRaHEg; z_y(E^(YH8*+PU~iRE*=M&*#cEb}Pwcx%y3`V65<49lBaa-6+iY+Mp(CwZEa%Le-@^ z__~Z;M&WQ2u}^*!MN0y>tpaW-_^+Vp9eccAe!W&p(uqhsEXc58qaW4Sl*ub^Gbu0O z{>bvOP~-1sd08H?*<>;~tK|Fzekj!p5`~0QPFzG08wkRT(^H+we$cxI{2X%@t7qZ~ zNy#{?795r%oTj6Ew(hh}qA9bX$~h&-OBq!1L@8xuG@R1Gr#TUC5pR{Lv;DYgGYh)3 zoKA1H&|to0E7%y}q?{G6uEd~h#3{-AT9n?jWq`Azf8Hqno?N^+t%vIEg0`SQPIIWu z5VRg)8$Ez^6T>YC`}4`0lh5ydy!tg74bH!qezOqKeXunh0bt*6qhM;byNWG7>H zSnon$3f8}k5?0d%L^+D+cM=C_BHGW|)%vSTA8=0E+y%uBTmYd5j2`L=01IQ$1s{@_ zMiJFMkeDhTNt%yD7eF04HkR%JO}Y!9W&jU5J)R{qhqeb$H|Pz+bOig?;k_N$e<4DD zd;5XuLAx9J2(@7U@%V>2M$+@%g=G9Q`=N#mPJNyDcVLVC?+I0o(d#`SMW@Gy{4NcB9^M9Q&o9{)>Fn0}TNWz;4vL*cGGxI1MuzvGadj zuW)|<#j^d0zgc8?@7eS?w0JwYUzjvE>HB#!lSLwh9SVJafy-wgmY_>Fb zMV8+w@7PXp9>#P(#>>Y)uk}1=t-!xyi>$X!%7?7ia}4Rl-dFhq!2X~ItZo88Hfm}1 zayl8&SUFD8ocn^^`YC1e-;5~qY&5u1TN*{-(eKU`L7fI7-0Nj z?3YggwuZ0=KsN=r77$B-HSRk2EvGB}itkcB0oc6Thh{ec*rp*yz*@JQrjCIlodk(| z7O?qMKQLEa_8DUD^Gqlp@>#&*RRCkb?i)Yb6sy2z`Kg1*=KxECh6mggjlX4XmIDyd z5pbp7`4agoVDT~l8Qm=48ZF)bE1Je6WsShAFb { before(async () => { + await reportingAPI.initEcommerce(); + await reportingAPI.initLogs(); await kibanaServer.uiSettings.update({ - 'csv:quoteValues': false, + 'csv:quoteValues': true, 'dateFormat:tz': 'UTC', - defaultIndex: 'logstash-*', }); - await reportingAPI.initEcommerce(); }); + after(async () => { await reportingAPI.teardownEcommerce(); + await reportingAPI.teardownLogs(); await reportingAPI.deleteAllReports(); }); - it('Exports CSV with almost all fields when using fieldsFromSource', async () => { - const { - status: resStatus, - text: resText, - type: resType, - } = (await generateAPI.getCSVFromSearchSource( - getMockJobParams({ - searchSource: { - query: { query: '', language: 'kuery' }, - index: '5193f870-d861-11e9-a311-0fa548c5f953', - sort: [{ order_date: 'desc' }], - fieldsFromSource: [ - '_id', - '_index', - '_score', - '_source', - '_type', - 'category', - 'category.keyword', - 'currency', - 'customer_birth_date', - 'customer_first_name', - 'customer_first_name.keyword', - 'customer_full_name', - 'customer_full_name.keyword', - 'customer_gender', - 'customer_id', - 'customer_last_name', - 'customer_last_name.keyword', - 'customer_phone', - 'day_of_week', - 'day_of_week_i', - 'email', - 'geoip.city_name', - 'geoip.continent_name', - 'geoip.country_iso_code', - 'geoip.location', - 'geoip.region_name', - 'manufacturer', - 'manufacturer.keyword', - 'order_date', - 'order_id', - 'products._id', - 'products._id.keyword', - 'products.base_price', - 'products.base_unit_price', - 'products.category', - 'products.category.keyword', - 'products.created_on', - 'products.discount_amount', - 'products.discount_percentage', - 'products.manufacturer', - 'products.manufacturer.keyword', - 'products.min_price', - 'products.price', - 'products.product_id', - 'products.product_name', - 'products.product_name.keyword', - 'products.quantity', - 'products.sku', - 'products.tax_amount', - 'products.taxful_price', - 'products.taxless_price', - 'products.unit_discount_amount', - 'sku', - 'taxful_total_price', - 'taxless_total_price', - 'total_quantity', - 'total_unique_products', - 'type', - 'user', - ], - filter: [], - parent: { - query: { language: 'kuery', query: '' }, + describe('unquoted values', () => { + before(async () => { + await kibanaServer.uiSettings.update({ 'csv:quoteValues': false }); + }); + + after(async () => { + await kibanaServer.uiSettings.update({ 'csv:quoteValues': true }); + }); + + it('Exports CSV with almost all fields when using fieldsFromSource', async () => { + const { + status: resStatus, + text: resText, + type: resType, + } = (await generateAPI.getCSVFromSearchSource( + getMockJobParams({ + searchSource: { + query: { query: '', language: 'kuery' }, + index: '5193f870-d861-11e9-a311-0fa548c5f953', + sort: [{ order_date: 'desc' }], + fieldsFromSource: [ + '_id', + '_index', + '_score', + '_source', + '_type', + 'category', + 'category.keyword', + 'currency', + 'customer_birth_date', + 'customer_first_name', + 'customer_first_name.keyword', + 'customer_full_name', + 'customer_full_name.keyword', + 'customer_gender', + 'customer_id', + 'customer_last_name', + 'customer_last_name.keyword', + 'customer_phone', + 'day_of_week', + 'day_of_week_i', + 'email', + 'geoip.city_name', + 'geoip.continent_name', + 'geoip.country_iso_code', + 'geoip.location', + 'geoip.region_name', + 'manufacturer', + 'manufacturer.keyword', + 'order_date', + 'order_id', + 'products._id', + 'products._id.keyword', + 'products.base_price', + 'products.base_unit_price', + 'products.category', + 'products.category.keyword', + 'products.created_on', + 'products.discount_amount', + 'products.discount_percentage', + 'products.manufacturer', + 'products.manufacturer.keyword', + 'products.min_price', + 'products.price', + 'products.product_id', + 'products.product_name', + 'products.product_name.keyword', + 'products.quantity', + 'products.sku', + 'products.tax_amount', + 'products.taxful_price', + 'products.taxless_price', + 'products.unit_discount_amount', + 'sku', + 'taxful_total_price', + 'taxless_total_price', + 'total_quantity', + 'total_unique_products', + 'type', + 'user', + ], filter: [], parent: { - filter: [ - { - meta: { index: '5193f870-d861-11e9-a311-0fa548c5f953', params: {} }, - range: { - order_date: { - gte: fromTime, - lte: toTime, - format: 'strict_date_optional_time', + query: { language: 'kuery', query: '' }, + filter: [], + parent: { + filter: [ + { + meta: { index: '5193f870-d861-11e9-a311-0fa548c5f953', params: {} }, + range: { + order_date: { + gte: fromTime, + lte: toTime, + format: 'strict_date_optional_time', + }, }, }, - }, - ], + ], + }, }, }, - }, - browserTimezone: 'UTC', - title: 'testfooyu78yt90-', - }) - )) as supertest.Response; - expect(resStatus).to.eql(200); - expect(resType).to.eql('text/csv'); - expectSnapshot(resText).toMatch(); - }); + browserTimezone: 'UTC', + title: 'testfooyu78yt90-', + }) + )) as supertest.Response; + expect(resStatus).to.eql(200); + expect(resType).to.eql('text/csv'); + expectSnapshot(resText).toMatch(); + }); - it('Exports CSV with all fields when using defaults', async () => { - const { - status: resStatus, - text: resText, - type: resType, - } = await generateAPI.getCSVFromSearchSource( - getMockJobParams({ - searchSource: { - query: { query: '', language: 'kuery' }, - index: '5193f870-d861-11e9-a311-0fa548c5f953', - sort: [{ order_date: 'desc' }], - fields: ['*'], - filter: [], - parent: { - query: { language: 'kuery', query: '' }, + it('Exports CSV with all fields when using defaults', async () => { + const { + status: resStatus, + text: resText, + type: resType, + } = await generateAPI.getCSVFromSearchSource( + getMockJobParams({ + searchSource: { + query: { query: '', language: 'kuery' }, + index: '5193f870-d861-11e9-a311-0fa548c5f953', + sort: [{ order_date: 'desc' }], + fields: ['*'], filter: [], parent: { - filter: [ - { - meta: { index: '5193f870-d861-11e9-a311-0fa548c5f953', params: {} }, - range: { - order_date: { - gte: fromTime, - lte: toTime, - format: 'strict_date_optional_time', + query: { language: 'kuery', query: '' }, + filter: [], + parent: { + filter: [ + { + meta: { index: '5193f870-d861-11e9-a311-0fa548c5f953', params: {} }, + range: { + order_date: { + gte: fromTime, + lte: toTime, + format: 'strict_date_optional_time', + }, }, }, - }, - ], + ], + }, }, }, - }, - browserTimezone: 'UTC', - title: 'testfooyu78yt90-', - }) - ); - expect(resStatus).to.eql(200); - expect(resType).to.eql('text/csv'); - expectSnapshot(resText).toMatch(); + browserTimezone: 'UTC', + title: 'testfooyu78yt90-', + }) + ); + expect(resStatus).to.eql(200); + expect(resType).to.eql('text/csv'); + expectSnapshot(resText).toMatch(); + }); }); describe('date formatting', () => { - before(async () => { - // load test data that contains a saved search and documents - await esArchiver.load('x-pack/test/functional/es_archives/reporting/logs'); - await esArchiver.load('x-pack/test/functional/es_archives/logstash_functional'); - }); - after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/reporting/logs'); - await esArchiver.unload('x-pack/test/functional/es_archives/logstash_functional'); - }); - it('With filters and timebased data, default to UTC', async () => { const res = (await generateAPI.getCSVFromSearchSource( getMockJobParams({ @@ -277,10 +279,18 @@ export default function ({ getService }: FtrProviderContext) { expect(resType).to.eql('text/csv'); expectSnapshot(resText).toMatch(); }); + }); - it('Formatted date_nanos data, UTC timezone', async () => { + describe('nanosecond formatting', () => { + before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/reporting/nanos'); + }); + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/reporting/nanos'); + }); + + it('Formatted date_nanos data, UTC timezone', async () => { const res = await generateAPI.getCSVFromSearchSource( getMockJobParams({ searchSource: { @@ -298,13 +308,9 @@ export default function ({ getService }: FtrProviderContext) { expect(resStatus).to.eql(200); expect(resType).to.eql('text/csv'); expectSnapshot(resText).toMatch(); - - await esArchiver.unload('x-pack/test/functional/es_archives/reporting/nanos'); }); it('Formatted date_nanos data, custom timezone (New York)', async () => { - await esArchiver.load('x-pack/test/functional/es_archives/reporting/nanos'); - const res = await generateAPI.getCSVFromSearchSource( getMockJobParams({ browserTimezone: 'America/New_York', @@ -323,8 +329,6 @@ export default function ({ getService }: FtrProviderContext) { expect(resStatus).to.eql(200); expect(resType).to.eql('text/csv'); expectSnapshot(resText).toMatch(); - - await esArchiver.unload('x-pack/test/functional/es_archives/reporting/nanos'); }); }); @@ -354,7 +358,6 @@ export default function ({ getService }: FtrProviderContext) { }); it('With filters and non-timebased data', async () => { - // load test data that contains a saved search and documents await esArchiver.load('x-pack/test/functional/es_archives/reporting/sales'); const { @@ -405,8 +408,6 @@ export default function ({ getService }: FtrProviderContext) { // NOTE: this test requires having the test server run with `xpack.reporting.csv.maxSizeBytes=6000` it(`Searches large amount of data, stops at Max Size Reached`, async () => { - await reportingAPI.initEcommerce(); - const { status: resStatus, text: resText, @@ -447,8 +448,6 @@ export default function ({ getService }: FtrProviderContext) { expect(resStatus).to.eql(200); expect(resType).to.eql('text/csv'); expectSnapshot(resText).toMatch(); - - await reportingAPI.teardownEcommerce(); }); }); }); diff --git a/x-pack/test/reporting_api_integration/reporting_and_security/generate_csv_discover_deprecated.ts b/x-pack/test/reporting_api_integration/reporting_and_security/generate_csv_discover_deprecated.ts index 9e3ddfaf57b39..bd662fb391f15 100644 --- a/x-pack/test/reporting_api_integration/reporting_and_security/generate_csv_discover_deprecated.ts +++ b/x-pack/test/reporting_api_integration/reporting_and_security/generate_csv_discover_deprecated.ts @@ -12,7 +12,6 @@ import { JOB_PARAMS_RISON_CSV_DEPRECATED } from '../services/fixtures'; // eslint-disable-next-line import/no-default-export export default function ({ getService }: FtrProviderContext) { - const esArchiver = getService('esArchiver'); const supertestSvc = getService('supertest'); const reportingAPI = getService('reportingAPI'); @@ -32,13 +31,11 @@ export default function ({ getService }: FtrProviderContext) { describe('Generation from Legacy Job Params', () => { before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/reporting/logs'); - await esArchiver.load('x-pack/test/functional/es_archives/logstash_functional'); + await reportingAPI.initLogs(); }); after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/reporting/logs'); - await esArchiver.unload('x-pack/test/functional/es_archives/logstash_functional'); + await reportingAPI.teardownLogs(); await reportingAPI.deleteAllReports(); }); diff --git a/x-pack/test/reporting_api_integration/reporting_and_security/ilm_migration_apis.ts b/x-pack/test/reporting_api_integration/reporting_and_security/ilm_migration_apis.ts index 6a2139a70dde5..af6afe99e8c9d 100644 --- a/x-pack/test/reporting_api_integration/reporting_and_security/ilm_migration_apis.ts +++ b/x-pack/test/reporting_api_integration/reporting_and_security/ilm_migration_apis.ts @@ -13,7 +13,6 @@ import { ILM_POLICY_NAME } from '../../../plugins/reporting/common/constants'; // eslint-disable-next-line import/no-default-export export default function ({ getService }: FtrProviderContext) { - const esArchiver = getService('esArchiver'); const es = getService('es'); const supertest = getService('supertest'); const supertestWithoutAuth = getService('supertestWithoutAuth'); @@ -22,14 +21,12 @@ export default function ({ getService }: FtrProviderContext) { describe('ILM policy migration APIs', () => { before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/reporting/logs'); - await esArchiver.load('x-pack/test/functional/es_archives/logstash_functional'); + await reportingAPI.initLogs(); await reportingAPI.migrateReportingIndices(); // ensure that the ILM policy exists for the first test }); after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/reporting/logs'); - await esArchiver.unload('x-pack/test/functional/es_archives/logstash_functional'); + await reportingAPI.teardownLogs(); }); afterEach(async () => { diff --git a/x-pack/test/reporting_api_integration/reporting_and_security/network_policy.ts b/x-pack/test/reporting_api_integration/reporting_and_security/network_policy.ts index f097208658467..842cfbcf7c1e1 100644 --- a/x-pack/test/reporting_api_integration/reporting_and_security/network_policy.ts +++ b/x-pack/test/reporting_api_integration/reporting_and_security/network_policy.ts @@ -10,11 +10,9 @@ import { FtrProviderContext } from '../ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function ({ getService }: FtrProviderContext) { - const esArchiver = getService('esArchiver'); const reportingAPI = getService('reportingAPI'); const retry = getService('retry'); const supertest = getService('supertest'); - const archive = 'x-pack/test/functional/es_archives/reporting/canvas_disallowed_url'; /* * The Reporting API Functional Test config implements a network policy that @@ -22,11 +20,11 @@ export default function ({ getService }: FtrProviderContext) { */ describe('Network Policy', () => { before(async () => { - await esArchiver.load(archive); // includes a canvas worksheet with an offending image URL + await reportingAPI.initLogs(); // includes a canvas worksheet with an offending image URL }); after(async () => { - await esArchiver.unload(archive); + await reportingAPI.teardownLogs(); }); it('should fail job when page voilates the network policy', async () => { diff --git a/x-pack/test/reporting_api_integration/reporting_and_security/spaces.ts b/x-pack/test/reporting_api_integration/reporting_and_security/spaces.ts index e61195e2f95c8..e1ca664122c76 100644 --- a/x-pack/test/reporting_api_integration/reporting_and_security/spaces.ts +++ b/x-pack/test/reporting_api_integration/reporting_and_security/spaces.ts @@ -38,18 +38,19 @@ export default function ({ getService }: FtrProviderContext) { ); }; + const spacesSharedObjectsArchive = + 'x-pack/test/functional/es_archives/reporting/ecommerce_kibana_spaces'; + describe('Exports and Spaces', () => { before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/reporting/ecommerce'); - await esArchiver.load('x-pack/test/functional/es_archives/reporting/ecommerce_kibana_spaces'); // multiple spaces with different config settings + await esArchiver.load(spacesSharedObjectsArchive); // multiple spaces with different config settings + await reportingAPI.initEcommerce(); }); after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/reporting/ecommerce'); - await esArchiver.unload( - 'x-pack/test/functional/es_archives/reporting/ecommerce_kibana_spaces' - ); + await reportingAPI.teardownEcommerce(); await reportingAPI.deleteAllReports(); + await esArchiver.unload(spacesSharedObjectsArchive); }); describe('CSV saved search export', () => { diff --git a/x-pack/test/reporting_api_integration/reporting_without_security/job_apis_csv.ts b/x-pack/test/reporting_api_integration/reporting_without_security/job_apis_csv.ts index 06f3756593d76..e1935c2617f41 100644 --- a/x-pack/test/reporting_api_integration/reporting_without_security/job_apis_csv.ts +++ b/x-pack/test/reporting_api_integration/reporting_without_security/job_apis_csv.ts @@ -49,12 +49,12 @@ export default function ({ getService }: FtrProviderContext) { describe('Job Listing APIs', () => { before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/reporting/logs'); + await reportingAPI.initLogs(); await esArchiver.load('x-pack/test/functional/es_archives/logstash_functional'); }); after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/reporting/logs'); + await reportingAPI.teardownLogs(); await esArchiver.unload('x-pack/test/functional/es_archives/logstash_functional'); }); diff --git a/x-pack/test/reporting_api_integration/reporting_without_security/job_apis_csv_deprecated.ts b/x-pack/test/reporting_api_integration/reporting_without_security/job_apis_csv_deprecated.ts index 6ff8946d48c5b..5cd6065352649 100644 --- a/x-pack/test/reporting_api_integration/reporting_without_security/job_apis_csv_deprecated.ts +++ b/x-pack/test/reporting_api_integration/reporting_without_security/job_apis_csv_deprecated.ts @@ -27,19 +27,16 @@ const parseApiJSON = (apiResponseText: string): { job: ReportApiJSON; path: stri // eslint-disable-next-line import/no-default-export export default function ({ getService }: FtrProviderContext) { - const esArchiver = getService('esArchiver'); const supertestNoAuth = getService('supertestWithoutAuth'); const reportingAPI = getService('reportingAPI'); describe('Job Listing APIs: Deprecated CSV Export', () => { before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/reporting/logs'); - await esArchiver.load('x-pack/test/functional/es_archives/logstash_functional'); + await reportingAPI.initLogs(); }); after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/reporting/logs'); - await esArchiver.unload('x-pack/test/functional/es_archives/logstash_functional'); + await reportingAPI.teardownLogs(); }); afterEach(async () => { diff --git a/x-pack/test/reporting_api_integration/services/scenarios.ts b/x-pack/test/reporting_api_integration/services/scenarios.ts index a596b61ea00d1..6af60018d01da 100644 --- a/x-pack/test/reporting_api_integration/services/scenarios.ts +++ b/x-pack/test/reporting_api_integration/services/scenarios.ts @@ -29,7 +29,9 @@ export function createScenarios({ getService }: Pick { + await esArchiver.load('x-pack/test/functional/es_archives/logstash_functional'); + await kibanaServer.importExport.load(logsSOPath); + }; + const teardownLogs = async () => { + await kibanaServer.importExport.unload(logsSOPath); + await esArchiver.unload('x-pack/test/functional/es_archives/logstash_functional'); + }; + const createDataAnalystRole = async () => { await security.role.create('data_analyst', { metadata: {}, @@ -222,6 +233,8 @@ export function createScenarios({ getService }: Pick Date: Tue, 2 Nov 2021 21:56:47 -0500 Subject: [PATCH 29/41] Bump node to 16.13.0 (#116519) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .ci/Dockerfile | 2 +- .node-version | 2 +- .nvmrc | 2 +- WORKSPACE.bazel | 14 +++++++------- package.json | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.ci/Dockerfile b/.ci/Dockerfile index 29ed08c84b23e..8e0d2d4351965 100644 --- a/.ci/Dockerfile +++ b/.ci/Dockerfile @@ -1,7 +1,7 @@ # NOTE: This Dockerfile is ONLY used to run certain tasks in CI. It is not used to run Kibana or as a distributable. # If you're looking for the Kibana Docker image distributable, please see: src/dev/build/tasks/os_packages/docker_generator/templates/dockerfile.template.ts -ARG NODE_VERSION=16.11.1 +ARG NODE_VERSION=16.13.0 FROM node:${NODE_VERSION} AS base diff --git a/.node-version b/.node-version index 141e9a2a2cef0..58a4133d910f4 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -16.11.1 +16.13.0 diff --git a/.nvmrc b/.nvmrc index 141e9a2a2cef0..5b0ad74a81023 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -16.11.1 +16.13.0 \ No newline at end of file diff --git a/WORKSPACE.bazel b/WORKSPACE.bazel index d3c44eab2a526..08c5bfa551437 100644 --- a/WORKSPACE.bazel +++ b/WORKSPACE.bazel @@ -27,14 +27,14 @@ check_rules_nodejs_version(minimum_version_string = "3.8.0") # we can update that rule. node_repositories( node_repositories = { - "16.11.1-darwin_amd64": ("node-v16.11.1-darwin-x64.tar.gz", "node-v16.11.1-darwin-x64", "ba54b8ed504bd934d03eb860fefe991419b4209824280d4274f6a911588b5e45"), - "16.11.1-darwin_arm64": ("node-v16.11.1-darwin-arm64.tar.gz", "node-v16.11.1-darwin-arm64", "5e772e478390fab3001b7148a923e4f22fca50170000f18b28475337d3a97248"), - "16.11.1-linux_arm64": ("node-v16.11.1-linux-arm64.tar.xz", "node-v16.11.1-linux-arm64", "083fc51f0ea26de9041aaf9821874651a9fd3b20d1cf57071ce6b523a0436f17"), - "16.11.1-linux_s390x": ("node-v16.11.1-linux-s390x.tar.xz", "node-v16.11.1-linux-s390x", "855b5c83c2ccb05273d50bb04376335c68d47df57f3187cdebe1f22b972d2825"), - "16.11.1-linux_amd64": ("node-v16.11.1-linux-x64.tar.xz", "node-v16.11.1-linux-x64", "493bcc9b660eff983a6de65a0f032eb2717f57207edf74c745bcb86e360310b3"), - "16.11.1-windows_amd64": ("node-v16.11.1-win-x64.zip", "node-v16.11.1-win-x64", "4d3c179b82d42e66e321c3948a4e332ed78592917a69d38b86e3a242d7e62fb7"), + "16.13.0-darwin_amd64": ("node-v16.13.0-darwin-x64.tar.gz", "node-v16.13.0-darwin-x64", "37e09a8cf2352f340d1204c6154058d81362fef4ec488b0197b2ce36b3f0367a"), + "16.13.0-darwin_arm64": ("node-v16.13.0-darwin-arm64.tar.gz", "node-v16.13.0-darwin-arm64", "46d83fc0bd971db5050ef1b15afc44a6665dee40bd6c1cbaec23e1b40fa49e6d"), + "16.13.0-linux_arm64": ("node-v16.13.0-linux-arm64.tar.xz", "node-v16.13.0-linux-arm64", "93a0d03f9f802353cb7052bc97a02cd9642b49fa985671cdc16c99936c86d7d2"), + "16.13.0-linux_s390x": ("node-v16.13.0-linux-s390x.tar.xz", "node-v16.13.0-linux-s390x", "49e972bf3e969d621157df4c8f2fa18ff748c167d5ebd0efc87e1b9f0c6541cc"), + "16.13.0-linux_amd64": ("node-v16.13.0-linux-x64.tar.xz", "node-v16.13.0-linux-x64", "a876ce787133149abd1696afa54b0b5bc5ce3d5ae359081d407ff776e39b7ba8"), + "16.13.0-windows_amd64": ("node-v16.13.0-win-x64.zip", "node-v16.13.0-win-x64", "5a39ec5d4786c2814a6c04488bebac6423c2aaa12832b24f0882456f2e4674e1"), }, - node_version = "16.11.1", + node_version = "16.13.0", node_urls = [ "https://nodejs.org/dist/v{version}/{filename}", ], diff --git a/package.json b/package.json index 1718c703ee7a7..f35800746095a 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,7 @@ "**/underscore": "^1.13.1" }, "engines": { - "node": "16.11.1", + "node": "16.13.0", "yarn": "^1.21.1" }, "dependencies": { From 6328a9f597f3cd694d8201a1ee6e787691bf1a00 Mon Sep 17 00:00:00 2001 From: Dmitry Tomashevich <39378793+Dmitriynj@users.noreply.github.com> Date: Wed, 3 Nov 2021 11:42:37 +0300 Subject: [PATCH 30/41] [Discover] Fix Data grid columns movement (#115279) * [Discover] fix data grid columns movement * [Discover] apply suggestion * [Discover] fix linting Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../application/apps/context/context_app.tsx | 4 ++-- .../apps/context/context_app_content.tsx | 2 +- .../components/doc_table/actions/columns.test.ts | 2 +- .../main/components/doc_table/actions/columns.ts | 7 ++++--- .../components/layout/discover_documents.tsx | 4 ++-- .../main/components/layout/discover_layout.tsx | 4 ++-- .../components/discover_grid/discover_grid.tsx | 16 ++++++++++++---- .../helpers/use_data_grid_columns.test.tsx | 10 +++++----- .../application/helpers/use_data_grid_columns.ts | 6 +++--- 9 files changed, 32 insertions(+), 23 deletions(-) diff --git a/src/plugins/discover/public/application/apps/context/context_app.tsx b/src/plugins/discover/public/application/apps/context/context_app.tsx index 9d39c93d250f2..bfc13aac90e4b 100644 --- a/src/plugins/discover/public/application/apps/context/context_app.tsx +++ b/src/plugins/discover/public/application/apps/context/context_app.tsx @@ -19,7 +19,7 @@ import { IndexPattern, IndexPatternField } from '../../../../../data/common'; import { LoadingStatus } from './services/context_query_state'; import { getServices } from '../../../kibana_services'; import { AppState, isEqualFilters } from './services/context_state'; -import { useDataGridColumns } from '../../helpers/use_data_grid_columns'; +import { useColumns } from '../../helpers/use_data_grid_columns'; import { useContextAppState } from './utils/use_context_app_state'; import { useContextAppFetch } from './utils/use_context_app_fetch'; import { popularizeField } from '../../helpers/popularize_field'; @@ -84,7 +84,7 @@ export const ContextApp = ({ indexPattern, anchorId }: ContextAppProps) => { fetchedState.anchor._id, ]); - const { columns, onAddColumn, onRemoveColumn, onSetColumns } = useDataGridColumns({ + const { columns, onAddColumn, onRemoveColumn, onSetColumns } = useColumns({ capabilities, config: uiSettings, indexPattern, diff --git a/src/plugins/discover/public/application/apps/context/context_app_content.tsx b/src/plugins/discover/public/application/apps/context/context_app_content.tsx index 2d4d3cab250f3..153639edc29a1 100644 --- a/src/plugins/discover/public/application/apps/context/context_app_content.tsx +++ b/src/plugins/discover/public/application/apps/context/context_app_content.tsx @@ -28,7 +28,7 @@ export interface ContextAppContentProps { columns: string[]; onAddColumn: (columnsName: string) => void; onRemoveColumn: (columnsName: string) => void; - onSetColumns: (columnsNames: string[]) => void; + onSetColumns: (columnsNames: string[], hideTimeColumn: boolean) => void; services: DiscoverServices; indexPattern: IndexPattern; predecessorCount: number; diff --git a/src/plugins/discover/public/application/apps/main/components/doc_table/actions/columns.test.ts b/src/plugins/discover/public/application/apps/main/components/doc_table/actions/columns.test.ts index d0984ff6fa797..93b38cfc6519c 100644 --- a/src/plugins/discover/public/application/apps/main/components/doc_table/actions/columns.test.ts +++ b/src/plugins/discover/public/application/apps/main/components/doc_table/actions/columns.test.ts @@ -64,7 +64,7 @@ describe('Test column actions', () => { sort: [], }); setAppState.mockClear(); - actions.onSetColumns(['first', 'second', 'third']); + actions.onSetColumns(['first', 'second', 'third'], true); expect(setAppState).toHaveBeenCalledWith({ columns: ['first', 'second', 'third'], }); diff --git a/src/plugins/discover/public/application/apps/main/components/doc_table/actions/columns.ts b/src/plugins/discover/public/application/apps/main/components/doc_table/actions/columns.ts index f3ad590ac6ce4..2fc82e25634bd 100644 --- a/src/plugins/discover/public/application/apps/main/components/doc_table/actions/columns.ts +++ b/src/plugins/discover/public/application/apps/main/components/doc_table/actions/columns.ts @@ -102,12 +102,13 @@ export function getStateColumnActions({ setAppState({ columns }); } - function onSetColumns(columns: string[]) { - // remove first element of columns if it's the configured timeFieldName, which is prepended automatically + function onSetColumns(columns: string[], hideTimeColumn: boolean) { + // The next line should gone when classic table will be removed const actualColumns = - indexPattern.timeFieldName && indexPattern.timeFieldName === columns[0] + !hideTimeColumn && indexPattern.timeFieldName && indexPattern.timeFieldName === columns[0] ? columns.slice(1) : columns; + setAppState({ columns: actualColumns }); } return { diff --git a/src/plugins/discover/public/application/apps/main/components/layout/discover_documents.tsx b/src/plugins/discover/public/application/apps/main/components/layout/discover_documents.tsx index d6ede9aa7fe5f..64d5e08f25d73 100644 --- a/src/plugins/discover/public/application/apps/main/components/layout/discover_documents.tsx +++ b/src/plugins/discover/public/application/apps/main/components/layout/discover_documents.tsx @@ -23,7 +23,7 @@ import { SAMPLE_SIZE_SETTING, SEARCH_FIELDS_FROM_SOURCE, } from '../../../../../../common'; -import { useDataGridColumns } from '../../../../helpers/use_data_grid_columns'; +import { useColumns } from '../../../../helpers/use_data_grid_columns'; import { IndexPattern } from '../../../../../../../data/common'; import { SavedSearch } from '../../../../../saved_searches'; import { DataDocumentsMsg, DataDocuments$ } from '../../services/use_saved_search'; @@ -69,7 +69,7 @@ function DiscoverDocumentsComponent({ const rows = useMemo(() => documentState.result || [], [documentState.result]); - const { columns, onAddColumn, onRemoveColumn, onMoveColumn, onSetColumns } = useDataGridColumns({ + const { columns, onAddColumn, onRemoveColumn, onMoveColumn, onSetColumns } = useColumns({ capabilities, config: uiSettings, indexPattern, diff --git a/src/plugins/discover/public/application/apps/main/components/layout/discover_layout.tsx b/src/plugins/discover/public/application/apps/main/components/layout/discover_layout.tsx index a0799777a3947..76ed7069b294a 100644 --- a/src/plugins/discover/public/application/apps/main/components/layout/discover_layout.tsx +++ b/src/plugins/discover/public/application/apps/main/components/layout/discover_layout.tsx @@ -35,7 +35,7 @@ import { getResultState } from '../../utils/get_result_state'; import { InspectorSession } from '../../../../../../../inspector/public'; import { DiscoverUninitialized } from '../uninitialized/uninitialized'; import { DataMainMsg } from '../../services/use_saved_search'; -import { useDataGridColumns } from '../../../../helpers/use_data_grid_columns'; +import { useColumns } from '../../../../helpers/use_data_grid_columns'; import { DiscoverDocuments } from './discover_documents'; import { FetchStatus } from '../../../../types'; import { useDataState } from '../../utils/use_data_state'; @@ -141,7 +141,7 @@ export function DiscoverLayout({ }; }, [inspectorSession]); - const { columns, onAddColumn, onRemoveColumn } = useDataGridColumns({ + const { columns, onAddColumn, onRemoveColumn } = useColumns({ capabilities, config: uiSettings, indexPattern, diff --git a/src/plugins/discover/public/application/components/discover_grid/discover_grid.tsx b/src/plugins/discover/public/application/components/discover_grid/discover_grid.tsx index ca403c813010b..6f96f21c9b8c8 100644 --- a/src/plugins/discover/public/application/components/discover_grid/discover_grid.tsx +++ b/src/plugins/discover/public/application/components/discover_grid/discover_grid.tsx @@ -36,7 +36,11 @@ import { import { defaultPageSize, gridStyle, pageSizeArr, toolbarVisibility } from './constants'; import { DiscoverServices } from '../../../build_services'; import { getDisplayedColumns } from '../../helpers/columns'; -import { MAX_DOC_FIELDS_DISPLAYED, SHOW_MULTIFIELDS } from '../../../../common'; +import { + DOC_HIDE_TIME_COLUMN_SETTING, + MAX_DOC_FIELDS_DISPLAYED, + SHOW_MULTIFIELDS, +} from '../../../../common'; import { DiscoverGridDocumentToolbarBtn, getDocId } from './discover_grid_document_selection'; import { SortPairArr } from '../../apps/main/components/doc_table/lib/get_sort'; import { getFieldsToShow } from '../../helpers/get_fields_to_show'; @@ -91,7 +95,7 @@ export interface DiscoverGridProps { /** * Function to set all columns */ - onSetColumns: (columns: string[]) => void; + onSetColumns: (columns: string[], hideTimeColumn: boolean) => void; /** * function to change sorting of the documents, skipped when isSortEnabled is set to false */ @@ -302,15 +306,19 @@ export const DiscoverGrid = ({ [displayedColumns, indexPattern, showTimeCol, settings, defaultColumns, isSortEnabled] ); + const hideTimeColumn = useMemo( + () => services.uiSettings.get(DOC_HIDE_TIME_COLUMN_SETTING, false), + [services.uiSettings] + ); const schemaDetectors = useMemo(() => getSchemaDetectors(), []); const columnsVisibility = useMemo( () => ({ visibleColumns: getVisibleColumns(displayedColumns, indexPattern, showTimeCol) as string[], setVisibleColumns: (newColumns: string[]) => { - onSetColumns(newColumns); + onSetColumns(newColumns, hideTimeColumn); }, }), - [displayedColumns, indexPattern, showTimeCol, onSetColumns] + [displayedColumns, indexPattern, showTimeCol, hideTimeColumn, onSetColumns] ); const sorting = useMemo(() => { if (isSortEnabled) { diff --git a/src/plugins/discover/public/application/helpers/use_data_grid_columns.test.tsx b/src/plugins/discover/public/application/helpers/use_data_grid_columns.test.tsx index ccee271b73e32..abb2138e882b1 100644 --- a/src/plugins/discover/public/application/helpers/use_data_grid_columns.test.tsx +++ b/src/plugins/discover/public/application/helpers/use_data_grid_columns.test.tsx @@ -7,14 +7,14 @@ */ import { renderHook } from '@testing-library/react-hooks'; -import { useDataGridColumns } from './use_data_grid_columns'; +import { useColumns } from './use_data_grid_columns'; import { indexPatternMock } from '../../__mocks__/index_pattern'; import { configMock } from '../../__mocks__/config'; import { indexPatternsMock } from '../../__mocks__/index_patterns'; import { AppState } from '../apps/context/services/context_state'; import { Capabilities } from '../../../../../core/types'; -describe('useDataGridColumns', () => { +describe('useColumns', () => { const defaultProps = { capabilities: { discover: { save: true } } as unknown as Capabilities, config: configMock, @@ -29,7 +29,7 @@ describe('useDataGridColumns', () => { test('should return valid result', () => { const { result } = renderHook(() => { - return useDataGridColumns(defaultProps); + return useColumns(defaultProps); }); expect(result.current.columns).toEqual(['Time', 'message']); @@ -41,7 +41,7 @@ describe('useDataGridColumns', () => { test('should skip _source column when useNewFieldsApi is set to true', () => { const { result } = renderHook(() => { - return useDataGridColumns({ + return useColumns({ ...defaultProps, state: { columns: ['Time', '_source'], @@ -55,7 +55,7 @@ describe('useDataGridColumns', () => { test('should return empty columns array', () => { const { result } = renderHook(() => { - return useDataGridColumns({ + return useColumns({ ...defaultProps, state: { columns: [], diff --git a/src/plugins/discover/public/application/helpers/use_data_grid_columns.ts b/src/plugins/discover/public/application/helpers/use_data_grid_columns.ts index 4dbe14017dc6e..888d67e2aaff3 100644 --- a/src/plugins/discover/public/application/helpers/use_data_grid_columns.ts +++ b/src/plugins/discover/public/application/helpers/use_data_grid_columns.ts @@ -20,7 +20,7 @@ import { } from '../apps/context/services/context_state'; import { getStateColumnActions } from '../apps/main/components/doc_table/actions/columns'; -interface UseDataGridColumnsProps { +interface UseColumnsProps { capabilities: Capabilities; config: IUiSettingsClient; indexPattern: IndexPattern; @@ -30,7 +30,7 @@ interface UseDataGridColumnsProps { state: DiscoverState | ContextState; } -export const useDataGridColumns = ({ +export const useColumns = ({ capabilities, config, indexPattern, @@ -38,7 +38,7 @@ export const useDataGridColumns = ({ setAppState, state, useNewFieldsApi, -}: UseDataGridColumnsProps) => { +}: UseColumnsProps) => { const { onAddColumn, onRemoveColumn, onSetColumns, onMoveColumn } = useMemo( () => getStateColumnActions({ From ee63830072a5d5a248b5514a32687e70da8ed2bd Mon Sep 17 00:00:00 2001 From: James Gowdy Date: Wed, 3 Nov 2021 09:03:02 +0000 Subject: [PATCH 31/41] [ML] Data view loading refactor (#116455) * [ML] Data view loading refactor * more renaming * more renaming * fixing tests * fixing jest test * small changes based on review Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/plugins/ml/public/application/app.tsx | 2 +- .../components/anomalies_table/links_menu.js | 18 ++--- .../full_time_range_selector.test.tsx | 4 +- .../full_time_range_selector.tsx | 8 +- .../{index_pattern.ts => data_view.ts} | 2 +- ...ndex_patterns.ts => data_view_contract.ts} | 2 +- .../ml/__mocks__/kibana_context_value.ts | 8 +- .../application/contexts/ml/ml_context.ts | 16 +--- .../contexts/ml/use_current_index_pattern.ts | 6 +- .../application/contexts/ml/use_ml_context.ts | 4 +- .../common/use_results_view_config.ts | 26 +++---- .../configuration_step_details.tsx | 4 +- .../configuration_step_form.tsx | 22 +++--- .../configuration_step/use_saved_search.ts | 4 +- .../runtime_mappings/runtime_mappings.tsx | 7 +- .../pages/analytics_creation/page.tsx | 6 +- .../source_selection.test.tsx | 35 ++++----- .../source_selection/source_selection.tsx | 6 +- .../use_create_analytics_form.ts | 6 +- .../pages/job_map/components/controls.tsx | 8 +- .../file_based/file_datavisualizer.tsx | 4 +- .../index_based/index_data_visualizer.tsx | 4 +- .../explorer/actions/job_selection.ts | 3 +- .../custom_url_editor/editor.test.tsx | 8 +- .../components/custom_url_editor/editor.tsx | 12 +-- .../components/custom_url_editor/utils.d.ts | 4 +- .../components/custom_url_editor/utils.js | 15 ++-- .../edit_job_flyout/edit_utils.d.ts | 4 +- .../components/edit_job_flyout/edit_utils.js | 17 +--- .../edit_job_flyout/tabs/custom_urls.tsx | 23 +++--- .../components/job_actions/management.js | 18 +---- .../jobs/jobs_list/components/utils.js | 9 +++ .../estimate_bucket_span.ts | 4 +- .../components/time_range_step/time_range.tsx | 2 +- .../preconfigured_job_redirect.ts | 33 ++++++-- .../jobs/new_job/pages/job_type/page.tsx | 18 ++--- .../jobs/new_job/pages/new_job/page.tsx | 4 +- .../new_job/pages/new_job/wizard_steps.tsx | 4 +- .../components/job_settings_form.tsx | 4 +- .../jobs/new_job/recognize/page.tsx | 12 +-- .../jobs/new_job/recognize/resolvers.ts | 4 +- .../public/application/routing/resolvers.ts | 8 +- .../ml/public/application/routing/router.tsx | 2 +- .../routing/routes/access_denied.tsx | 2 +- .../analytics_job_creation.tsx | 4 +- .../analytics_job_exploration.tsx | 8 +- .../analytics_jobs_list.tsx | 8 +- .../data_frame_analytics/analytics_map.tsx | 8 +- .../routes/datavisualizer/datavisualizer.tsx | 2 +- .../routes/datavisualizer/file_based.tsx | 6 +- .../routes/datavisualizer/index_based.tsx | 6 +- .../application/routing/routes/explorer.tsx | 16 ++-- .../application/routing/routes/jobs_list.tsx | 8 +- .../routes/new_job/index_or_search.tsx | 7 +- .../routing/routes/new_job/job_type.tsx | 8 +- .../routing/routes/new_job/recognize.tsx | 20 +++-- .../routing/routes/new_job/wizard.tsx | 20 +++-- .../application/routing/routes/overview.tsx | 2 +- .../routing/routes/settings/calendar_list.tsx | 2 +- .../routes/settings/calendar_new_edit.tsx | 2 +- .../routing/routes/settings/filter_list.tsx | 2 +- .../routes/settings/filter_list_new_edit.tsx | 2 +- .../routing/routes/settings/settings.tsx | 2 +- .../routing/routes/timeseriesexplorer.tsx | 16 ++-- .../routes/trained_models/models_list.tsx | 8 +- .../routes/trained_models/nodes_list.tsx | 8 +- .../application/routing/use_resolver.test.ts | 15 ++-- .../application/routing/use_resolver.ts | 35 +++++---- .../services/annotations_service.tsx | 5 +- .../services/field_format_service.ts | 72 +++++++++-------- .../load_new_job_capabilities.ts | 18 ++--- .../new_job_capabilities._service.test.ts | 8 +- .../new_job_capabilities_service.ts | 8 +- .../new_job_capabilities_service_analytics.ts | 8 +- .../timeseriesexplorer/timeseriesexplorer.js | 4 +- .../ml/public/application/util/index_utils.ts | 78 +++++++++---------- .../data_frame_analytics/index_patterns.ts | 8 +- .../ml/server/routes/data_frame_analytics.ts | 20 ++--- 78 files changed, 454 insertions(+), 402 deletions(-) rename x-pack/plugins/ml/public/application/contexts/ml/__mocks__/{index_pattern.ts => data_view.ts} (92%) rename x-pack/plugins/ml/public/application/contexts/ml/__mocks__/{index_patterns.ts => data_view_contract.ts} (92%) diff --git a/x-pack/plugins/ml/public/application/app.tsx b/x-pack/plugins/ml/public/application/app.tsx index 1df0a7afe475b..de212cbe4916e 100644 --- a/x-pack/plugins/ml/public/application/app.tsx +++ b/x-pack/plugins/ml/public/application/app.tsx @@ -70,7 +70,7 @@ const App: FC = ({ coreStart, deps, appMountParams }) => { const pageDeps = { history: appMountParams.history, - indexPatterns: deps.data.indexPatterns, + dataViewsContract: deps.data.dataViews, config: coreStart.uiSettings!, setBreadcrumbs: coreStart.chrome!.setBreadcrumbs, redirectToMlAccessDeniedPage, diff --git a/x-pack/plugins/ml/public/application/components/anomalies_table/links_menu.js b/x-pack/plugins/ml/public/application/components/anomalies_table/links_menu.js index 18fc10e69bc05..b0561893eb19f 100644 --- a/x-pack/plugins/ml/public/application/components/anomalies_table/links_menu.js +++ b/x-pack/plugins/ml/public/application/components/anomalies_table/links_menu.js @@ -28,7 +28,7 @@ import { ml } from '../../services/ml_api_service'; import { mlJobService } from '../../services/job_service'; import { getUrlForRecord, openCustomUrlWindow } from '../../util/custom_url_utils'; import { formatHumanReadableDateTimeSeconds } from '../../../../common/util/date_utils'; -import { getIndexPatternIdFromName } from '../../util/index_utils'; +import { getDataViewIdFromName } from '../../util/index_utils'; import { replaceStringTokens } from '../../util/string_utils'; import { ML_APP_LOCATOR, ML_PAGES } from '../../../../common/constants/locator'; /* @@ -258,18 +258,18 @@ class LinksMenuUI extends Component { }; const createAndOpenUrl = (index, categorizationFieldType) => { - // Find the ID of the data view with a title attribute which matches the - // index configured in the datafeed. If a Kibana data view has not been created - // for this index, then the user will see a warning message on the Discover tab advising - // them that no matching data view has been configured. - const indexPatternId = getIndexPatternIdFromName(index) || index; - // Get the definition of the category and use the terms or regex to view the // matching events in the Kibana Discover tab depending on whether the // categorization field is of mapping type text (preferred) or keyword. ml.results .getCategoryDefinition(record.job_id, categoryId) - .then((resp) => { + .then(async (resp) => { + // Find the ID of the data view with a title attribute which matches the + // index configured in the datafeed. If a Kibana data view has not been created + // for this index, then the user will see a warning message on the Discover tab advising + // them that no matching data view has been configured. + const dataViewId = (await getDataViewIdFromName(index)) ?? index; + let query = null; // Build query using categorization regex (if keyword type) or terms (if text type). // Check for terms or regex in case categoryId represents an anomaly from the absence of the @@ -313,7 +313,7 @@ class LinksMenuUI extends Component { }); const appStateProps = { - index: indexPatternId, + index: dataViewId, filters: [], }; if (query !== null) { diff --git a/x-pack/plugins/ml/public/application/components/full_time_range_selector/full_time_range_selector.test.tsx b/x-pack/plugins/ml/public/application/components/full_time_range_selector/full_time_range_selector.test.tsx index afa4acdd8583d..d04f8f7b648f5 100644 --- a/x-pack/plugins/ml/public/application/components/full_time_range_selector/full_time_range_selector.test.tsx +++ b/x-pack/plugins/ml/public/application/components/full_time_range_selector/full_time_range_selector.test.tsx @@ -21,7 +21,7 @@ jest.mock('./full_time_range_selector_service', () => ({ })); describe('FullTimeRangeSelector', () => { - const indexPattern = { + const dataView = { id: '0844fc70-5ab5-11e9-935e-836737467b0f', fields: [], title: 'test-data-view', @@ -34,7 +34,7 @@ describe('FullTimeRangeSelector', () => { }; const requiredProps = { - indexPattern, + dataView, query, }; diff --git a/x-pack/plugins/ml/public/application/components/full_time_range_selector/full_time_range_selector.tsx b/x-pack/plugins/ml/public/application/components/full_time_range_selector/full_time_range_selector.tsx index 4ab35a7c03f5a..846c79468b823 100644 --- a/x-pack/plugins/ml/public/application/components/full_time_range_selector/full_time_range_selector.tsx +++ b/x-pack/plugins/ml/public/application/components/full_time_range_selector/full_time_range_selector.tsx @@ -14,7 +14,7 @@ import type { DataView } from '../../../../../../../src/plugins/data_views/publi import { setFullTimeRange } from './full_time_range_selector_service'; interface Props { - indexPattern: DataView; + dataView: DataView; query: Query; disabled: boolean; callback?: (a: any) => void; @@ -22,7 +22,7 @@ interface Props { // Component for rendering a button which automatically sets the range of the time filter // to the time range of data in the index(es) mapped to the supplied Kibana index pattern or query. -export const FullTimeRangeSelector: FC = ({ indexPattern, query, disabled, callback }) => { +export const FullTimeRangeSelector: FC = ({ dataView, query, disabled, callback }) => { // wrapper around setFullTimeRange to allow for the calling of the optional callBack prop async function setRange(i: DataView, q: Query) { const fullTimeRange = await setFullTimeRange(i, q); @@ -33,14 +33,14 @@ export const FullTimeRangeSelector: FC = ({ indexPattern, query, disabled return ( setRange(indexPattern, query)} + onClick={() => setRange(dataView, query)} data-test-subj="mlButtonUseFullData" > diff --git a/x-pack/plugins/ml/public/application/contexts/ml/__mocks__/index_pattern.ts b/x-pack/plugins/ml/public/application/contexts/ml/__mocks__/data_view.ts similarity index 92% rename from x-pack/plugins/ml/public/application/contexts/ml/__mocks__/index_pattern.ts rename to x-pack/plugins/ml/public/application/contexts/ml/__mocks__/data_view.ts index 93f92002c4bfd..0d3c0993c880b 100644 --- a/x-pack/plugins/ml/public/application/contexts/ml/__mocks__/index_pattern.ts +++ b/x-pack/plugins/ml/public/application/contexts/ml/__mocks__/data_view.ts @@ -7,7 +7,7 @@ import type { DataView } from '../../../../../../../../src/plugins/data_views/public'; -export const indexPatternMock = { +export const dataViewMock = { id: 'the-index-pattern-id', title: 'the-index-pattern-title', fields: [], diff --git a/x-pack/plugins/ml/public/application/contexts/ml/__mocks__/index_patterns.ts b/x-pack/plugins/ml/public/application/contexts/ml/__mocks__/data_view_contract.ts similarity index 92% rename from x-pack/plugins/ml/public/application/contexts/ml/__mocks__/index_patterns.ts rename to x-pack/plugins/ml/public/application/contexts/ml/__mocks__/data_view_contract.ts index 571ce8ac3f423..61a466b6496ef 100644 --- a/x-pack/plugins/ml/public/application/contexts/ml/__mocks__/index_patterns.ts +++ b/x-pack/plugins/ml/public/application/contexts/ml/__mocks__/data_view_contract.ts @@ -7,7 +7,7 @@ import type { DataViewsContract } from '../../../../../../../../src/plugins/data_views/public'; -export const indexPatternsMock = new (class { +export const dataViewsContractMock = new (class { fieldFormats = []; config = {}; savedObjectsClient = {}; diff --git a/x-pack/plugins/ml/public/application/contexts/ml/__mocks__/kibana_context_value.ts b/x-pack/plugins/ml/public/application/contexts/ml/__mocks__/kibana_context_value.ts index 7045e08947f35..642bc4baee712 100644 --- a/x-pack/plugins/ml/public/application/contexts/ml/__mocks__/kibana_context_value.ts +++ b/x-pack/plugins/ml/public/application/contexts/ml/__mocks__/kibana_context_value.ts @@ -5,8 +5,8 @@ * 2.0. */ -import { indexPatternMock } from './index_pattern'; -import { indexPatternsMock } from './index_patterns'; +import { dataViewMock } from './data_view'; +import { dataViewsContractMock } from './data_view_contract'; import { kibanaConfigMock } from './kibana_config'; import { savedSearchMock } from './saved_search'; @@ -15,8 +15,8 @@ export const kibanaContextValueMock = { query: 'the-query-string', language: 'the-query-language', }, - currentIndexPattern: indexPatternMock, + currentDataView: dataViewMock, currentSavedSearch: savedSearchMock, - indexPatterns: indexPatternsMock, + dataViewsContract: dataViewsContractMock, kibanaConfig: kibanaConfigMock, }; diff --git a/x-pack/plugins/ml/public/application/contexts/ml/ml_context.ts b/x-pack/plugins/ml/public/application/contexts/ml/ml_context.ts index cd7059b5302f2..d707f56b10a34 100644 --- a/x-pack/plugins/ml/public/application/contexts/ml/ml_context.ts +++ b/x-pack/plugins/ml/public/application/contexts/ml/ml_context.ts @@ -12,25 +12,17 @@ import { MlServicesContext } from '../../app'; export interface MlContextValue { combinedQuery: any; - currentIndexPattern: DataView; // TODO this should be IndexPattern or null + currentDataView: DataView; // TODO this should be DataView or null currentSavedSearch: SavedSearchSavedObject | null; - indexPatterns: DataViewsContract; + dataViewsContract: DataViewsContract; kibanaConfig: any; // IUiSettingsClient; kibanaVersion: string; } export type SavedSearchQuery = object; -// This context provides dependencies which can be injected -// via angularjs only (like services, currentIndexPattern etc.). -// Because we cannot just import these dependencies, the default value -// for the context is just {} and of type `Partial` -// for the angularjs based dependencies. Therefore, the -// actual dependencies are set like we did previously with KibanaContext -// in the wrapping angularjs directive. In the custom hook we check if -// the dependencies are present with error reporting if they weren't -// added properly. That's why in tests, these custom hooks must not -// be mocked, instead ` needs +// In tests, these custom hooks must not be mocked, +// instead ` needs // to be used. This guarantees that we have both properly set up // TypeScript support and runtime checks for these dependencies. // Multiple custom hooks can be created to access subsets of diff --git a/x-pack/plugins/ml/public/application/contexts/ml/use_current_index_pattern.ts b/x-pack/plugins/ml/public/application/contexts/ml/use_current_index_pattern.ts index 4a8b77bdb2fde..69c36ee09d745 100644 --- a/x-pack/plugins/ml/public/application/contexts/ml/use_current_index_pattern.ts +++ b/x-pack/plugins/ml/public/application/contexts/ml/use_current_index_pattern.ts @@ -12,9 +12,9 @@ import { MlContext } from './ml_context'; export const useCurrentIndexPattern = () => { const context = useContext(MlContext); - if (context.currentIndexPattern === undefined) { - throw new Error('currentIndexPattern is undefined'); + if (context.currentDataView === undefined) { + throw new Error('currentDataView is undefined'); } - return context.currentIndexPattern; + return context.currentDataView; }; diff --git a/x-pack/plugins/ml/public/application/contexts/ml/use_ml_context.ts b/x-pack/plugins/ml/public/application/contexts/ml/use_ml_context.ts index 3d81c46684bef..1a07cb0338855 100644 --- a/x-pack/plugins/ml/public/application/contexts/ml/use_ml_context.ts +++ b/x-pack/plugins/ml/public/application/contexts/ml/use_ml_context.ts @@ -14,9 +14,9 @@ export const useMlContext = () => { if ( context.combinedQuery === undefined || - context.currentIndexPattern === undefined || + context.currentDataView === undefined || context.currentSavedSearch === undefined || - context.indexPatterns === undefined || + context.dataViewsContract === undefined || context.kibanaConfig === undefined ) { throw new Error('required attribute is undefined'); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/common/use_results_view_config.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/common/use_results_view_config.ts index ac638fe1f41a0..43dff4142023d 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/common/use_results_view_config.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/common/use_results_view_config.ts @@ -13,7 +13,7 @@ import type { DataView } from '../../../../../../../src/plugins/data_views/publi import { extractErrorMessage } from '../../../../common/util/errors'; -import { getIndexPatternIdFromName } from '../../util/index_utils'; +import { getDataViewIdFromName } from '../../util/index_utils'; import { ml } from '../../services/ml_api_service'; import { newJobCapsServiceAnalytics } from '../../services/new_job_capabilities/new_job_capabilities_service_analytics'; import { useMlContext } from '../../contexts/ml'; @@ -98,36 +98,36 @@ export const useResultsViewConfig = (jobId: string) => { const destIndex = Array.isArray(jobConfigUpdate.dest.index) ? jobConfigUpdate.dest.index[0] : jobConfigUpdate.dest.index; - const destIndexPatternId = getIndexPatternIdFromName(destIndex) || destIndex; - let indexP: DataView | undefined; + const destDataViewId = (await getDataViewIdFromName(destIndex)) ?? destIndex; + let dataView: DataView | undefined; try { - indexP = await mlContext.indexPatterns.get(destIndexPatternId); + dataView = await mlContext.dataViewsContract.get(destDataViewId); // Force refreshing the fields list here because a user directly coming // from the job creation wizard might land on the page without the // data view being fully initialized because it was created // before the analytics job populated the destination index. - await mlContext.indexPatterns.refreshFields(indexP); + await mlContext.dataViewsContract.refreshFields(dataView); } catch (e) { - indexP = undefined; + dataView = undefined; } - if (indexP === undefined) { + if (dataView === undefined) { setNeedsDestIndexPattern(true); const sourceIndex = jobConfigUpdate.source.index[0]; - const sourceIndexPatternId = getIndexPatternIdFromName(sourceIndex) || sourceIndex; + const sourceDataViewId = (await getDataViewIdFromName(sourceIndex)) ?? sourceIndex; try { - indexP = await mlContext.indexPatterns.get(sourceIndexPatternId); + dataView = await mlContext.dataViewsContract.get(sourceDataViewId); } catch (e) { - indexP = undefined; + dataView = undefined; } } - if (indexP !== undefined) { - await newJobCapsServiceAnalytics.initializeFromIndexPattern(indexP); + if (dataView !== undefined) { + await newJobCapsServiceAnalytics.initializeFromDataVIew(dataView); setJobConfig(analyticsConfigs.data_frame_analytics[0]); - setIndexPattern(indexP); + setIndexPattern(dataView); setIsInitialized(true); setIsLoadingJobConfig(false); } else { diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_details.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_details.tsx index 53270bf60ae8e..2c812226bfabb 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_details.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_details.tsx @@ -31,7 +31,7 @@ interface Props { export const ConfigurationStepDetails: FC = ({ setCurrentStep, state }) => { const mlContext = useMlContext(); - const { currentIndexPattern } = mlContext; + const { currentDataView } = mlContext; const { form, isJobCreated } = state; const { dependentVariable, includes, jobConfigQueryString, jobType, trainingPercent } = form; @@ -43,7 +43,7 @@ export const ConfigurationStepDetails: FC = ({ setCurrentStep, state }) = title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.sourceIndex', { defaultMessage: 'Source index', }), - description: currentIndexPattern.title || UNSET_CONFIG_ITEM, + description: currentDataView.title || UNSET_CONFIG_ITEM, }, { title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.Query', { diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx index 9bf751eec797f..21090ce671d02 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx @@ -101,7 +101,7 @@ export const ConfigurationStepForm: FC = ({ setCurrentStep, }) => { const mlContext = useMlContext(); - const { currentSavedSearch, currentIndexPattern } = mlContext; + const { currentSavedSearch, currentDataView } = mlContext; const { savedSearchQuery, savedSearchQueryStr } = useSavedSearch(); const [fieldOptionsFetchFail, setFieldOptionsFetchFail] = useState(false); @@ -167,7 +167,7 @@ export const ConfigurationStepForm: FC = ({ }; const indexData = useIndexData( - currentIndexPattern, + currentDataView, getIndexDataQuery(savedSearchQuery, jobConfigQuery), toastNotifications, runtimeMappings @@ -196,7 +196,7 @@ export const ConfigurationStepForm: FC = ({ setMaxDistinctValuesError(undefined); try { - if (currentIndexPattern !== undefined) { + if (currentDataView !== undefined) { const depVarOptions = []; let depVarUpdate = formState.dependentVariable; // Get fields and filter for supported types for job type @@ -334,7 +334,7 @@ export const ConfigurationStepForm: FC = ({ }, 300); useEffect(() => { - setFormState({ sourceIndex: currentIndexPattern.title }); + setFormState({ sourceIndex: currentDataView.title }); }, []); const indexPatternFieldsTableItems = useMemo(() => { @@ -356,7 +356,7 @@ export const ConfigurationStepForm: FC = ({ useEffect(() => { if (isJobTypeWithDepVar) { - const indexPatternRuntimeFields = getCombinedRuntimeMappings(currentIndexPattern); + const indexPatternRuntimeFields = getCombinedRuntimeMappings(currentDataView); let runtimeOptions; if (indexPatternRuntimeFields) { @@ -498,14 +498,14 @@ export const ConfigurationStepForm: FC = ({ fields: includesTableItems .filter((d) => d.feature_type === 'numerical' && d.is_included) .map((d) => d.name), - index: currentIndexPattern.title, + index: currentDataView.title, legendType: getScatterplotMatrixLegendType(jobType), searchQuery: jobConfigQuery, runtimeMappings, - indexPattern: currentIndexPattern, + indexPattern: currentDataView, }), [ - currentIndexPattern.title, + currentDataView.title, dependentVariable, includesTableItems, isJobTypeWithDepVar, @@ -548,7 +548,7 @@ export const ConfigurationStepForm: FC = ({ fullWidth > @@ -568,7 +568,7 @@ export const ConfigurationStepForm: FC = ({ {savedSearchQuery !== null ? currentSavedSearch?.attributes.title - : currentIndexPattern.title} + : currentDataView.title} } @@ -586,7 +586,7 @@ export const ConfigurationStepForm: FC = ({ helpText={ dependentVariableOptions.length === 0 && dependentVariableFetchFail === false && - currentIndexPattern && + currentDataView && i18n.translate( 'xpack.ml.dataframe.analytics.create.dependentVariableOptionsNoNumericalFields', { diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/use_saved_search.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/use_saved_search.ts index ad23c018afbbb..c4611a1740913 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/use_saved_search.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/use_saved_search.ts @@ -33,7 +33,7 @@ export function useSavedSearch() { const [savedSearchQueryStr, setSavedSearchQueryStr] = useState(undefined); const mlContext = useMlContext(); - const { currentSavedSearch, currentIndexPattern, kibanaConfig } = mlContext; + const { currentSavedSearch, currentDataView, kibanaConfig } = mlContext; const getQueryData = () => { let qry: estypes.QueryDslQueryContainer = {}; @@ -46,7 +46,7 @@ export function useSavedSearch() { if (queryLanguage === SEARCH_QUERY_LANGUAGE.KUERY) { const ast = fromKueryExpression(qryString); - qry = toElasticsearchQuery(ast, currentIndexPattern); + qry = toElasticsearchQuery(ast, currentDataView); } else { qry = luceneStringToDsl(qryString); decorateQuery(qry, kibanaConfig.get('query:queryString:options')); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/runtime_mappings/runtime_mappings.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/runtime_mappings/runtime_mappings.tsx index 95881fc328976..e24d8e89d464e 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/runtime_mappings/runtime_mappings.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/runtime_mappings/runtime_mappings.tsx @@ -95,7 +95,7 @@ export const RuntimeMappings: FC = ({ actions, state }) => { } = useXJsonMode(runtimeMappings || ''); const mlContext = useMlContext(); - const { currentIndexPattern } = mlContext; + const { currentDataView } = mlContext; const applyChanges = () => { const removeRuntimeMappings = advancedRuntimeMappingsConfig === ''; @@ -133,10 +133,7 @@ export const RuntimeMappings: FC = ({ actions, state }) => { }; useEffect(function getInitialRuntimeMappings() { - const combinedRuntimeMappings = getCombinedRuntimeMappings( - currentIndexPattern, - runtimeMappings - ); + const combinedRuntimeMappings = getCombinedRuntimeMappings(currentDataView, runtimeMappings); const prettySourceConfig = JSON.stringify(combinedRuntimeMappings, null, 2); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/page.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/page.tsx index 42a56d6327a60..8b136cfcd3637 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/page.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/page.tsx @@ -57,7 +57,7 @@ export const Page: FC = ({ jobId }) => { ]); const mlContext = useMlContext(); - const { currentIndexPattern } = mlContext; + const { currentDataView } = mlContext; const createAnalyticsForm = useCreateAnalyticsForm(); const { state } = createAnalyticsForm; @@ -69,7 +69,7 @@ export const Page: FC = ({ jobId }) => { useEffect(() => { initiateWizard(); - if (currentIndexPattern) { + if (currentDataView) { (async function () { if (jobId !== undefined) { const analyticsConfigs = await ml.dataFrameAnalytics.getDataFrameAnalytics(jobId, true); @@ -192,7 +192,7 @@ export const Page: FC = ({ jobId }) => {

diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/source_selection/source_selection.test.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/source_selection/source_selection.test.tsx index 48760f412725f..c9bf469bd1f40 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/source_selection/source_selection.test.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/source_selection/source_selection.test.tsx @@ -10,10 +10,7 @@ import { render, fireEvent, waitFor, screen } from '@testing-library/react'; import { __IntlProvider as IntlProvider } from '@kbn/i18n/react'; -import { - getIndexPatternAndSavedSearch, - IndexPatternAndSavedSearch, -} from '../../../../../util/index_utils'; +import { getDataViewAndSavedSearch, DataViewAndSavedSearch } from '../../../../../util/index_utils'; import { SourceSelection } from './source_selection'; @@ -83,27 +80,25 @@ jest.mock('../../../../../contexts/kibana', () => ({ jest.mock('../../../../../util/index_utils', () => { return { - getIndexPatternAndSavedSearch: jest.fn( - async (id: string): Promise => { - return { - indexPattern: { - // @ts-expect-error fields should not be empty - fields: [], - title: - id === 'the-remote-saved-search-id' - ? 'my_remote_cluster:index-pattern-title' - : 'index-pattern-title', - }, - savedSearch: null, - }; - } - ), + getDataViewAndSavedSearch: jest.fn(async (id: string): Promise => { + return { + dataView: { + // @ts-expect-error fields should not be empty + fields: [], + title: + id === 'the-remote-saved-search-id' + ? 'my_remote_cluster:index-pattern-title' + : 'index-pattern-title', + }, + savedSearch: null, + }; + }), isCcsIndexPattern: (a: string) => a.includes(':'), }; }); const mockOnClose = jest.fn(); -const mockGetDataViewAndSavedSearch = getIndexPatternAndSavedSearch as jest.Mock; +const mockGetDataViewAndSavedSearch = getDataViewAndSavedSearch as jest.Mock; describe('Data Frame Analytics: ', () => { afterEach(() => { diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/source_selection/source_selection.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/source_selection/source_selection.tsx index c2f17a45c1e06..e0922d7e12653 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/source_selection/source_selection.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/source_selection/source_selection.tsx @@ -25,7 +25,7 @@ import { useMlKibana, useNavigateToPath } from '../../../../../contexts/kibana'; import { getNestedProperty } from '../../../../../util/object_utils'; -import { getIndexPatternAndSavedSearch, isCcsIndexPattern } from '../../../../../util/index_utils'; +import { getDataViewAndSavedSearch, isCcsIndexPattern } from '../../../../../util/index_utils'; const fixedPageSize: number = 8; @@ -57,8 +57,8 @@ export const SourceSelection: FC = ({ onClose }) => { if (type === 'index-pattern') { dataViewName = getNestedProperty(savedObject, 'attributes.title'); } else if (type === 'search') { - const indexPatternAndSavedSearch = await getIndexPatternAndSavedSearch(id); - dataViewName = indexPatternAndSavedSearch.indexPattern?.title ?? ''; + const dataViewAndSavedSearch = await getDataViewAndSavedSearch(id); + dataViewName = dataViewAndSavedSearch.dataView?.title ?? ''; } if (isCcsIndexPattern(dataViewName)) { diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts index 18abc9be270be..88b0774e107e2 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.ts @@ -127,7 +127,7 @@ export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => { const dataViewName = destinationIndex; try { - await mlContext.indexPatterns.createAndSave( + await mlContext.dataViewsContract.createAndSave( { title: dataViewName, }, @@ -179,7 +179,7 @@ export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => { try { // Set the existing data view names. const indexPatternsMap: SourceIndexMap = {}; - const savedObjects = (await mlContext.indexPatterns.getCache()) || []; + const savedObjects = (await mlContext.dataViewsContract.getCache()) || []; savedObjects.forEach((obj) => { const title = obj?.attributes?.title; if (title !== undefined) { @@ -201,7 +201,7 @@ export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => { }; const initiateWizard = async () => { - await mlContext.indexPatterns.clearCache(); + await mlContext.dataViewsContract.clearCache(); await prepareFormValidation(); }; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/job_map/components/controls.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/job_map/components/controls.tsx index 1b961c05b2f30..b2879e2ffde54 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/job_map/components/controls.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/job_map/components/controls.tsx @@ -33,7 +33,7 @@ import { JOB_MAP_NODE_TYPES } from '../../../../../../common/constants/data_fram import { ML_PAGES } from '../../../../../../common/constants/locator'; import { checkPermission } from '../../../../capabilities/check_capabilities'; import { useMlLocator, useNotifications, useNavigateToPath } from '../../../../contexts/kibana'; -import { getIndexPatternIdFromName } from '../../../../util/index_utils'; +import { getDataViewIdFromName } from '../../../../util/index_utils'; import { useNavigateToWizardWithClonedJob } from '../../analytics_management/components/action_clone/clone_action_name'; import { useDeleteAction, @@ -112,12 +112,12 @@ export const Controls: FC = React.memo( const nodeType = selectedNode?.data('type'); const onCreateJobClick = useCallback(async () => { - const indexId = getIndexPatternIdFromName(nodeLabel); + const dataViewId = await getDataViewIdFromName(nodeLabel); - if (indexId) { + if (dataViewId !== null) { const path = await mlLocator.getUrl({ page: ML_PAGES.DATA_FRAME_ANALYTICS_CREATE_JOB, - pageState: { index: indexId }, + pageState: { index: dataViewId }, }); await navigateToPath(path); diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/file_datavisualizer.tsx b/x-pack/plugins/ml/public/application/datavisualizer/file_based/file_datavisualizer.tsx index 5fe519f25efb6..93c0291d4f9d4 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/file_datavisualizer.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/file_datavisualizer.tsx @@ -30,7 +30,7 @@ export const FileDataVisualizerPage: FC = () => { docLinks, dataVisualizer, data: { - indexPatterns: { get: getIndexPattern }, + dataViews: { get: getDataView }, }, }, } = useMlKibana(); @@ -60,7 +60,7 @@ export const FileDataVisualizerPage: FC = () => { }, canDisplay: async ({ indexPatternId }) => { try { - const { timeFieldName } = await getIndexPattern(indexPatternId); + const { timeFieldName } = await getDataView(indexPatternId); return ( isFullLicense() && timeFieldName !== undefined && diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx b/x-pack/plugins/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx index 0faf5b775d23e..7ae2712a2edd8 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/index_based/index_data_visualizer.tsx @@ -29,7 +29,7 @@ export const IndexDataVisualizerPage: FC = () => { docLinks, dataVisualizer, data: { - indexPatterns: { get: getIndexPattern }, + dataViews: { get: getDataView }, }, }, } = useMlKibana(); @@ -74,7 +74,7 @@ export const IndexDataVisualizerPage: FC = () => { }, canDisplay: async ({ indexPatternId }) => { try { - const { timeFieldName } = await getIndexPattern(indexPatternId); + const { timeFieldName } = await getDataView(indexPatternId); return ( isFullLicense() && timeFieldName !== undefined && diff --git a/x-pack/plugins/ml/public/application/explorer/actions/job_selection.ts b/x-pack/plugins/ml/public/application/explorer/actions/job_selection.ts index 46d47bb554705..ed244cbd894ba 100644 --- a/x-pack/plugins/ml/public/application/explorer/actions/job_selection.ts +++ b/x-pack/plugins/ml/public/application/explorer/actions/job_selection.ts @@ -17,8 +17,7 @@ import { createJobs } from '../explorer_utils'; export function jobSelectionActionCreator(selectedJobIds: string[]) { return from(mlFieldFormatService.populateFormats(selectedJobIds)).pipe( map((resp) => { - if (resp.err) { - console.log('Error populating field formats:', resp.err); // eslint-disable-line no-console + if (resp.error) { return null; } diff --git a/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/editor.test.tsx b/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/editor.test.tsx index 409f6a5911bb7..8fb4c43d77207 100644 --- a/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/editor.test.tsx +++ b/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/editor.test.tsx @@ -16,7 +16,7 @@ import React from 'react'; import { CustomUrlEditor } from './editor'; import { TIME_RANGE_TYPE, URL_TYPE } from './constants'; import { CustomUrlSettings } from './utils'; -import { DataView } from '../../../../../../../../src/plugins/data_views/common'; +import { DataViewListItem } from '../../../../../../../../src/plugins/data_views/common'; function prepareTest(customUrl: CustomUrlSettings, setEditCustomUrlFn: (url: UrlConfig) => void) { const savedCustomUrls = [ @@ -47,10 +47,10 @@ function prepareTest(customUrl: CustomUrlSettings, setEditCustomUrlFn: (url: Url { id: 'dash2', title: 'Dashboard 2' }, ]; - const indexPatterns = [ + const dataViewListItems = [ { id: 'pattern1', title: 'Data view 1' }, { id: 'pattern2', title: 'Data view 2' }, - ] as DataView[]; + ] as DataViewListItem[]; const queryEntityFieldNames = ['airline']; @@ -59,7 +59,7 @@ function prepareTest(customUrl: CustomUrlSettings, setEditCustomUrlFn: (url: Url setEditCustomUrl: setEditCustomUrlFn, savedCustomUrls, dashboards, - indexPatterns, + dataViewListItems, queryEntityFieldNames, }; diff --git a/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/editor.tsx b/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/editor.tsx index c769e2548370c..b140c950d52ef 100644 --- a/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/editor.tsx +++ b/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/editor.tsx @@ -29,7 +29,7 @@ import { isValidLabel } from '../../../util/custom_url_utils'; import { TIME_RANGE_TYPE, URL_TYPE } from './constants'; import { UrlConfig } from '../../../../../common/types/custom_urls'; -import { DataView } from '../../../../../../../../src/plugins/data_views/common'; +import { DataViewListItem } from '../../../../../../../../src/plugins/data_views/common'; function getLinkToOptions() { return [ @@ -59,7 +59,7 @@ interface CustomUrlEditorProps { setEditCustomUrl: (url: any) => void; savedCustomUrls: UrlConfig[]; dashboards: any[]; - indexPatterns: DataView[]; + dataViewListItems: DataViewListItem[]; queryEntityFieldNames: string[]; } @@ -71,7 +71,7 @@ export const CustomUrlEditor: FC = ({ setEditCustomUrl, savedCustomUrls, dashboards, - indexPatterns, + dataViewListItems, queryEntityFieldNames, }) => { if (customUrl === undefined) { @@ -164,8 +164,8 @@ export const CustomUrlEditor: FC = ({ return { value: dashboard.id, text: dashboard.title }; }); - const indexPatternOptions = indexPatterns.map((indexPattern) => { - return { value: indexPattern.id, text: indexPattern.title }; + const dataViewOptions = dataViewListItems.map(({ id, title }) => { + return { value: id, text: title }; }); const entityOptions = queryEntityFieldNames.map((fieldName) => ({ label: fieldName })); @@ -274,7 +274,7 @@ export const CustomUrlEditor: FC = ({ display="rowCompressed" > 0) { urlType = URL_TYPE.KIBANA_DASHBOARD; kibanaSettings.dashboardId = dashboards[0].id; - } else if (indexPatterns !== undefined && indexPatterns.length > 0) { + } else if (dataViews !== undefined && dataViews.length > 0) { urlType = URL_TYPE.KIBANA_DISCOVER; } @@ -40,15 +39,15 @@ export function getNewCustomUrlDefaults(job, dashboards, indexPatterns) { // which matches the indices configured in the job datafeed. const datafeedConfig = job.datafeed_config; if ( - indexPatterns !== undefined && - indexPatterns.length > 0 && + dataViews !== undefined && + dataViews.length > 0 && datafeedConfig !== undefined && datafeedConfig.indices !== undefined && datafeedConfig.indices.length > 0 ) { - const defaultIndexPatternId = - getIndexPatternIdFromName(datafeedConfig.indices.join()) ?? indexPatterns[0].id; - kibanaSettings.discoverIndexPatternId = defaultIndexPatternId; + const indicesName = datafeedConfig.indices.join(); + const defaultDataViewId = dataViews.find((dv) => dv.title === indicesName)?.id; + kibanaSettings.discoverIndexPatternId = defaultDataViewId; } return { diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_utils.d.ts b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_utils.d.ts index 32e99e3e433e0..9e9f112cee25d 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_utils.d.ts +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_utils.d.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { DataView } from 'src/plugins/data_views/common'; +import type { DataViewListItem } from 'src/plugins/data_views/common'; export function loadSavedDashboards(maxNumber: number): Promise; -export function loadIndexPatterns(maxNumber: number): Promise; +export function loadDataViewListItems(): Promise; diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_utils.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_utils.js index f32800d6b7b7f..3a94cf6c673f3 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_utils.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_utils.js @@ -102,20 +102,9 @@ export function loadSavedDashboards(maxNumber) { }); } -export function loadIndexPatterns(maxNumber) { - // Loads the list of Kibana data views, as used in editing custom URLs. - return new Promise((resolve, reject) => { - const dataViewsContract = getDataViews(); - dataViewsContract - .find('*', maxNumber) - .then((dataViews) => { - const sortedDataViews = dataViews.sort((a, b) => a.title.localeCompare(b.title)); - resolve(sortedDataViews); - }) - .catch((resp) => { - reject(resp); - }); - }); +export async function loadDataViewListItems() { + const dataViewsContract = getDataViews(); + return (await dataViewsContract.getIdsWithTitle()).sort((a, b) => a.title.localeCompare(b.title)); } function extractDescription(job, newJobData) { diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/custom_urls.tsx b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/custom_urls.tsx index 45f059690c3a9..035836373adf6 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/custom_urls.tsx +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/custom_urls.tsx @@ -33,15 +33,14 @@ import { CustomUrlSettings, } from '../../../../components/custom_url_editor/utils'; import { withKibana } from '../../../../../../../../../../src/plugins/kibana_react/public'; -import { loadSavedDashboards, loadIndexPatterns } from '../edit_utils'; +import { loadSavedDashboards, loadDataViewListItems } from '../edit_utils'; import { openCustomUrlWindow } from '../../../../../util/custom_url_utils'; import { Job } from '../../../../../../../common/types/anomaly_detection_jobs'; import { UrlConfig } from '../../../../../../../common/types/custom_urls'; -import { DataView } from '../../../../../../../../../../src/plugins/data_views/common'; +import { DataViewListItem } from '../../../../../../../../../../src/plugins/data_views/common'; import { MlKibanaReactContextValue } from '../../../../../contexts/kibana'; const MAX_NUMBER_DASHBOARDS = 1000; -const MAX_NUMBER_INDEX_PATTERNS = 1000; interface CustomUrlsProps { job: Job; @@ -54,7 +53,7 @@ interface CustomUrlsProps { interface CustomUrlsState { customUrls: UrlConfig[]; dashboards: any[]; - indexPatterns: DataView[]; + dataViewListItems: DataViewListItem[]; queryEntityFieldNames: string[]; editorOpen: boolean; editorSettings?: CustomUrlSettings; @@ -67,7 +66,7 @@ class CustomUrlsUI extends Component { this.state = { customUrls: [], dashboards: [], - indexPatterns: [], + dataViewListItems: [], queryEntityFieldNames: [], editorOpen: false, }; @@ -100,9 +99,9 @@ class CustomUrlsUI extends Component { ); }); - loadIndexPatterns(MAX_NUMBER_INDEX_PATTERNS) - .then((indexPatterns) => { - this.setState({ indexPatterns }); + loadDataViewListItems() + .then((dataViewListItems) => { + this.setState({ dataViewListItems }); }) .catch((resp) => { // eslint-disable-next-line no-console @@ -121,11 +120,11 @@ class CustomUrlsUI extends Component { editNewCustomUrl = () => { // Opens the editor for configuring a new custom URL. this.setState((prevState) => { - const { dashboards, indexPatterns } = prevState; + const { dashboards, dataViewListItems } = prevState; return { editorOpen: true, - editorSettings: getNewCustomUrlDefaults(this.props.job, dashboards, indexPatterns), + editorSettings: getNewCustomUrlDefaults(this.props.job, dashboards, dataViewListItems), }; }); }; @@ -209,7 +208,7 @@ class CustomUrlsUI extends Component { editorOpen, editorSettings, dashboards, - indexPatterns, + dataViewListItems, queryEntityFieldNames, } = this.state; @@ -220,7 +219,7 @@ class CustomUrlsUI extends Component { setEditCustomUrl={this.setEditCustomUrl} savedCustomUrls={customUrls} dashboards={dashboards} - indexPatterns={indexPatterns} + dataViewListItems={dataViewListItems} queryEntityFieldNames={queryEntityFieldNames} /> ); diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_actions/management.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_actions/management.js index 1124052f86367..64bc7f4a517c2 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_actions/management.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_actions/management.js @@ -7,7 +7,6 @@ import { checkPermission } from '../../../../capabilities/check_capabilities'; import { mlNodesAvailable } from '../../../../ml_nodes_check/check_ml_nodes'; -import { getIndexPatternNames } from '../../../../util/index_utils'; import { JOB_ACTION } from '../../../../../../common/constants/job_actions'; import { @@ -19,7 +18,6 @@ import { isClosable, isResettable, } from '../utils'; -import { getToastNotifications } from '../../../../util/dependency_cache'; import { i18n } from '@kbn/i18n'; export function actionsMenuContent( @@ -136,21 +134,7 @@ export function actionsMenuContent( return isJobBlocked(item) === false && canCreateJob; }, onClick: (item) => { - const indexPatternNames = getIndexPatternNames(); - const indexPatternTitle = item.datafeedIndices.join(','); - const jobIndicesAvailable = indexPatternNames.includes(indexPatternTitle); - - if (!jobIndicesAvailable) { - getToastNotifications().addDanger( - i18n.translate('xpack.ml.jobsList.managementActions.noSourceDataViewForClone', { - defaultMessage: - 'Unable to clone the anomaly detection job {jobId}. No data view exists for index {indexPatternTitle}.', - values: { jobId: item.id, indexPatternTitle }, - }) - ); - } else { - cloneJob(item.id); - } + cloneJob(item.id); closeMenu(true); }, 'data-test-subj': 'mlActionButtonCloneJob', diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.js index 414d920237e8c..fe09ed45f1274 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.js @@ -16,6 +16,7 @@ import { import { getToastNotifications } from '../../../util/dependency_cache'; import { ml } from '../../../services/ml_api_service'; import { stringMatch } from '../../../util/string_utils'; +import { getDataViewNames } from '../../../util/index_utils'; import { JOB_STATE, DATAFEED_STATE } from '../../../../../common/constants/states'; import { JOB_ACTION } from '../../../../../common/constants/job_actions'; import { parseInterval } from '../../../../../common/util/parse_interval'; @@ -217,6 +218,14 @@ export async function cloneJob(jobId) { loadJobForCloning(jobId), loadFullJob(jobId, false), ]); + + const dataViewNames = await getDataViewNames(); + const jobIndicesAvailable = dataViewNames.includes(datafeed.indices.join(',')); + + if (jobIndicesAvailable === false) { + return; + } + if (cloneableJob !== undefined && originalJob?.custom_settings?.created_by !== undefined) { // if the job is from a wizards, i.e. contains a created_by property // use tempJobCloningObjects to temporarily store the job diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/bucket_span_estimator/estimate_bucket_span.ts b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/bucket_span_estimator/estimate_bucket_span.ts index 88ed17cba0003..eb9c4bf755707 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/bucket_span_estimator/estimate_bucket_span.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/bucket_span_estimator/estimate_bucket_span.ts @@ -38,10 +38,10 @@ export function useEstimateBucketSpan() { end: jobCreator.end, }, fields: jobCreator.fields.map((f) => (f.id === EVENT_RATE_FIELD_ID ? null : f.id)), - index: mlContext.currentIndexPattern.title, + index: mlContext.currentDataView.title, query: mlContext.combinedQuery, splitField: undefined, - timeField: mlContext.currentIndexPattern.timeFieldName, + timeField: mlContext.currentDataView.timeFieldName, runtimeMappings: jobCreator.runtimeMappings ?? undefined, indicesOptions: jobCreator.datafeedConfig.indices_options, }; diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/time_range_step/time_range.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/time_range_step/time_range.tsx index 4c13130ae4ce3..9924bf1674f79 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/time_range_step/time_range.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/time_range_step/time_range.tsx @@ -104,7 +104,7 @@ export const TimeRangeStep: FC = ({ setCurrentStep, isCurrentStep }) { + if (dataViewsContract === null) { + throw new Error('Data views are not initialized!'); + } - return `jobs/new_job/${page}?index=${indexPatternId}&_g=()`; + const [dv] = await dataViewsContract?.find(datafeed.indices.join(',')); + if (!dv) { + return null; + } + return dv.id ?? dv.title; } diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/job_type/page.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/job_type/page.tsx index 15ca0b82695ff..5e62158977280 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/job_type/page.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/job_type/page.tsx @@ -45,9 +45,9 @@ export const Page: FC = () => { const [recognizerResultsCount, setRecognizerResultsCount] = useState(0); - const { currentSavedSearch, currentIndexPattern } = mlContext; + const { currentSavedSearch, currentDataView } = mlContext; - const isTimeBasedIndex = timeBasedIndexCheck(currentIndexPattern); + const isTimeBasedIndex = timeBasedIndexCheck(currentDataView); const indexWarningTitle = !isTimeBasedIndex && isSavedSearchSavedObject(currentSavedSearch) ? i18n.translate( @@ -57,13 +57,13 @@ export const Page: FC = () => { '{savedSearchTitle} uses data view {dataViewName} which is not time based', values: { savedSearchTitle: currentSavedSearch.attributes.title as string, - dataViewName: currentIndexPattern.title, + dataViewName: currentDataView.title, }, } ) : i18n.translate('xpack.ml.newJob.wizard.jobType.dataViewNotTimeBasedMessage', { defaultMessage: 'Data view {dataViewName} is not time based', - values: { dataViewName: currentIndexPattern.title }, + values: { dataViewName: currentDataView.title }, }); const pageTitleLabel = isSavedSearchSavedObject(currentSavedSearch) @@ -73,7 +73,7 @@ export const Page: FC = () => { }) : i18n.translate('xpack.ml.newJob.wizard.jobType.dataViewPageTitleLabel', { defaultMessage: 'data view {dataViewName}', - values: { dataViewName: currentIndexPattern.title }, + values: { dataViewName: currentDataView.title }, }); const recognizerResults = { @@ -85,13 +85,13 @@ export const Page: FC = () => { const getUrlParams = () => { return !isSavedSearchSavedObject(currentSavedSearch) - ? `?index=${currentIndexPattern.id}` + ? `?index=${currentDataView.id}` : `?savedSearchId=${currentSavedSearch.id}`; }; const addSelectionToRecentlyAccessed = async () => { const title = !isSavedSearchSavedObject(currentSavedSearch) - ? currentIndexPattern.title + ? currentDataView.title : (currentSavedSearch.attributes.title as string); const mlLocator = share.url.locators.get(ML_APP_LOCATOR)!; @@ -101,7 +101,7 @@ export const Page: FC = () => { pageState: { ...(currentSavedSearch?.id ? { savedSearchId: currentSavedSearch.id } - : { index: currentIndexPattern.id }), + : { index: currentDataView.id }), }, }, { absolute: true } @@ -270,7 +270,7 @@ export const Page: FC = () => { diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/new_job/page.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/new_job/page.tsx index f708ea83f1d0d..52e3c55afc15a 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/new_job/page.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/new_job/page.tsx @@ -55,7 +55,7 @@ export interface PageProps { export const Page: FC = ({ existingJobsAndGroups, jobType }) => { const mlContext = useMlContext(); const jobCreator = jobCreatorFactory(jobType)( - mlContext.currentIndexPattern, + mlContext.currentDataView, mlContext.currentSavedSearch, mlContext.combinedQuery ); @@ -184,7 +184,7 @@ export const Page: FC = ({ existingJobsAndGroups, jobType }) => { chartInterval.setMaxBars(MAX_BARS); chartInterval.setInterval('auto'); - const chartLoader = new ChartLoader(mlContext.currentIndexPattern, mlContext.combinedQuery); + const chartLoader = new ChartLoader(mlContext.currentDataView, mlContext.combinedQuery); const jobValidator = new JobValidator(jobCreator); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/new_job/wizard_steps.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/new_job/wizard_steps.tsx index c24fb2521ea58..b2e5a4ee1c22f 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/new_job/wizard_steps.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/new_job/wizard_steps.tsx @@ -40,10 +40,10 @@ export const WizardSteps: FC = ({ currentStep, setCurrentStep }) => { defaultMessage: 'New job from saved search {title}', values: { title: mlContext.currentSavedSearch.attributes.title as string }, }); - } else if (mlContext.currentIndexPattern.id !== undefined) { + } else if (mlContext.currentDataView.id !== undefined) { return i18n.translate('xpack.ml.newJob.wizard.stepComponentWrapper.summaryTitleDataView', { defaultMessage: 'New job from data view {dataViewName}', - values: { dataViewName: mlContext.currentIndexPattern.title }, + values: { dataViewName: mlContext.currentDataView.title }, }); } return ''; diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/recognize/components/job_settings_form.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/recognize/components/job_settings_form.tsx index 8dc0ca0ddbdc4..c996f50ea5018 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/recognize/components/job_settings_form.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/recognize/components/job_settings_form.tsx @@ -53,7 +53,7 @@ export const JobSettingsForm: FC = ({ jobs, }) => { const { from, to } = getTimeFilterRange(); - const { currentIndexPattern: indexPattern } = useMlContext(); + const { currentDataView: dataView } = useMlContext(); const jobPrefixValidator = composeValidators( patternValidator(/^([a-z0-9]+[a-z0-9\-_]*)?$/), @@ -181,7 +181,7 @@ export const JobSettingsForm: FC = ({ } checked={formState.useFullIndexData} diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/recognize/page.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/recognize/page.tsx index dbf9bce64b114..7603855d47e5c 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/recognize/page.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/recognize/page.tsx @@ -92,7 +92,7 @@ export const Page: FC = ({ moduleId, existingGroupIds }) => { const { currentSavedSearch: savedSearch, - currentIndexPattern: indexPattern, + currentDataView: dataView, combinedQuery, } = useMlContext(); const pageTitle = @@ -103,7 +103,7 @@ export const Page: FC = ({ moduleId, existingGroupIds }) => { }) : i18n.translate('xpack.ml.newJob.recognize.dataViewPageTitle', { defaultMessage: 'data view {dataViewName}', - values: { dataViewName: indexPattern.title }, + values: { dataViewName: dataView.title }, }); const displayQueryWarning = savedSearch !== null; const tempQuery = savedSearch === null ? undefined : combinedQuery; @@ -135,10 +135,10 @@ export const Page: FC = ({ moduleId, existingGroupIds }) => { timeRange: TimeRange ): Promise => { if (useFullIndexData) { - const runtimeMappings = indexPattern.getComputedFields().runtimeFields as RuntimeMappings; + const runtimeMappings = dataView.getComputedFields().runtimeFields as RuntimeMappings; const { start, end } = await ml.getTimeFieldRange({ - index: indexPattern.title, - timeFieldName: indexPattern.timeFieldName, + index: dataView.title, + timeFieldName: dataView.timeFieldName, query: combinedQuery, ...(isPopulatedObject(runtimeMappings) ? { runtimeMappings } : {}), }); @@ -178,7 +178,7 @@ export const Page: FC = ({ moduleId, existingGroupIds }) => { moduleId, prefix: resultJobPrefix, query: tempQuery, - indexPatternName: indexPattern.title, + indexPatternName: dataView.title, useDedicatedIndex, startDatafeed: startDatafeedAfterSave, ...(jobOverridesPayload !== null ? { jobOverrides: jobOverridesPayload } : {}), diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/recognize/resolvers.ts b/x-pack/plugins/ml/public/application/jobs/new_job/recognize/resolvers.ts index b4e581d46bcc3..6dca394c0227d 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/recognize/resolvers.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/recognize/resolvers.ts @@ -19,7 +19,7 @@ import { CreateLinkWithUserDefaults } from '../../../components/custom_hooks/use */ export function checkViewOrCreateJobs( moduleId: string, - indexPatternId: string, + dataViewId: string, createLinkWithUserDefaults: CreateLinkWithUserDefaults, navigateToPath: NavigateToPath ): Promise { @@ -36,7 +36,7 @@ export function checkViewOrCreateJobs( await navigateToPath(url); reject(); } else { - await navigateToPath(`/jobs/new_job/recognize?id=${moduleId}&index=${indexPatternId}`); + await navigateToPath(`/jobs/new_job/recognize?id=${moduleId}&index=${dataViewId}`); reject(); } }) diff --git a/x-pack/plugins/ml/public/application/routing/resolvers.ts b/x-pack/plugins/ml/public/application/routing/resolvers.ts index 3479005809efb..d95dfc9d2784f 100644 --- a/x-pack/plugins/ml/public/application/routing/resolvers.ts +++ b/x-pack/plugins/ml/public/application/routing/resolvers.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { loadIndexPatterns, loadSavedSearches } from '../util/index_utils'; +import { cacheDataViewsContract, loadSavedSearches } from '../util/index_utils'; import { checkFullLicense } from '../license'; import { checkGetJobsCapabilitiesResolver } from '../capabilities/check_capabilities'; import { getMlNodeCount } from '../ml_nodes_check/check_ml_nodes'; @@ -21,18 +21,18 @@ export interface ResolverResults { } interface BasicResolverDependencies { - indexPatterns: DataViewsContract; + dataViewsContract: DataViewsContract; redirectToMlAccessDeniedPage: () => Promise; } export const basicResolvers = ({ - indexPatterns, + dataViewsContract, redirectToMlAccessDeniedPage, }: BasicResolverDependencies): Resolvers => ({ checkFullLicense, getMlNodeCount, loadMlServerInfo, - loadIndexPatterns: () => loadIndexPatterns(indexPatterns), + cacheDataViewsContract: () => cacheDataViewsContract(dataViewsContract), checkGetJobsCapabilities: () => checkGetJobsCapabilitiesResolver(redirectToMlAccessDeniedPage), loadSavedSearches, }); diff --git a/x-pack/plugins/ml/public/application/routing/router.tsx b/x-pack/plugins/ml/public/application/routing/router.tsx index 847dcc1ae1107..5f220d86cd6ad 100644 --- a/x-pack/plugins/ml/public/application/routing/router.tsx +++ b/x-pack/plugins/ml/public/application/routing/router.tsx @@ -43,7 +43,7 @@ export interface PageProps { interface PageDependencies { config: IUiSettingsClient; history: AppMountParameters['history']; - indexPatterns: DataViewsContract; + dataViewsContract: DataViewsContract; setBreadcrumbs: ChromeStart['setBreadcrumbs']; redirectToMlAccessDeniedPage: () => Promise; } diff --git a/x-pack/plugins/ml/public/application/routing/routes/access_denied.tsx b/x-pack/plugins/ml/public/application/routing/routes/access_denied.tsx index afb36b89732f1..10b2d1438c32b 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/access_denied.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/access_denied.tsx @@ -27,7 +27,7 @@ export const accessDeniedRouteFactory = (): MlRoute => ({ }); const PageWrapper: FC = ({ deps }) => { - const { context } = useResolver(undefined, undefined, deps.config, {}); + const { context } = useResolver(undefined, undefined, deps.config, deps.dataViewsContract, {}); return ( diff --git a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_job_creation.tsx b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_job_creation.tsx index ab57c264683ca..e550eaa338b08 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_job_creation.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_job_creation.tsx @@ -44,10 +44,10 @@ const PageWrapper: FC = ({ location, deps }) => { sort: false, }); - const { context } = useResolver(index, savedSearchId, deps.config, { + const { context } = useResolver(index, savedSearchId, deps.config, deps.dataViewsContract, { ...basicResolvers(deps), analyticsFields: () => - loadNewJobCapabilities(index, savedSearchId, deps.indexPatterns, DATA_FRAME_ANALYTICS), + loadNewJobCapabilities(index, savedSearchId, deps.dataViewsContract, DATA_FRAME_ANALYTICS), }); return ( diff --git a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_job_exploration.tsx b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_job_exploration.tsx index 98603f69f382f..49a756fd12ced 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_job_exploration.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_job_exploration.tsx @@ -39,7 +39,13 @@ export const analyticsJobExplorationRouteFactory = ( }); const PageWrapper: FC = ({ location, deps }) => { - const { context } = useResolver(undefined, undefined, deps.config, basicResolvers(deps)); + const { context } = useResolver( + undefined, + undefined, + deps.config, + deps.dataViewsContract, + basicResolvers(deps) + ); const [globalState] = useUrlState('_g'); diff --git a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_jobs_list.tsx b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_jobs_list.tsx index d6a70105a71b4..2e55a9e85fb6e 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_jobs_list.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_jobs_list.tsx @@ -35,7 +35,13 @@ export const analyticsJobsListRouteFactory = ( }); const PageWrapper: FC = ({ location, deps }) => { - const { context } = useResolver(undefined, undefined, deps.config, basicResolvers(deps)); + const { context } = useResolver( + undefined, + undefined, + deps.config, + deps.dataViewsContract, + basicResolvers(deps) + ); return ( diff --git a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_map.tsx b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_map.tsx index 77e7d3b3740ef..29bf616041624 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_map.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_map.tsx @@ -35,7 +35,13 @@ export const analyticsMapRouteFactory = ( }); const PageWrapper: FC = ({ deps }) => { - const { context } = useResolver(undefined, undefined, deps.config, basicResolvers(deps)); + const { context } = useResolver( + undefined, + undefined, + deps.config, + deps.dataViewsContract, + basicResolvers(deps) + ); return ( diff --git a/x-pack/plugins/ml/public/application/routing/routes/datavisualizer/datavisualizer.tsx b/x-pack/plugins/ml/public/application/routing/routes/datavisualizer/datavisualizer.tsx index b2466c7466747..4db8d9a0c72f3 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/datavisualizer/datavisualizer.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/datavisualizer/datavisualizer.tsx @@ -32,7 +32,7 @@ export const selectorRouteFactory = ( const PageWrapper: FC = ({ location, deps }) => { const { redirectToMlAccessDeniedPage } = deps; - const { context } = useResolver(undefined, undefined, deps.config, { + const { context } = useResolver(undefined, undefined, deps.config, deps.dataViewsContract, { checkBasicLicense, checkFindFileStructurePrivilege: () => checkFindFileStructurePrivilegeResolver(redirectToMlAccessDeniedPage), diff --git a/x-pack/plugins/ml/public/application/routing/routes/datavisualizer/file_based.tsx b/x-pack/plugins/ml/public/application/routing/routes/datavisualizer/file_based.tsx index 5b16bf8352b27..9e48940a29b56 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/datavisualizer/file_based.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/datavisualizer/file_based.tsx @@ -16,7 +16,7 @@ import { FileDataVisualizerPage } from '../../../datavisualizer/file_based'; import { checkBasicLicense } from '../../../license'; import { checkFindFileStructurePrivilegeResolver } from '../../../capabilities/check_capabilities'; -import { loadIndexPatterns } from '../../../util/index_utils'; +import { cacheDataViewsContract } from '../../../util/index_utils'; import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs'; @@ -41,9 +41,9 @@ export const fileBasedRouteFactory = ( const PageWrapper: FC = ({ deps }) => { const { redirectToMlAccessDeniedPage } = deps; - const { context } = useResolver(undefined, undefined, deps.config, { + const { context } = useResolver(undefined, undefined, deps.config, deps.dataViewsContract, { checkBasicLicense, - loadIndexPatterns: () => loadIndexPatterns(deps.indexPatterns), + cacheDataViewsContract: () => cacheDataViewsContract(deps.dataViewsContract), checkFindFileStructurePrivilege: () => checkFindFileStructurePrivilegeResolver(redirectToMlAccessDeniedPage), }); diff --git a/x-pack/plugins/ml/public/application/routing/routes/datavisualizer/index_based.tsx b/x-pack/plugins/ml/public/application/routing/routes/datavisualizer/index_based.tsx index 04543a28ab3e6..dde1a3a768553 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/datavisualizer/index_based.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/datavisualizer/index_based.tsx @@ -18,7 +18,7 @@ import { IndexDataVisualizerPage as Page } from '../../../datavisualizer/index_b import { checkBasicLicense } from '../../../license'; import { checkGetJobsCapabilitiesResolver } from '../../../capabilities/check_capabilities'; -import { loadIndexPatterns } from '../../../util/index_utils'; +import { cacheDataViewsContract } from '../../../util/index_utils'; import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs'; export const indexBasedRouteFactory = ( @@ -43,9 +43,9 @@ const PageWrapper: FC = ({ location, deps }) => { const { redirectToMlAccessDeniedPage } = deps; const { index, savedSearchId }: Record = parse(location.search, { sort: false }); - const { context } = useResolver(index, savedSearchId, deps.config, { + const { context } = useResolver(index, savedSearchId, deps.config, deps.dataViewsContract, { checkBasicLicense, - loadIndexPatterns: () => loadIndexPatterns(deps.indexPatterns), + cacheDataViewsContract: () => cacheDataViewsContract(deps.dataViewsContract), checkGetJobsCapabilities: () => checkGetJobsCapabilitiesResolver(redirectToMlAccessDeniedPage), }); diff --git a/x-pack/plugins/ml/public/application/routing/routes/explorer.tsx b/x-pack/plugins/ml/public/application/routing/routes/explorer.tsx index 49e7857eee082..e50dc301f970b 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/explorer.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/explorer.tsx @@ -57,11 +57,17 @@ export const explorerRouteFactory = ( }); const PageWrapper: FC = ({ deps }) => { - const { context, results } = useResolver(undefined, undefined, deps.config, { - ...basicResolvers(deps), - jobs: mlJobService.loadJobsWrapper, - jobsWithTimeRange: () => ml.jobs.jobsWithTimerange(getDateFormatTz()), - }); + const { context, results } = useResolver( + undefined, + undefined, + deps.config, + deps.dataViewsContract, + { + ...basicResolvers(deps), + jobs: mlJobService.loadJobsWrapper, + jobsWithTimeRange: () => ml.jobs.jobsWithTimerange(getDateFormatTz()), + } + ); const annotationUpdatesService = useMemo(() => new AnnotationUpdatesService(), []); return ( diff --git a/x-pack/plugins/ml/public/application/routing/routes/jobs_list.tsx b/x-pack/plugins/ml/public/application/routing/routes/jobs_list.tsx index ecaa0a9e42e3f..52cdfc5c42b2d 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/jobs_list.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/jobs_list.tsx @@ -39,7 +39,13 @@ export const jobListRouteFactory = (navigateToPath: NavigateToPath, basePath: st }); const PageWrapper: FC = ({ deps }) => { - const { context } = useResolver(undefined, undefined, deps.config, basicResolvers(deps)); + const { context } = useResolver( + undefined, + undefined, + deps.config, + deps.dataViewsContract, + basicResolvers(deps) + ); const timefilter = useTimefilter({ timeRangeSelector: false, autoRefreshSelector: true }); const [globalState, setGlobalState] = useUrlState('_g'); diff --git a/x-pack/plugins/ml/public/application/routing/routes/new_job/index_or_search.tsx b/x-pack/plugins/ml/public/application/routing/routes/new_job/index_or_search.tsx index 53057cb16c132..e92bac32debcb 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/new_job/index_or_search.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/new_job/index_or_search.tsx @@ -17,7 +17,7 @@ import { basicResolvers } from '../../resolvers'; import { Page, preConfiguredJobRedirect } from '../../../jobs/new_job/pages/index_or_search'; import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs'; import { checkBasicLicense } from '../../../license'; -import { loadIndexPatterns } from '../../../util/index_utils'; +import { cacheDataViewsContract } from '../../../util/index_utils'; import { checkGetJobsCapabilitiesResolver } from '../../../capabilities/check_capabilities'; enum MODE { @@ -86,11 +86,11 @@ const PageWrapper: FC = ({ nextStepPath, deps, mode }) = const newJobResolvers = { ...basicResolvers(deps), preConfiguredJobRedirect: () => - preConfiguredJobRedirect(deps.indexPatterns, basePath.get(), navigateToUrl), + preConfiguredJobRedirect(deps.dataViewsContract, basePath.get(), navigateToUrl), }; const dataVizResolvers = { checkBasicLicense, - loadIndexPatterns: () => loadIndexPatterns(deps.indexPatterns), + cacheDataViewsContract: () => cacheDataViewsContract(deps.dataViewsContract), checkGetJobsCapabilities: () => checkGetJobsCapabilitiesResolver(redirectToMlAccessDeniedPage), }; @@ -98,6 +98,7 @@ const PageWrapper: FC = ({ nextStepPath, deps, mode }) = undefined, undefined, deps.config, + deps.dataViewsContract, mode === MODE.NEW_JOB ? newJobResolvers : dataVizResolvers ); return ( diff --git a/x-pack/plugins/ml/public/application/routing/routes/new_job/job_type.tsx b/x-pack/plugins/ml/public/application/routing/routes/new_job/job_type.tsx index 235a91ea73791..cdd2b890f086e 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/new_job/job_type.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/new_job/job_type.tsx @@ -35,7 +35,13 @@ export const jobTypeRouteFactory = (navigateToPath: NavigateToPath, basePath: st const PageWrapper: FC = ({ location, deps }) => { const { index, savedSearchId }: Record = parse(location.search, { sort: false }); - const { context } = useResolver(index, savedSearchId, deps.config, basicResolvers(deps)); + const { context } = useResolver( + index, + savedSearchId, + deps.config, + deps.dataViewsContract, + basicResolvers(deps) + ); return ( diff --git a/x-pack/plugins/ml/public/application/routing/routes/new_job/recognize.tsx b/x-pack/plugins/ml/public/application/routing/routes/new_job/recognize.tsx index 125d7cbb0f84a..7e7da6c79a858 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/new_job/recognize.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/new_job/recognize.tsx @@ -49,10 +49,16 @@ export const checkViewOrCreateRouteFactory = (): MlRoute => ({ const PageWrapper: FC = ({ location, deps }) => { const { id, index, savedSearchId }: Record = parse(location.search, { sort: false }); - const { context, results } = useResolver(index, savedSearchId, deps.config, { - ...basicResolvers(deps), - existingJobsAndGroups: mlJobService.getJobAndGroupIds, - }); + const { context, results } = useResolver( + index, + savedSearchId, + deps.config, + deps.dataViewsContract, + { + ...basicResolvers(deps), + existingJobsAndGroups: mlJobService.getJobAndGroupIds, + } + ); return ( @@ -62,7 +68,7 @@ const PageWrapper: FC = ({ location, deps }) => { }; const CheckViewOrCreateWrapper: FC = ({ location, deps }) => { - const { id: moduleId, index: indexPatternId }: Record = parse(location.search, { + const { id: moduleId, index: dataViewId }: Record = parse(location.search, { sort: false, }); const { createLinkWithUserDefaults } = useCreateADLinks(); @@ -70,9 +76,9 @@ const CheckViewOrCreateWrapper: FC = ({ location, deps }) => { const navigateToPath = useNavigateToPath(); // the single resolver checkViewOrCreateJobs redirects only. so will always reject - useResolver(undefined, undefined, deps.config, { + useResolver(undefined, undefined, deps.config, deps.dataViewsContract, { checkViewOrCreateJobs: () => - checkViewOrCreateJobs(moduleId, indexPatternId, createLinkWithUserDefaults, navigateToPath), + checkViewOrCreateJobs(moduleId, dataViewId, createLinkWithUserDefaults, navigateToPath), }); return null; }; diff --git a/x-pack/plugins/ml/public/application/routing/routes/new_job/wizard.tsx b/x-pack/plugins/ml/public/application/routing/routes/new_job/wizard.tsx index d95fbcaba9f67..7953d15a55b1e 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/new_job/wizard.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/new_job/wizard.tsx @@ -153,13 +153,19 @@ const PageWrapper: FC = ({ location, jobType, deps }) => { ); const { index, savedSearchId }: Record = parse(location.search, { sort: false }); - const { context, results } = useResolver(index, savedSearchId, deps.config, { - ...basicResolvers(deps), - privileges: () => checkCreateJobsCapabilitiesResolver(redirectToJobsManagementPage), - jobCaps: () => - loadNewJobCapabilities(index, savedSearchId, deps.indexPatterns, ANOMALY_DETECTOR), - existingJobsAndGroups: mlJobService.getJobAndGroupIds, - }); + const { context, results } = useResolver( + index, + savedSearchId, + deps.config, + deps.dataViewsContract, + { + ...basicResolvers(deps), + privileges: () => checkCreateJobsCapabilitiesResolver(redirectToJobsManagementPage), + jobCaps: () => + loadNewJobCapabilities(index, savedSearchId, deps.dataViewsContract, ANOMALY_DETECTOR), + existingJobsAndGroups: mlJobService.getJobAndGroupIds, + } + ); return ( diff --git a/x-pack/plugins/ml/public/application/routing/routes/overview.tsx b/x-pack/plugins/ml/public/application/routing/routes/overview.tsx index 9f21609db35ca..dd3fc70a6425e 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/overview.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/overview.tsx @@ -44,7 +44,7 @@ export const overviewRouteFactory = ( const PageWrapper: FC = ({ deps }) => { const { redirectToMlAccessDeniedPage } = deps; - const { context } = useResolver(undefined, undefined, deps.config, { + const { context } = useResolver(undefined, undefined, deps.config, deps.dataViewsContract, { checkFullLicense, checkGetJobsCapabilities: () => checkGetJobsCapabilitiesResolver(redirectToMlAccessDeniedPage), getMlNodeCount, diff --git a/x-pack/plugins/ml/public/application/routing/routes/settings/calendar_list.tsx b/x-pack/plugins/ml/public/application/routing/routes/settings/calendar_list.tsx index d8a59ebed9de6..08949d5d514b2 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/settings/calendar_list.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/settings/calendar_list.tsx @@ -38,7 +38,7 @@ export const calendarListRouteFactory = ( const PageWrapper: FC = ({ deps }) => { const { redirectToMlAccessDeniedPage } = deps; - const { context } = useResolver(undefined, undefined, deps.config, { + const { context } = useResolver(undefined, undefined, deps.config, deps.dataViewsContract, { checkFullLicense, checkGetJobsCapabilities: () => checkGetJobsCapabilitiesResolver(redirectToMlAccessDeniedPage), getMlNodeCount, diff --git a/x-pack/plugins/ml/public/application/routing/routes/settings/calendar_new_edit.tsx b/x-pack/plugins/ml/public/application/routing/routes/settings/calendar_new_edit.tsx index 18c4d43b564d7..05f9f92479f45 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/settings/calendar_new_edit.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/settings/calendar_new_edit.tsx @@ -83,7 +83,7 @@ const PageWrapper: FC = ({ location, mode, deps }) => { ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE ); - const { context } = useResolver(undefined, undefined, deps.config, { + const { context } = useResolver(undefined, undefined, deps.config, deps.dataViewsContract, { checkFullLicense, checkGetJobsCapabilities: () => checkGetJobsCapabilitiesResolver(redirectToMlAccessDeniedPage), checkMlNodesAvailable: () => checkMlNodesAvailable(redirectToJobsManagementPage), diff --git a/x-pack/plugins/ml/public/application/routing/routes/settings/filter_list.tsx b/x-pack/plugins/ml/public/application/routing/routes/settings/filter_list.tsx index d28569ff8aaa4..8e956ecf59d5d 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/settings/filter_list.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/settings/filter_list.tsx @@ -39,7 +39,7 @@ export const filterListRouteFactory = ( const PageWrapper: FC = ({ deps }) => { const { redirectToMlAccessDeniedPage } = deps; - const { context } = useResolver(undefined, undefined, deps.config, { + const { context } = useResolver(undefined, undefined, deps.config, deps.dataViewsContract, { checkFullLicense, checkGetJobsCapabilities: () => checkGetJobsCapabilitiesResolver(redirectToMlAccessDeniedPage), getMlNodeCount, diff --git a/x-pack/plugins/ml/public/application/routing/routes/settings/filter_list_new_edit.tsx b/x-pack/plugins/ml/public/application/routing/routes/settings/filter_list_new_edit.tsx index e12a33b48439c..cf32063210624 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/settings/filter_list_new_edit.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/settings/filter_list_new_edit.tsx @@ -85,7 +85,7 @@ const PageWrapper: FC = ({ location, mode, deps }) => { ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE ); - const { context } = useResolver(undefined, undefined, deps.config, { + const { context } = useResolver(undefined, undefined, deps.config, deps.dataViewsContract, { checkFullLicense, checkGetJobsCapabilities: () => checkGetJobsCapabilitiesResolver(redirectToMlAccessDeniedPage), checkMlNodesAvailable: () => checkMlNodesAvailable(redirectToJobsManagementPage), diff --git a/x-pack/plugins/ml/public/application/routing/routes/settings/settings.tsx b/x-pack/plugins/ml/public/application/routing/routes/settings/settings.tsx index 51b8e8e837633..5e0e8dbc801e7 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/settings/settings.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/settings/settings.tsx @@ -37,7 +37,7 @@ export const settingsRouteFactory = ( const PageWrapper: FC = ({ deps }) => { const { redirectToMlAccessDeniedPage } = deps; - const { context } = useResolver(undefined, undefined, deps.config, { + const { context } = useResolver(undefined, undefined, deps.config, deps.dataViewsContract, { checkFullLicense, checkGetJobsCapabilities: () => checkGetJobsCapabilitiesResolver(redirectToMlAccessDeniedPage), getMlNodeCount, diff --git a/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx b/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx index 8c704ef4240a0..91697b8a89bd7 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx @@ -65,11 +65,17 @@ export const timeSeriesExplorerRouteFactory = ( }); const PageWrapper: FC = ({ deps }) => { - const { context, results } = useResolver(undefined, undefined, deps.config, { - ...basicResolvers(deps), - jobs: mlJobService.loadJobsWrapper, - jobsWithTimeRange: () => ml.jobs.jobsWithTimerange(getDateFormatTz()), - }); + const { context, results } = useResolver( + undefined, + undefined, + deps.config, + deps.dataViewsContract, + { + ...basicResolvers(deps), + jobs: mlJobService.loadJobsWrapper, + jobsWithTimeRange: () => ml.jobs.jobsWithTimerange(getDateFormatTz()), + } + ); const annotationUpdatesService = useMemo(() => new AnnotationUpdatesService(), []); return ( diff --git a/x-pack/plugins/ml/public/application/routing/routes/trained_models/models_list.tsx b/x-pack/plugins/ml/public/application/routing/routes/trained_models/models_list.tsx index 9367a58372484..646df84aee5e7 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/trained_models/models_list.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/trained_models/models_list.tsx @@ -35,7 +35,13 @@ export const modelsListRouteFactory = ( }); const PageWrapper: FC = ({ location, deps }) => { - const { context } = useResolver(undefined, undefined, deps.config, basicResolvers(deps)); + const { context } = useResolver( + undefined, + undefined, + deps.config, + deps.dataViewsContract, + basicResolvers(deps) + ); return ( diff --git a/x-pack/plugins/ml/public/application/routing/routes/trained_models/nodes_list.tsx b/x-pack/plugins/ml/public/application/routing/routes/trained_models/nodes_list.tsx index bd88527af1a8d..c0bd22e657bb0 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/trained_models/nodes_list.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/trained_models/nodes_list.tsx @@ -35,7 +35,13 @@ export const nodesListRouteFactory = ( }); const PageWrapper: FC = ({ location, deps }) => { - const { context } = useResolver(undefined, undefined, deps.config, basicResolvers(deps)); + const { context } = useResolver( + undefined, + undefined, + deps.config, + deps.dataViewsContract, + basicResolvers(deps) + ); return ( diff --git a/x-pack/plugins/ml/public/application/routing/use_resolver.test.ts b/x-pack/plugins/ml/public/application/routing/use_resolver.test.ts index 4c5c8c7b21ddd..98deb8df7b60d 100644 --- a/x-pack/plugins/ml/public/application/routing/use_resolver.test.ts +++ b/x-pack/plugins/ml/public/application/routing/use_resolver.test.ts @@ -11,6 +11,7 @@ import { IUiSettingsClient } from 'kibana/public'; import { useCreateAndNavigateToMlLink } from '../contexts/kibana/use_create_url'; import { useNotifications } from '../contexts/kibana'; +import type { DataViewsContract } from '../../../../../../src/plugins/data_views/public'; import { useResolver } from './use_resolver'; @@ -44,9 +45,9 @@ describe('useResolver', () => { jest.useRealTimers(); }); - it('should accept undefined as indexPatternId and savedSearchId.', async () => { + it('should accept undefined as dataViewId and savedSearchId.', async () => { const { result, waitForNextUpdate } = renderHook(() => - useResolver(undefined, undefined, {} as IUiSettingsClient, {}) + useResolver(undefined, undefined, {} as IUiSettingsClient, {} as DataViewsContract, {}) ); await act(async () => { @@ -64,9 +65,9 @@ describe('useResolver', () => { ], }, }, - currentIndexPattern: null, + currentDataView: null, currentSavedSearch: null, - indexPatterns: null, + dataViewsContract: {}, kibanaConfig: {}, }, results: {}, @@ -75,8 +76,10 @@ describe('useResolver', () => { expect(redirectToJobsManagementPage).toHaveBeenCalledTimes(0); }); - it('should add an error toast and redirect if indexPatternId is an empty string.', async () => { - const { result } = renderHook(() => useResolver('', undefined, {} as IUiSettingsClient, {})); + it('should add an error toast and redirect if dataViewId is an empty string.', async () => { + const { result } = renderHook(() => + useResolver('', undefined, {} as IUiSettingsClient, {} as DataViewsContract, {}) + ); await act(async () => {}); diff --git a/x-pack/plugins/ml/public/application/routing/use_resolver.ts b/x-pack/plugins/ml/public/application/routing/use_resolver.ts index 62450106d0dfb..59d64f412cc80 100644 --- a/x-pack/plugins/ml/public/application/routing/use_resolver.ts +++ b/x-pack/plugins/ml/public/application/routing/use_resolver.ts @@ -9,10 +9,9 @@ import { useEffect, useState } from 'react'; import { IUiSettingsClient } from 'kibana/public'; import { i18n } from '@kbn/i18n'; import { - getIndexPatternById, - getIndexPatternsContract, - getIndexPatternAndSavedSearch, - IndexPatternAndSavedSearch, + getDataViewById, + getDataViewAndSavedSearch, + DataViewAndSavedSearch, } from '../util/index_utils'; import { createSearchItems } from '../jobs/new_job/utils/new_job_utils'; import { ResolverResults, Resolvers } from './resolvers'; @@ -20,19 +19,21 @@ import { MlContextValue } from '../contexts/ml'; import { useNotifications } from '../contexts/kibana'; import { useCreateAndNavigateToMlLink } from '../contexts/kibana/use_create_url'; import { ML_PAGES } from '../../../common/constants/locator'; +import type { DataViewsContract } from '../../../../../../src/plugins/data_views/public'; /** * Hook to resolve route specific requirements - * @param indexPatternId optional Kibana index pattern id, used for wizards + * @param dataViewId optional Kibana data view id, used for wizards * @param savedSearchId optional Kibana saved search id, used for wizards * @param config Kibana UI Settings * @param resolvers an array of resolvers to be executed for the route * @return { context, results } returns the ML context and resolver results */ export const useResolver = ( - indexPatternId: string | undefined, + dataViewId: string | undefined, savedSearchId: string | undefined, config: IUiSettingsClient, + dataViewsContract: DataViewsContract, resolvers: Resolvers ): { context: MlContextValue; results: ResolverResults } => { const notifications = useNotifications(); @@ -63,38 +64,38 @@ export const useResolver = ( } try { - if (indexPatternId === '') { + if (dataViewId === '') { throw new Error( i18n.translate('xpack.ml.useResolver.errorIndexPatternIdEmptyString', { - defaultMessage: 'indexPatternId must not be empty string.', + defaultMessage: 'dataViewId must not be empty string.', }) ); } - let indexPatternAndSavedSearch: IndexPatternAndSavedSearch = { + let dataViewAndSavedSearch: DataViewAndSavedSearch = { savedSearch: null, - indexPattern: null, + dataView: null, }; if (savedSearchId !== undefined) { - indexPatternAndSavedSearch = await getIndexPatternAndSavedSearch(savedSearchId); - } else if (indexPatternId !== undefined) { - indexPatternAndSavedSearch.indexPattern = await getIndexPatternById(indexPatternId); + dataViewAndSavedSearch = await getDataViewAndSavedSearch(savedSearchId); + } else if (dataViewId !== undefined) { + dataViewAndSavedSearch.dataView = await getDataViewById(dataViewId); } - const { savedSearch, indexPattern } = indexPatternAndSavedSearch; + const { savedSearch, dataView } = dataViewAndSavedSearch; const { combinedQuery } = createSearchItems( config, - indexPattern !== null ? indexPattern : undefined, + dataView !== null ? dataView : undefined, savedSearch ); setContext({ combinedQuery, - currentIndexPattern: indexPattern, + currentDataView: dataView, currentSavedSearch: savedSearch, - indexPatterns: getIndexPatternsContract(), + dataViewsContract, kibanaConfig: config, }); } catch (error) { diff --git a/x-pack/plugins/ml/public/application/services/annotations_service.tsx b/x-pack/plugins/ml/public/application/services/annotations_service.tsx index a84cd7ee000f2..f3d50115f0206 100644 --- a/x-pack/plugins/ml/public/application/services/annotations_service.tsx +++ b/x-pack/plugins/ml/public/application/services/annotations_service.tsx @@ -17,7 +17,7 @@ export type AnnotationState = Annotation | null; /* This observable offers a way to share state between components that don't have a direct parent -> * -> child relationship. - It's also useful in mixed angularjs/React environments. + It's also useful in mixed React environments. For example, we want to trigger the flyout for editing annotations from both the timeseries_chart and the annotations_table. Since we don't want two flyout instances, @@ -75,8 +75,7 @@ export const annotation$ = new BehaviorSubject(null); /* This observable provides a way to trigger a reload of annotations based on a given event. - Instead of passing around callbacks or deeply nested props, it can be imported for both - angularjs controllers/directives and React components. + Instead of passing around callbacks or deeply nested props, it can be imported in React components. */ export const annotationsRefresh$ = new BehaviorSubject(Date.now()); export const annotationsRefreshed = () => annotationsRefresh$.next(Date.now()); diff --git a/x-pack/plugins/ml/public/application/services/field_format_service.ts b/x-pack/plugins/ml/public/application/services/field_format_service.ts index d95975156a99d..ddcb447430a45 100644 --- a/x-pack/plugins/ml/public/application/services/field_format_service.ts +++ b/x-pack/plugins/ml/public/application/services/field_format_service.ts @@ -6,7 +6,7 @@ */ import { mlFunctionToESAggregation } from '../../../common/util/job_utils'; -import { getIndexPatternById, getIndexPatternIdFromName } from '../util/index_utils'; +import { getDataViewById, getDataViewIdFromName } from '../util/index_utils'; import { mlJobService } from './job_service'; import type { DataView } from '../../../../../../src/plugins/data_views/public'; @@ -25,35 +25,40 @@ class FieldFormatService { // configured in the datafeed of each job. // Builds a map of Kibana FieldFormats (plugins/data/common/field_formats) // against detector index by job ID. - populateFormats(jobIds: string[]): Promise { - return new Promise((resolve, reject) => { - // Populate a map of data view IDs against job ID, by finding the ID of the data - // view with a title attribute which matches the indices configured in the datafeed. - // If a Kibana data view has not been created - // for this index, then no custom field formatting will occur. - jobIds.forEach((jobId) => { - const jobObj = mlJobService.getJob(jobId); - const datafeedIndices = jobObj.datafeed_config.indices; - const id = getIndexPatternIdFromName(datafeedIndices.length ? datafeedIndices[0] : ''); - if (id !== null) { - this.indexPatternIdsByJob[jobId] = id; - } - }); + async populateFormats(jobIds: string[]): Promise { + // Populate a map of data view IDs against job ID, by finding the ID of the data + // view with a title attribute which matches the indices configured in the datafeed. + // If a Kibana data view has not been created + // for this index, then no custom field formatting will occur. + ( + await Promise.all( + jobIds.map(async (jobId) => { + const jobObj = mlJobService.getJob(jobId); + return { + jobId, + dataViewId: await getDataViewIdFromName(jobObj.datafeed_config.indices.join(',')), + }; + }) + ) + ).forEach(({ jobId, dataViewId }) => { + if (dataViewId !== null) { + this.indexPatternIdsByJob[jobId] = dataViewId; + } + }); - const promises = jobIds.map((jobId) => Promise.all([this.getFormatsForJob(jobId)])); + const promises = jobIds.map((jobId) => Promise.all([this.getFormatsForJob(jobId)])); - Promise.all(promises) - .then((fmtsByJobByDetector) => { - fmtsByJobByDetector.forEach((formatsByDetector, i) => { - this.formatsByJob[jobIds[i]] = formatsByDetector[0]; - }); + try { + const fmtsByJobByDetector = await Promise.all(promises); + fmtsByJobByDetector.forEach((formatsByDetector, i) => { + this.formatsByJob[jobIds[i]] = formatsByDetector[0]; + }); - resolve(this.formatsByJob); - }) - .catch((err) => { - reject({ formats: {}, err }); - }); - }); + return this.formatsByJob; + } catch (error) { + console.log('Error populating field formats:', error); // eslint-disable-line no-console + return { formats: {}, error }; + } } // Return the FieldFormat to use for formatting values from @@ -87,13 +92,13 @@ class FieldFormatService { const detectors = jobObj.analysis_config.detectors || []; const formatsByDetector: any[] = []; - const indexPatternId = this.indexPatternIdsByJob[jobId]; - if (indexPatternId !== undefined) { + const dataViewId = this.indexPatternIdsByJob[jobId]; + if (dataViewId !== undefined) { // Load the full data view configuration to obtain the formats of each field. - getIndexPatternById(indexPatternId) - .then((indexPatternData) => { + getDataViewById(dataViewId) + .then((dataView) => { // Store the FieldFormat for each job by detector_index. - const fieldList = indexPatternData.fields; + const fieldList = dataView.fields; detectors.forEach((dtr) => { const esAgg = mlFunctionToESAggregation(dtr.function); // distinct_count detectors should fall back to the default @@ -101,8 +106,7 @@ class FieldFormatService { if (dtr.field_name !== undefined && esAgg !== 'cardinality') { const field = fieldList.getByName(dtr.field_name); if (field !== undefined) { - formatsByDetector[dtr.detector_index!] = - indexPatternData.getFormatterForField(field); + formatsByDetector[dtr.detector_index!] = dataView.getFormatterForField(field); } } }); diff --git a/x-pack/plugins/ml/public/application/services/new_job_capabilities/load_new_job_capabilities.ts b/x-pack/plugins/ml/public/application/services/new_job_capabilities/load_new_job_capabilities.ts index b4d3c47364a3d..8c05d5a418219 100644 --- a/x-pack/plugins/ml/public/application/services/new_job_capabilities/load_new_job_capabilities.ts +++ b/x-pack/plugins/ml/public/application/services/new_job_capabilities/load_new_job_capabilities.ts @@ -6,7 +6,7 @@ */ import { DataView, DataViewsContract } from '../../../../../../../src/plugins/data_views/public'; -import { getIndexPatternAndSavedSearch } from '../../util/index_utils'; +import { getDataViewAndSavedSearch } from '../../util/index_utils'; import { JobType } from '../../../../common/types/saved_objects'; import { newJobCapsServiceAnalytics } from '../new_job_capabilities/new_job_capabilities_service_analytics'; import { newJobCapsService } from '../new_job_capabilities/new_job_capabilities_service'; @@ -17,9 +17,9 @@ export const DATA_FRAME_ANALYTICS = 'data-frame-analytics'; // called in the routing resolve block to initialize the NewJobCapabilites // service for the corresponding job type with the currently selected data view export function loadNewJobCapabilities( - indexPatternId: string, + dataViewId: string, savedSearchId: string, - indexPatterns: DataViewsContract, + dataViewContract: DataViewsContract, jobType: JobType ) { return new Promise(async (resolve, reject) => { @@ -27,24 +27,24 @@ export function loadNewJobCapabilities( const serviceToUse = jobType === ANOMALY_DETECTOR ? newJobCapsService : newJobCapsServiceAnalytics; - if (indexPatternId !== undefined) { + if (dataViewId !== undefined) { // index pattern is being used - const indexPattern: DataView = await indexPatterns.get(indexPatternId); - await serviceToUse.initializeFromIndexPattern(indexPattern); + const dataView: DataView = await dataViewContract.get(dataViewId); + await serviceToUse.initializeFromDataVIew(dataView); resolve(serviceToUse.newJobCaps); } else if (savedSearchId !== undefined) { // saved search is being used // load the data view from the saved search - const { indexPattern } = await getIndexPatternAndSavedSearch(savedSearchId); + const { dataView } = await getDataViewAndSavedSearch(savedSearchId); - if (indexPattern === null) { + if (dataView === null) { // eslint-disable-next-line no-console console.error('Cannot retrieve data view from saved search'); reject(); return; } - await serviceToUse.initializeFromIndexPattern(indexPattern); + await serviceToUse.initializeFromDataVIew(dataView); resolve(serviceToUse.newJobCaps); } else { reject(); diff --git a/x-pack/plugins/ml/public/application/services/new_job_capabilities/new_job_capabilities._service.test.ts b/x-pack/plugins/ml/public/application/services/new_job_capabilities/new_job_capabilities._service.test.ts index 49c8b08007d52..373de76d59457 100644 --- a/x-pack/plugins/ml/public/application/services/new_job_capabilities/new_job_capabilities._service.test.ts +++ b/x-pack/plugins/ml/public/application/services/new_job_capabilities/new_job_capabilities._service.test.ts @@ -20,7 +20,7 @@ jest.mock('../ml_api_service', () => ({ }, })); -const indexPattern = { +const dataView = { id: 'cloudwatch-*', title: 'cloudwatch-*', } as unknown as DataView; @@ -28,7 +28,7 @@ const indexPattern = { describe('new_job_capabilities_service', () => { describe('cloudwatch newJobCaps()', () => { it('can construct job caps objects from endpoint json', async () => { - await newJobCapsService.initializeFromIndexPattern(indexPattern); + await newJobCapsService.initializeFromDataVIew(dataView); const { fields, aggs } = await newJobCapsService.newJobCaps; const networkOutField = fields.find((f) => f.id === 'NetworkOut') || { aggs: [] }; @@ -47,7 +47,7 @@ describe('new_job_capabilities_service', () => { }); it('job caps including text fields', async () => { - await newJobCapsService.initializeFromIndexPattern(indexPattern, true, false); + await newJobCapsService.initializeFromDataVIew(dataView, true, false); const { fields, aggs } = await newJobCapsService.newJobCaps; expect(fields).toHaveLength(13); // one more field @@ -55,7 +55,7 @@ describe('new_job_capabilities_service', () => { }); it('job caps excluding event rate', async () => { - await newJobCapsService.initializeFromIndexPattern(indexPattern, false, true); + await newJobCapsService.initializeFromDataVIew(dataView, false, true); const { fields, aggs } = await newJobCapsService.newJobCaps; expect(fields).toHaveLength(11); // one less field diff --git a/x-pack/plugins/ml/public/application/services/new_job_capabilities/new_job_capabilities_service.ts b/x-pack/plugins/ml/public/application/services/new_job_capabilities/new_job_capabilities_service.ts index 45dc71ed6a6b9..210c409e8e281 100644 --- a/x-pack/plugins/ml/public/application/services/new_job_capabilities/new_job_capabilities_service.ts +++ b/x-pack/plugins/ml/public/application/services/new_job_capabilities/new_job_capabilities_service.ts @@ -36,8 +36,8 @@ class NewJobCapsService extends NewJobCapabilitiesServiceBase { return filterCategoryFields(this._fields); } - public async initializeFromIndexPattern( - indexPattern: DataView, + public async initializeFromDataVIew( + dataView: DataView, includeEventRateField = true, removeTextFields = true ) { @@ -45,8 +45,8 @@ class NewJobCapsService extends NewJobCapabilitiesServiceBase { this._includeEventRateField = includeEventRateField; this._removeTextFields = removeTextFields; - const resp = await ml.jobs.newJobCaps(indexPattern.title, indexPattern.type === 'rollup'); - const { fields: allFields, aggs } = createObjects(resp, indexPattern.title); + const resp = await ml.jobs.newJobCaps(dataView.title, dataView.type === 'rollup'); + const { fields: allFields, aggs } = createObjects(resp, dataView.title); if (this._includeEventRateField === true) { addEventRateField(aggs, allFields); diff --git a/x-pack/plugins/ml/public/application/services/new_job_capabilities/new_job_capabilities_service_analytics.ts b/x-pack/plugins/ml/public/application/services/new_job_capabilities/new_job_capabilities_service_analytics.ts index f8f9ae6b2b0a3..2786c14127ecd 100644 --- a/x-pack/plugins/ml/public/application/services/new_job_capabilities/new_job_capabilities_service_analytics.ts +++ b/x-pack/plugins/ml/public/application/services/new_job_capabilities/new_job_capabilities_service_analytics.ts @@ -43,14 +43,14 @@ export function removeNestedFieldChildren(resp: NewJobCapsResponse, indexPattern } class NewJobCapsServiceAnalytics extends NewJobCapabilitiesServiceBase { - public async initializeFromIndexPattern(indexPattern: DataView) { + public async initializeFromDataVIew(dataView: DataView) { try { const resp: NewJobCapsResponse = await ml.dataFrameAnalytics.newJobCapsAnalytics( - indexPattern.title, - indexPattern.type === 'rollup' + dataView.title, + dataView.type === 'rollup' ); - const allFields = removeNestedFieldChildren(resp, indexPattern.title); + const allFields = removeNestedFieldChildren(resp, dataView.title); const { fieldsPreferringKeyword } = processTextAndKeywordFields(allFields); diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js index e4d7fc457de0b..7d90d748218e9 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js @@ -720,9 +720,7 @@ export class TimeSeriesExplorer extends React.Component { appStateHandler(APP_STATE_ACTION.SET_DETECTOR_INDEX, detectorId); } // Populate the map of jobs / detectors / field formatters for the selected IDs and refresh. - mlFieldFormatService.populateFormats([jobId]).catch((err) => { - console.log('Error populating field formats:', err); - }); + mlFieldFormatService.populateFormats([jobId]); } componentDidMount() { diff --git a/x-pack/plugins/ml/public/application/util/index_utils.ts b/x-pack/plugins/ml/public/application/util/index_utils.ts index b105761e5ebcf..5d35081dcb0fc 100644 --- a/x-pack/plugins/ml/public/application/util/index_utils.ts +++ b/x-pack/plugins/ml/public/application/util/index_utils.ts @@ -9,17 +9,13 @@ import { i18n } from '@kbn/i18n'; import type { Query } from '../../../../../../src/plugins/data/public'; import type { DataView, DataViewsContract } from '../../../../../../src/plugins/data_views/public'; import type { SavedSearchSavedObject } from '../../../common/types/kibana'; -import { getToastNotifications, getSavedObjectsClient, getDataViews } from './dependency_cache'; +import { getToastNotifications, getSavedObjectsClient } from './dependency_cache'; -let indexPatternCache: DataView[] = []; let savedSearchesCache: SavedSearchSavedObject[] = []; -let indexPatternsContract: DataViewsContract | null = null; +let dataViewsContract: DataViewsContract | null = null; -export async function loadIndexPatterns(indexPatterns: DataViewsContract) { - indexPatternsContract = indexPatterns; - const dataViewsContract = getDataViews(); - indexPatternCache = await dataViewsContract.find('*', 10000); - return indexPatternCache; +export async function cacheDataViewsContract(dvc: DataViewsContract) { + dataViewsContract = dvc; } export function loadSavedSearches() { @@ -41,29 +37,45 @@ export async function loadSavedSearchById(id: string) { return ss.error === undefined ? ss : null; } -export function getIndexPatterns() { - return indexPatternCache; +export async function getDataViewNames() { + if (dataViewsContract === null) { + throw new Error('Data views are not initialized!'); + } + return (await dataViewsContract.getIdsWithTitle()).map(({ title }) => title); } -export function getIndexPatternsContract() { - return indexPatternsContract; +export async function getDataViewIdFromName(name: string): Promise { + if (dataViewsContract === null) { + throw new Error('Data views are not initialized!'); + } + const [dv] = await dataViewsContract?.find(name); + if (!dv) { + return null; + } + return dv.id ?? dv.title; } -export function getIndexPatternNames() { - return indexPatternCache.map((i) => i.title); -} +export function getDataViewById(id: string): Promise { + if (dataViewsContract === null) { + throw new Error('Data views are not initialized!'); + } -export function getIndexPatternIdFromName(name: string) { - return indexPatternCache.find((i) => i.title === name)?.id ?? null; + if (id) { + return dataViewsContract.get(id); + } else { + return dataViewsContract.create({}); + } } -export interface IndexPatternAndSavedSearch { + +export interface DataViewAndSavedSearch { savedSearch: SavedSearchSavedObject | null; - indexPattern: DataView | null; + dataView: DataView | null; } -export async function getIndexPatternAndSavedSearch(savedSearchId: string) { - const resp: IndexPatternAndSavedSearch = { + +export async function getDataViewAndSavedSearch(savedSearchId: string) { + const resp: DataViewAndSavedSearch = { savedSearch: null, - indexPattern: null, + dataView: null, }; if (savedSearchId === undefined) { @@ -74,8 +86,8 @@ export async function getIndexPatternAndSavedSearch(savedSearchId: string) { if (ss === null) { return resp; } - const indexPatternId = ss.references.find((r) => r.type === 'index-pattern')?.id; - resp.indexPattern = await getIndexPatternById(indexPatternId!); + const dataViewId = ss.references.find((r) => r.type === 'index-pattern')?.id; + resp.dataView = await getDataViewById(dataViewId!); resp.savedSearch = ss; return resp; } @@ -88,18 +100,6 @@ export function getQueryFromSavedSearchObject(savedSearch: SavedSearchSavedObjec }; } -export function getIndexPatternById(id: string): Promise { - if (indexPatternsContract !== null) { - if (id) { - return indexPatternsContract.get(id); - } else { - return indexPatternsContract.create({}); - } - } else { - throw new Error('Data views are not initialized!'); - } -} - export function getSavedSearchById(id: string): SavedSearchSavedObject | undefined { return savedSearchesCache.find((s) => s.id === id); } @@ -109,14 +109,14 @@ export function getSavedSearchById(id: string): SavedSearchSavedObject | undefin * an optional flag will trigger the display a notification at the top of the page * warning that the index is not time based */ -export function timeBasedIndexCheck(indexPattern: DataView, showNotification = false) { - if (!indexPattern.isTimeBased()) { +export function timeBasedIndexCheck(dataView: DataView, showNotification = false) { + if (!dataView.isTimeBased()) { if (showNotification) { const toastNotifications = getToastNotifications(); toastNotifications.addWarning({ title: i18n.translate('xpack.ml.dataViewNotBasedOnTimeSeriesNotificationTitle', { defaultMessage: 'The data view {dataViewName} is not based on a time series', - values: { dataViewName: indexPattern.title }, + values: { dataViewName: dataView.title }, }), text: i18n.translate('xpack.ml.dataViewNotBasedOnTimeSeriesNotificationDescription', { defaultMessage: 'Anomaly detection only runs over time-based indices', diff --git a/x-pack/plugins/ml/server/models/data_frame_analytics/index_patterns.ts b/x-pack/plugins/ml/server/models/data_frame_analytics/index_patterns.ts index 568be4197baf8..b0135b0295fd8 100644 --- a/x-pack/plugins/ml/server/models/data_frame_analytics/index_patterns.ts +++ b/x-pack/plugins/ml/server/models/data_frame_analytics/index_patterns.ts @@ -7,17 +7,17 @@ import { DataViewsService } from '../../../../../../src/plugins/data_views/common'; -export class IndexPatternHandler { +export class DataViewHandler { constructor(private dataViewService: DataViewsService) {} // returns a id based on an index pattern name - async getIndexPatternId(indexName: string) { + async getDataViewId(indexName: string) { const dv = (await this.dataViewService.find(indexName)).find( ({ title }) => title === indexName ); return dv?.id; } - async deleteIndexPatternById(indexId: string) { - return await this.dataViewService.delete(indexId); + async deleteDataViewById(dataViewId: string) { + return await this.dataViewService.delete(dataViewId); } } diff --git a/x-pack/plugins/ml/server/routes/data_frame_analytics.ts b/x-pack/plugins/ml/server/routes/data_frame_analytics.ts index ac90659a6f3c9..7c435a247d7fa 100644 --- a/x-pack/plugins/ml/server/routes/data_frame_analytics.ts +++ b/x-pack/plugins/ml/server/routes/data_frame_analytics.ts @@ -29,7 +29,7 @@ import type { GetAnalyticsMapArgs, ExtendAnalyticsMapArgs, } from '../models/data_frame_analytics/types'; -import { IndexPatternHandler } from '../models/data_frame_analytics/index_patterns'; +import { DataViewHandler } from '../models/data_frame_analytics/index_patterns'; import { AnalyticsManager } from '../models/data_frame_analytics/analytics_manager'; import { validateAnalyticsJob } from '../models/data_frame_analytics/validation'; import { fieldServiceProvider } from '../models/job_service/new_job_caps/field_service'; @@ -38,14 +38,14 @@ import { getAuthorizationHeader } from '../lib/request_authorization'; import type { MlClient } from '../lib/ml_client'; import type { DataViewsService } from '../../../../../src/plugins/data_views/common'; -function getIndexPatternId(dataViewsService: DataViewsService, patternName: string) { - const iph = new IndexPatternHandler(dataViewsService); - return iph.getIndexPatternId(patternName); +function getDataViewId(dataViewsService: DataViewsService, patternName: string) { + const iph = new DataViewHandler(dataViewsService); + return iph.getDataViewId(patternName); } -function deleteDestIndexPatternById(dataViewsService: DataViewsService, indexPatternId: string) { - const iph = new IndexPatternHandler(dataViewsService); - return iph.deleteIndexPatternById(indexPatternId); +function deleteDestDataViewById(dataViewsService: DataViewsService, dataViewId: string) { + const iph = new DataViewHandler(dataViewsService); + return iph.deleteDataViewById(dataViewId); } function getAnalyticsMap( @@ -427,9 +427,9 @@ export function dataFrameAnalyticsRoutes({ router, mlLicense, routeGuard }: Rout if (destinationIndex && deleteDestIndexPattern) { try { const dataViewsService = await getDataViewsService(); - const indexPatternId = await getIndexPatternId(dataViewsService, destinationIndex); - if (indexPatternId) { - await deleteDestIndexPatternById(dataViewsService, indexPatternId); + const dataViewId = await getDataViewId(dataViewsService, destinationIndex); + if (dataViewId) { + await deleteDestDataViewById(dataViewsService, dataViewId); } destIndexPatternDeleted.success = true; } catch (deleteDestIndexPatternError) { From e1bf92ce034b348709e74890264811408134db2d Mon Sep 17 00:00:00 2001 From: Tim Sullivan Date: Wed, 3 Nov 2021 02:27:45 -0700 Subject: [PATCH 32/41] [Reporting] Internally correct the hostname to "localhost" if "server.host" is "0.0.0.0" (#117022) * [Reporting] Internal correction for server hostname if "server.host" is "0.0.0.0" * add schema validation * correction to the failover logic the test is validation * update per feedback * more check against invalid hostnames * remove silly translations for server logs * remove unused translations * improve the tests --- .../server/config/create_config.test.ts | 92 +++++++++++++------ .../reporting/server/config/create_config.ts | 51 +++++----- .../reporting/server/config/schema.test.ts | 28 ++++-- .../plugins/reporting/server/config/schema.ts | 14 ++- .../test_helpers/create_mock_levellogger.ts | 3 +- .../translations/translations/ja-JP.json | 4 - .../translations/translations/zh-CN.json | 4 - 7 files changed, 123 insertions(+), 73 deletions(-) diff --git a/x-pack/plugins/reporting/server/config/create_config.test.ts b/x-pack/plugins/reporting/server/config/create_config.test.ts index e042142a54a0f..718638e1ba62b 100644 --- a/x-pack/plugins/reporting/server/config/create_config.test.ts +++ b/x-pack/plugins/reporting/server/config/create_config.test.ts @@ -5,27 +5,29 @@ * 2.0. */ -import { CoreSetup, PluginInitializerContext } from 'src/core/server'; +import * as Rx from 'rxjs'; +import { CoreSetup, HttpServerInfo, PluginInitializerContext } from 'src/core/server'; import { coreMock } from 'src/core/server/mocks'; -import { LevelLogger } from '../lib'; -import { createMockConfigSchema } from '../test_helpers'; +import { LevelLogger } from '../lib/level_logger'; +import { createMockConfigSchema, createMockLevelLogger } from '../test_helpers'; +import { ReportingConfigType } from './'; import { createConfig$ } from './create_config'; +const createMockConfig = ( + mockInitContext: PluginInitializerContext +): Rx.Observable => mockInitContext.config.create(); + describe('Reporting server createConfig$', () => { let mockCoreSetup: CoreSetup; let mockInitContext: PluginInitializerContext; - let mockLogger: LevelLogger; + let mockLogger: jest.Mocked; beforeEach(() => { mockCoreSetup = coreMock.createSetup(); mockInitContext = coreMock.createPluginInitializerContext( createMockConfigSchema({ kibanaServer: {} }) ); - mockLogger = { - warn: jest.fn(), - debug: jest.fn(), - clone: jest.fn().mockImplementation(() => mockLogger), - } as unknown as LevelLogger; + mockLogger = createMockLevelLogger(); }); afterEach(() => { @@ -37,19 +39,12 @@ describe('Reporting server createConfig$', () => { ...createMockConfigSchema({ kibanaServer: {} }), encryptionKey: undefined, }); - const mockConfig$: any = mockInitContext.config.create(); + const mockConfig$ = createMockConfig(mockInitContext); const result = await createConfig$(mockCoreSetup, mockConfig$, mockLogger).toPromise(); expect(result.encryptionKey).toMatch(/\S{32,}/); // random 32 characters - expect(result.kibanaServer).toMatchInlineSnapshot(` - Object { - "hostname": "localhost", - "port": 80, - "protocol": "http", - } - `); - expect((mockLogger.warn as any).mock.calls.length).toBe(1); - expect((mockLogger.warn as any).mock.calls[0]).toMatchObject([ + expect(mockLogger.warn.mock.calls.length).toBe(1); + expect(mockLogger.warn.mock.calls[0]).toMatchObject([ 'Generating a random key for xpack.reporting.encryptionKey. To prevent sessions from being invalidated on restart, please set xpack.reporting.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command.', ]); }); @@ -60,10 +55,10 @@ describe('Reporting server createConfig$', () => { encryptionKey: 'iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii', }) ); - const mockConfig$: any = mockInitContext.config.create(); + const mockConfig$ = createMockConfig(mockInitContext); const result = await createConfig$(mockCoreSetup, mockConfig$, mockLogger).toPromise(); expect(result.encryptionKey).toMatch('iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii'); - expect((mockLogger.warn as any).mock.calls.length).toBe(0); + expect(mockLogger.warn.mock.calls.length).toBe(0); }); it('uses the user-provided encryption key, reporting kibanaServer settings to override server info', async () => { @@ -77,7 +72,7 @@ describe('Reporting server createConfig$', () => { }, }) ); - const mockConfig$: any = mockInitContext.config.create(); + const mockConfig$ = createMockConfig(mockInitContext); const result = await createConfig$(mockCoreSetup, mockConfig$, mockLogger).toPromise(); expect(result).toMatchInlineSnapshot(` @@ -108,7 +103,7 @@ describe('Reporting server createConfig$', () => { }, } `); - expect((mockLogger.warn as any).mock.calls.length).toBe(0); + expect(mockLogger.warn.mock.calls.length).toBe(0); }); it('uses user-provided disableSandbox: false', async () => { @@ -118,11 +113,11 @@ describe('Reporting server createConfig$', () => { capture: { browser: { chromium: { disableSandbox: false } } }, }) ); - const mockConfig$: any = mockInitContext.config.create(); + const mockConfig$ = createMockConfig(mockInitContext); const result = await createConfig$(mockCoreSetup, mockConfig$, mockLogger).toPromise(); expect(result.capture.browser.chromium).toMatchObject({ disableSandbox: false }); - expect((mockLogger.warn as any).mock.calls.length).toBe(0); + expect(mockLogger.warn.mock.calls.length).toBe(0); }); it('uses user-provided disableSandbox: true', async () => { @@ -132,11 +127,11 @@ describe('Reporting server createConfig$', () => { capture: { browser: { chromium: { disableSandbox: true } } }, }) ); - const mockConfig$: any = mockInitContext.config.create(); + const mockConfig$ = createMockConfig(mockInitContext); const result = await createConfig$(mockCoreSetup, mockConfig$, mockLogger).toPromise(); expect(result.capture.browser.chromium).toMatchObject({ disableSandbox: true }); - expect((mockLogger.warn as any).mock.calls.length).toBe(0); + expect(mockLogger.warn.mock.calls.length).toBe(0); }); it('provides a default for disableSandbox', async () => { @@ -145,10 +140,49 @@ describe('Reporting server createConfig$', () => { encryptionKey: '888888888888888888888888888888888', }) ); - const mockConfig$: any = mockInitContext.config.create(); + const mockConfig$ = createMockConfig(mockInitContext); const result = await createConfig$(mockCoreSetup, mockConfig$, mockLogger).toPromise(); expect(result.capture.browser.chromium).toMatchObject({ disableSandbox: expect.any(Boolean) }); - expect((mockLogger.warn as any).mock.calls.length).toBe(0); + expect(mockLogger.warn.mock.calls.length).toBe(0); }); + + for (const hostname of [ + '0', + '0.0', + '0.0.0', + '0.0.0.0', + '0000:0000:0000:0000:0000:0000:0000:0000', + '::', + ]) { + it(`apply failover logic when hostname is given as ${hostname}`, async () => { + mockInitContext = coreMock.createPluginInitializerContext( + createMockConfigSchema({ + encryptionKey: 'aaaaaaaaaaaaabbbbbbbbbbbbaaaaaaaaa', + // overwrite settings added by createMockConfigSchema and apply the default settings + // TODO make createMockConfigSchema _not_ default xpack.reporting.kibanaServer.hostname to 'localhost' + kibanaServer: { + hostname: undefined, + port: undefined, + }, + }) + ); + mockCoreSetup.http.getServerInfo = jest.fn().mockImplementation( + (): HttpServerInfo => ({ + name: 'cool server', + hostname, + port: 5601, + protocol: 'http', + }) + ); + + const mockConfig$ = createMockConfig(mockInitContext); + const result = await createConfig$(mockCoreSetup, mockConfig$, mockLogger).toPromise(); + expect(result.kibanaServer).toMatchObject({ + hostname: 'localhost', + port: 5601, + protocol: 'http', + }); + }); + } }); diff --git a/x-pack/plugins/reporting/server/config/create_config.ts b/x-pack/plugins/reporting/server/config/create_config.ts index b1d69d17334be..5de54a43582ab 100644 --- a/x-pack/plugins/reporting/server/config/create_config.ts +++ b/x-pack/plugins/reporting/server/config/create_config.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { i18n } from '@kbn/i18n'; import crypto from 'crypto'; -import { upperFirst } from 'lodash'; +import ipaddr from 'ipaddr.js'; +import { sum, upperFirst } from 'lodash'; import { Observable } from 'rxjs'; import { map, mergeMap } from 'rxjs/operators'; import { CoreSetup } from 'src/core/server'; @@ -33,20 +33,29 @@ export function createConfig$( let encryptionKey = config.encryptionKey; if (encryptionKey === undefined) { logger.warn( - i18n.translate('xpack.reporting.serverConfig.randomEncryptionKey', { - defaultMessage: - 'Generating a random key for xpack.reporting.encryptionKey. To prevent sessions from being invalidated on ' + - 'restart, please set xpack.reporting.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command.', - }) + 'Generating a random key for xpack.reporting.encryptionKey. To prevent sessions from being invalidated on ' + + 'restart, please set xpack.reporting.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command.' ); encryptionKey = crypto.randomBytes(16).toString('hex'); } const { kibanaServer: reportingServer } = config; const serverInfo = core.http.getServerInfo(); - // kibanaServer.hostname, default to server.host - const kibanaServerHostname = reportingServer.hostname + // set kibanaServer.hostname, default to server.host, don't allow "0.0.0.0" as it breaks in Windows + let kibanaServerHostname = reportingServer.hostname ? reportingServer.hostname : serverInfo.hostname; + + if ( + ipaddr.isValid(kibanaServerHostname) && + !sum(ipaddr.parse(kibanaServerHostname).toByteArray()) + ) { + logger.warn( + `Found 'server.host: "0.0.0.0"' in Kibana configuration. Reporting is not able to use this as the Kibana server hostname.` + + ` To enable PNG/PDF Reporting to work, 'xpack.reporting.kibanaServer.hostname: localhost' is automatically set in the configuration.` + + ` You can prevent this message by adding 'xpack.reporting.kibanaServer.hostname: localhost' in kibana.yml.` + ); + kibanaServerHostname = 'localhost'; + } // kibanaServer.port, default to server.port const kibanaServerPort = reportingServer.port ? reportingServer.port : serverInfo.port; // kibanaServer.protocol, default to server.protocol @@ -66,36 +75,24 @@ export function createConfig$( mergeMap(async (config) => { if (config.capture.browser.chromium.disableSandbox != null) { // disableSandbox was set by user - return config; + return { ...config }; } // disableSandbox was not set by user, apply default for OS const { os, disableSandbox } = await getDefaultChromiumSandboxDisabled(); const osName = [os.os, os.dist, os.release].filter(Boolean).map(upperFirst).join(' '); - logger.debug( - i18n.translate('xpack.reporting.serverConfig.osDetected', { - defaultMessage: `Running on OS: '{osName}'`, - values: { osName }, - }) - ); + logger.debug(`Running on OS: '{osName}'`); if (disableSandbox === true) { logger.warn( - i18n.translate('xpack.reporting.serverConfig.autoSet.sandboxDisabled', { - defaultMessage: `Chromium sandbox provides an additional layer of protection, but is not supported for {osName} OS. Automatically setting '{configKey}: true'.`, - values: { - configKey: 'xpack.reporting.capture.browser.chromium.disableSandbox', - osName, - }, - }) + `Chromium sandbox provides an additional layer of protection, but is not supported for ${osName} OS.` + + ` Automatically setting 'xpack.reporting.capture.browser.chromium.disableSandbox: true'.` ); } else { logger.info( - i18n.translate('xpack.reporting.serverConfig.autoSet.sandboxEnabled', { - defaultMessage: `Chromium sandbox provides an additional layer of protection, and is supported for {osName} OS. Automatically enabling Chromium sandbox.`, - values: { osName }, - }) + `Chromium sandbox provides an additional layer of protection, and is supported for ${osName} OS.` + + ` Automatically enabling Chromium sandbox.` ); } diff --git a/x-pack/plugins/reporting/server/config/schema.test.ts b/x-pack/plugins/reporting/server/config/schema.test.ts index 6ad7d03bd1a8f..7f1da5b55ccb6 100644 --- a/x-pack/plugins/reporting/server/config/schema.test.ts +++ b/x-pack/plugins/reporting/server/config/schema.test.ts @@ -288,11 +288,25 @@ describe('Reporting Config Schema', () => { `); }); - it(`logs the proper validation messages`, () => { - // kibanaServer - const throwValidationErr = () => ConfigSchema.validate({ kibanaServer: { hostname: '0' } }); - expect(throwValidationErr).toThrowError( - `[kibanaServer.hostname]: value must be a valid hostname (see RFC 1123).` - ); - }); + for (const address of ['0', '0.0', '0.0.0']) { + it(`fails to validate "kibanaServer.hostname" with an invalid hostname: "${address}"`, () => { + expect(() => + ConfigSchema.validate({ + kibanaServer: { hostname: address }, + }) + ).toThrowError(`[kibanaServer.hostname]: value must be a valid hostname (see RFC 1123).`); + }); + } + + for (const address of ['0.0.0.0', '0000:0000:0000:0000:0000:0000:0000:0000', '::']) { + it(`fails to validate "kibanaServer.hostname" hostname as zero: "${address}"`, () => { + expect(() => + ConfigSchema.validate({ + kibanaServer: { hostname: address }, + }) + ).toThrowError( + `[kibanaServer.hostname]: cannot use '0.0.0.0' as Kibana host name, consider using the default (localhost) instead` + ); + }); + } }); diff --git a/x-pack/plugins/reporting/server/config/schema.ts b/x-pack/plugins/reporting/server/config/schema.ts index 5b15260be06cb..4c56fc4c6db60 100644 --- a/x-pack/plugins/reporting/server/config/schema.ts +++ b/x-pack/plugins/reporting/server/config/schema.ts @@ -6,10 +6,22 @@ */ import { ByteSizeValue, schema, TypeOf } from '@kbn/config-schema'; +import ipaddr from 'ipaddr.js'; +import { sum } from 'lodash'; import moment from 'moment'; const KibanaServerSchema = schema.object({ - hostname: schema.maybe(schema.string({ hostname: true })), + hostname: schema.maybe( + schema.string({ + hostname: true, + validate(value) { + if (ipaddr.isValid(value) && !sum(ipaddr.parse(value).toByteArray())) { + // prevent setting a hostname that fails in Chromium on Windows + return `cannot use '0.0.0.0' as Kibana host name, consider using the default (localhost) instead`; + } + }, + }) + ), port: schema.maybe(schema.number()), protocol: schema.maybe( schema.string({ diff --git a/x-pack/plugins/reporting/server/test_helpers/create_mock_levellogger.ts b/x-pack/plugins/reporting/server/test_helpers/create_mock_levellogger.ts index 1a8bfe7b70208..a6e6be47bdfcd 100644 --- a/x-pack/plugins/reporting/server/test_helpers/create_mock_levellogger.ts +++ b/x-pack/plugins/reporting/server/test_helpers/create_mock_levellogger.ts @@ -16,7 +16,6 @@ export function createMockLevelLogger() { const logger = new LevelLogger(loggingSystemMock.create()) as jest.Mocked; - logger.clone.mockImplementation(createMockLevelLogger); // logger.debug.mockImplementation(consoleLogger('debug')); // uncomment this to see debug logs in jest tests logger.info.mockImplementation(consoleLogger('info')); logger.warn.mockImplementation(consoleLogger('warn')); @@ -24,5 +23,7 @@ export function createMockLevelLogger() { logger.error.mockImplementation(consoleLogger('error')); logger.trace.mockImplementation(consoleLogger('trace')); + logger.clone.mockImplementation(() => logger); + return logger; } diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 857e97daa3515..47876265894ec 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -19243,10 +19243,6 @@ "xpack.reporting.screenCapturePanelContent.canvasLayoutLabel": "全ページレイアウト", "xpack.reporting.screenCapturePanelContent.optimizeForPrintingHelpText": "複数のページを使用します。ページごとに最大2のビジュアライゼーションが表示されます", "xpack.reporting.screenCapturePanelContent.optimizeForPrintingLabel": "印刷用に最適化", - "xpack.reporting.serverConfig.autoSet.sandboxDisabled": "Chromiumサンドボックスは保護が強化されていますが、{osName} OSではサポートされていません。自動的に'{configKey}: true'を設定しています。", - "xpack.reporting.serverConfig.autoSet.sandboxEnabled": "Chromiumサンドボックスは保護が強化され、{osName} OSでサポートされています。自動的にChromiumサンドボックスを有効にしています。", - "xpack.reporting.serverConfig.osDetected": "OSは'{osName}'で実行しています", - "xpack.reporting.serverConfig.randomEncryptionKey": "xpack.reporting.encryptionKey のランダムキーを生成しています。再起動時にセッションが無効にならないようにするには、kibana.yml でxpack.reporting.encryptionKey を設定するか、bin/kibana-encryption-keys コマンドを使用してください。", "xpack.reporting.shareContextMenu.csvReportsButtonLabel": "CSV レポート", "xpack.reporting.shareContextMenu.pdfReportsButtonLabel": "PDF レポート", "xpack.reporting.shareContextMenu.pngReportsButtonLabel": "PNG レポート", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 6c860a5b2cc4d..0fd4fea956a11 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -19520,10 +19520,6 @@ "xpack.reporting.screenCapturePanelContent.canvasLayoutLabel": "全页面布局", "xpack.reporting.screenCapturePanelContent.optimizeForPrintingHelpText": "使用多页,每页最多显示 2 个可视化", "xpack.reporting.screenCapturePanelContent.optimizeForPrintingLabel": "打印优化", - "xpack.reporting.serverConfig.autoSet.sandboxDisabled": "Chromium 沙盒提供附加保护层,但不受 {osName} OS 支持。自动设置“{configKey}: true”。", - "xpack.reporting.serverConfig.autoSet.sandboxEnabled": "Chromium 沙盒提供附加保护层,受 {osName} OS 支持。自动启用 Chromium 沙盒。", - "xpack.reporting.serverConfig.osDetected": "正在以下 OS 上运行:“{osName}”", - "xpack.reporting.serverConfig.randomEncryptionKey": "为 xpack.reporting.encryptionKey 生成随机密钥。为防止会话在重启时失效,请在 kibana.yml 中设置 xpack.reporting.encryptionKey 或使用 bin/kibana-encryption-keys 命令。", "xpack.reporting.shareContextMenu.csvReportsButtonLabel": "CSV 报告", "xpack.reporting.shareContextMenu.pdfReportsButtonLabel": "PDF 报告", "xpack.reporting.shareContextMenu.pngReportsButtonLabel": "PNG 报告", From 2ad1f9139f0d7a15697cdc15c04971373516f555 Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Wed, 3 Nov 2021 10:44:11 +0100 Subject: [PATCH 33/41] [Lens] Removing updateVisualizationState updater (#113779) * make editVisualizationAction separate action to get rid of updater * add insertLayer to remove updater when inserting new Layer * wip * removeLayers before merging into one function * chart switch refactor to single updater * replace updater with newState * check onEditAction * remove layer remove updater * addLayer updater * set Layer Default Dimension * wip * mocks divided * visualization mock lint fix * add tests for layers * further divide * rename ts to tsx * Rename index.tsx to index.ts * Update x-pack/plugins/lens/public/state_management/index.ts * Revert "[Lens] cleanup divide mock file to smaller pieces" This reverts commit 1bb2e1a4f1431f843cf777ad6f90eeca047e0774. Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../config_panel/config_panel.test.tsx | 203 ++++------ .../config_panel/config_panel.tsx | 179 +-------- .../config_panel/layer_actions.test.ts | 130 ------ .../config_panel/layer_actions.ts | 95 ----- .../editor_frame/editor_frame.test.tsx | 51 ++- .../workspace_panel/chart_switch.test.tsx | 158 ++++---- .../workspace_panel/chart_switch.tsx | 50 +-- .../workspace_panel/workspace_panel.tsx | 6 +- .../workspace_panel_wrapper.tsx | 3 +- .../lens/public/state_management/index.ts | 7 +- .../state_management/lens_slice.test.ts | 117 +++++- .../public/state_management/lens_slice.ts | 377 +++++++++++++++--- x-pack/plugins/lens/public/types.ts | 2 +- x-pack/plugins/lens/public/utils.ts | 17 +- 14 files changed, 680 insertions(+), 715 deletions(-) delete mode 100644 x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_actions.test.ts delete mode 100644 x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_actions.ts diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.test.tsx index a8436edb63f63..cd26cd3197587 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.test.tsx @@ -7,7 +7,13 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; -import { createMockFramePublicAPI, visualizationMap, datasourceMap } from '../../../mocks'; +import { + createMockFramePublicAPI, + mockVisualizationMap, + mockDatasourceMap, + mockStoreDeps, + MountStoreProps, +} from '../../../mocks'; import { Visualization } from '../../../types'; import { LayerPanels } from './config_panel'; import { LayerPanel } from './layer_panel'; @@ -16,6 +22,7 @@ import { generateId } from '../../../id_generator'; import { mountWithProvider } from '../../../mocks'; import { layerTypes } from '../../../../common'; import { ReactWrapper } from 'enzyme'; +import { addLayer } from '../../../state_management'; jest.mock('../../../id_generator'); @@ -39,8 +46,40 @@ afterEach(() => { describe('ConfigPanel', () => { const frame = createMockFramePublicAPI(); - - function getDefaultProps() { + function prepareAndMountComponent( + props: ReturnType, + customStoreProps?: Partial + ) { + (generateId as jest.Mock).mockReturnValue(`newId`); + return mountWithProvider( + , + { + preloadedState: { + datasourceStates: { + testDatasource: { + isLoading: false, + state: 'state', + }, + }, + activeDatasourceId: 'testDatasource', + }, + storeDeps: mockStoreDeps({ + datasourceMap: props.datasourceMap, + visualizationMap: props.visualizationMap, + }), + ...customStoreProps, + }, + { + attachTo: container, + } + ); + } + function getDefaultProps( + { datasourceMap = mockDatasourceMap(), visualizationMap = mockVisualizationMap() } = { + datasourceMap: mockDatasourceMap(), + visualizationMap: mockVisualizationMap(), + } + ) { frame.datasourceLayers = { first: datasourceMap.testDatasource.publicAPIMock, }; @@ -75,22 +114,13 @@ describe('ConfigPanel', () => { it('should fail to render layerPanels if the public API is out of date', async () => { const props = getDefaultProps(); props.framePublicAPI.datasourceLayers = {}; - const { instance } = await mountWithProvider(); + const { instance } = await prepareAndMountComponent(props); expect(instance.find(LayerPanel).exists()).toBe(false); }); it('allow datasources and visualizations to use setters', async () => { const props = getDefaultProps(); - const { instance, lensStore } = await mountWithProvider(, { - preloadedState: { - datasourceStates: { - testDatasource: { - isLoading: false, - state: 'state', - }, - }, - }, - }); + const { instance, lensStore } = await prepareAndMountComponent(props); const { updateDatasource, updateAll } = instance.find(LayerPanel).props(); const updater = () => 'updated'; @@ -116,22 +146,7 @@ describe('ConfigPanel', () => { describe('focus behavior when adding or removing layers', () => { it('should focus the only layer when resetting the layer', async () => { - const { instance } = await mountWithProvider( - , - { - preloadedState: { - datasourceStates: { - testDatasource: { - isLoading: false, - state: 'state', - }, - }, - }, - }, - { - attachTo: container, - } - ); + const { instance } = await prepareAndMountComponent(getDefaultProps()); const firstLayerFocusable = instance .find(LayerPanel) .first() @@ -146,29 +161,15 @@ describe('ConfigPanel', () => { }); it('should focus the second layer when removing the first layer', async () => { - const defaultProps = getDefaultProps(); + const datasourceMap = mockDatasourceMap(); + const defaultProps = getDefaultProps({ datasourceMap }); // overwriting datasourceLayers to test two layers frame.datasourceLayers = { first: datasourceMap.testDatasource.publicAPIMock, second: datasourceMap.testDatasource.publicAPIMock, }; - const { instance } = await mountWithProvider( - , - { - preloadedState: { - datasourceStates: { - testDatasource: { - isLoading: false, - state: 'state', - }, - }, - }, - }, - { - attachTo: container, - } - ); + const { instance } = await prepareAndMountComponent(defaultProps); const secondLayerFocusable = instance .find(LayerPanel) .at(1) @@ -183,28 +184,14 @@ describe('ConfigPanel', () => { }); it('should focus the first layer when removing the second layer', async () => { - const defaultProps = getDefaultProps(); + const datasourceMap = mockDatasourceMap(); + const defaultProps = getDefaultProps({ datasourceMap }); // overwriting datasourceLayers to test two layers frame.datasourceLayers = { first: datasourceMap.testDatasource.publicAPIMock, second: datasourceMap.testDatasource.publicAPIMock, }; - const { instance } = await mountWithProvider( - , - { - preloadedState: { - datasourceStates: { - testDatasource: { - isLoading: false, - state: 'state', - }, - }, - }, - }, - { - attachTo: container, - } - ); + const { instance } = await prepareAndMountComponent(defaultProps); const firstLayerFocusable = instance .find(LayerPanel) .first() @@ -219,31 +206,22 @@ describe('ConfigPanel', () => { }); it('should focus the added layer', async () => { - (generateId as jest.Mock).mockReturnValue(`second`); + const datasourceMap = mockDatasourceMap(); + frame.datasourceLayers = { + first: datasourceMap.testDatasource.publicAPIMock, + newId: datasourceMap.testDatasource.publicAPIMock, + }; - const { instance } = await mountWithProvider( - , + const defaultProps = getDefaultProps({ datasourceMap }); + + const { instance } = await prepareAndMountComponent(defaultProps, { + dispatch: jest.fn((x) => { + if (x.type === addLayer.type) { + frame.datasourceLayers.newId = datasourceMap.testDatasource.publicAPIMock; + } + }), + }); - { - preloadedState: { - datasourceStates: { - testDatasource: { - isLoading: false, - state: 'state', - }, - }, - activeDatasourceId: 'testDatasource', - }, - dispatch: jest.fn((x) => { - if (x.payload.subType === 'ADD_LAYER') { - frame.datasourceLayers.second = datasourceMap.testDatasource.publicAPIMock; - } - }), - }, - { - attachTo: container, - } - ); act(() => { instance.find('[data-test-subj="lnsLayerAddButton"]').first().simulate('click'); }); @@ -253,26 +231,6 @@ describe('ConfigPanel', () => { }); describe('initial default value', () => { - function prepareAndMountComponent(props: ReturnType) { - (generateId as jest.Mock).mockReturnValue(`newId`); - return mountWithProvider( - , - { - preloadedState: { - datasourceStates: { - testDatasource: { - isLoading: false, - state: 'state', - }, - }, - activeDatasourceId: 'testDatasource', - }, - }, - { - attachTo: container, - } - ); - } function clickToAddLayer(instance: ReactWrapper) { act(() => { instance.find('[data-test-subj="lnsLayerAddButton"]').first().simulate('click'); @@ -297,8 +255,10 @@ describe('ConfigPanel', () => { } it('should not add an initial dimension when not specified', async () => { - const props = getDefaultProps(); - props.activeVisualization.getSupportedLayers = jest.fn(() => [ + const datasourceMap = mockDatasourceMap(); + const visualizationMap = mockVisualizationMap(); + + visualizationMap.testVis.getSupportedLayers = jest.fn(() => [ { type: layerTypes.DATA, label: 'Data Layer' }, { type: layerTypes.REFERENCELINE, @@ -306,6 +266,7 @@ describe('ConfigPanel', () => { }, ]); datasourceMap.testDatasource.initializeDimension = jest.fn(); + const props = getDefaultProps({ datasourceMap, visualizationMap }); const { instance, lensStore } = await prepareAndMountComponent(props); await clickToAddLayer(instance); @@ -315,8 +276,11 @@ describe('ConfigPanel', () => { }); it('should not add an initial dimension when initialDimensions are not available for the given layer type', async () => { - const props = getDefaultProps(); - props.activeVisualization.getSupportedLayers = jest.fn(() => [ + const datasourceMap = mockDatasourceMap(); + const visualizationMap = mockVisualizationMap(); + datasourceMap.testDatasource.initializeDimension = jest.fn(); + + visualizationMap.testVis.getSupportedLayers = jest.fn(() => [ { type: layerTypes.DATA, label: 'Data Layer', @@ -335,8 +299,7 @@ describe('ConfigPanel', () => { label: 'Reference layer', }, ]); - datasourceMap.testDatasource.initializeDimension = jest.fn(); - + const props = getDefaultProps({ datasourceMap, visualizationMap }); const { instance, lensStore } = await prepareAndMountComponent(props); await clickToAddLayer(instance); @@ -345,8 +308,9 @@ describe('ConfigPanel', () => { }); it('should use group initial dimension value when adding a new layer if available', async () => { - const props = getDefaultProps(); - props.activeVisualization.getSupportedLayers = jest.fn(() => [ + const datasourceMap = mockDatasourceMap(); + const visualizationMap = mockVisualizationMap(); + visualizationMap.testVis.getSupportedLayers = jest.fn(() => [ { type: layerTypes.DATA, label: 'Data Layer' }, { type: layerTypes.REFERENCELINE, @@ -363,6 +327,7 @@ describe('ConfigPanel', () => { }, ]); datasourceMap.testDatasource.initializeDimension = jest.fn(); + const props = getDefaultProps({ datasourceMap, visualizationMap }); const { instance, lensStore } = await prepareAndMountComponent(props); await clickToAddLayer(instance); @@ -378,8 +343,10 @@ describe('ConfigPanel', () => { }); it('should add an initial dimension value when clicking on the empty dimension button', async () => { - const props = getDefaultProps(); - props.activeVisualization.getSupportedLayers = jest.fn(() => [ + const datasourceMap = mockDatasourceMap(); + + const visualizationMap = mockVisualizationMap(); + visualizationMap.testVis.getSupportedLayers = jest.fn(() => [ { type: layerTypes.DATA, label: 'Data Layer', @@ -395,7 +362,7 @@ describe('ConfigPanel', () => { }, ]); datasourceMap.testDatasource.initializeDimension = jest.fn(); - + const props = getDefaultProps({ visualizationMap, datasourceMap }); const { instance, lensStore } = await prepareAndMountComponent(props); await clickToAddDimension(instance); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx index 0b6223ac87ce2..d3574abe4f57a 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx @@ -7,26 +7,26 @@ import React, { useMemo, memo } from 'react'; import { EuiForm } from '@elastic/eui'; -import { mapValues } from 'lodash'; import { Visualization } from '../../../types'; import { LayerPanel } from './layer_panel'; import { trackUiEvent } from '../../../lens_ui_telemetry'; import { generateId } from '../../../id_generator'; -import { appendLayer } from './layer_actions'; import { ConfigPanelWrapperProps } from './types'; import { useFocusUpdate } from './use_focus_update'; import { + setLayerDefaultDimension, useLensDispatch, + removeOrClearLayer, + addLayer, updateState, updateDatasourceState, updateVisualizationState, setToggleFullscreen, useLensSelector, selectVisualization, - VisualizationState, - LensAppState, } from '../../../state_management'; -import { AddLayerButton, getLayerType } from './add_layer'; +import { AddLayerButton } from './add_layer'; +import { getRemoveOperation } from '../../../utils'; export const ConfigPanelWrapper = memo(function ConfigPanelWrapper(props: ConfigPanelWrapperProps) { const visualization = useLensSelector(selectVisualization); @@ -39,18 +39,6 @@ export const ConfigPanelWrapper = memo(function ConfigPanelWrapper(props: Config ) : null; }); -function getRemoveOperation( - activeVisualization: Visualization, - visualizationState: VisualizationState['state'], - layerId: string, - layerCount: number -) { - if (activeVisualization.getRemoveOperation) { - return activeVisualization.getRemoveOperation(visualizationState, layerId); - } - // fallback to generic count check - return layerCount === 1 ? 'clear' : 'remove'; -} export function LayerPanels( props: ConfigPanelWrapperProps & { activeVisualization: Visualization; @@ -73,8 +61,7 @@ export function LayerPanels( dispatchLens( updateVisualizationState({ visualizationId: activeVisualization.id, - updater: newState, - clearStagedPreview: false, + newState, }) ); }, @@ -110,7 +97,6 @@ export function LayerPanels( setTimeout(() => { dispatchLens( updateState({ - subType: 'UPDATE_ALL_STATES', updater: (prevState) => { const updatedDatasourceState = typeof newDatasourceState === 'function' @@ -133,7 +119,6 @@ export function LayerPanels( ...prevState.visualization, state: updatedVisualizationState, }, - stagedPreview: undefined, }; }, }) @@ -183,66 +168,22 @@ export function LayerPanels( datasourceMap[activeDatasourceId]?.initializeDimension ) { dispatchLens( - updateState({ - subType: 'LAYER_DEFAULT_DIMENSION', - updater: (state) => - addInitialValueIfAvailable({ - ...props, - state, - activeDatasourceId, - layerId, - layerType: getLayerType( - activeVisualization, - state.visualization.state, - layerId - ), - columnId, - groupId, - }), + setLayerDefaultDimension({ + layerId, + columnId, + groupId, }) ); } }} onRemoveLayer={() => { dispatchLens( - updateState({ - subType: 'REMOVE_OR_CLEAR_LAYER', - updater: (state) => { - const isOnlyLayer = - getRemoveOperation( - activeVisualization, - state.visualization.state, - layerId, - layerIds.length - ) === 'clear'; - - return { - ...state, - datasourceStates: mapValues( - state.datasourceStates, - (datasourceState, datasourceId) => { - const datasource = datasourceMap[datasourceId!]; - return { - ...datasourceState, - state: isOnlyLayer - ? datasource.clearLayer(datasourceState.state, layerId) - : datasource.removeLayer(datasourceState.state, layerId), - }; - } - ), - visualization: { - ...state.visualization, - state: - isOnlyLayer || !activeVisualization.removeLayer - ? activeVisualization.clearLayer(state.visualization.state, layerId) - : activeVisualization.removeLayer(state.visualization.state, layerId), - }, - stagedPreview: undefined, - }; - }, + removeOrClearLayer({ + visualizationId: activeVisualization.id, + layerId, + layerIds, }) ); - removeLayerRef(layerId); }} toggleFullscreen={toggleFullscreen} @@ -254,96 +195,12 @@ export function LayerPanels( visualizationState={visualization.state} layersMeta={props.framePublicAPI} onAddLayerClick={(layerType) => { - const id = generateId(); - dispatchLens( - updateState({ - subType: 'ADD_LAYER', - updater: (state) => { - const newState = appendLayer({ - activeVisualization, - generateId: () => id, - trackUiEvent, - activeDatasource: datasourceMap[activeDatasourceId!], - state, - layerType, - }); - return addInitialValueIfAvailable({ - ...props, - activeDatasourceId: activeDatasourceId!, - state: newState, - layerId: id, - layerType, - }); - }, - }) - ); - setNextFocusedLayerId(id); + const layerId = generateId(); + dispatchLens(addLayer({ layerId, layerType })); + trackUiEvent('layer_added'); + setNextFocusedLayerId(layerId); }} /> ); } - -function addInitialValueIfAvailable({ - state, - activeVisualization, - framePublicAPI, - layerType, - activeDatasourceId, - datasourceMap, - layerId, - columnId, - groupId, -}: ConfigPanelWrapperProps & { - state: LensAppState; - activeDatasourceId: string; - activeVisualization: Visualization; - layerId: string; - layerType: string; - columnId?: string; - groupId?: string; -}) { - const layerInfo = activeVisualization - .getSupportedLayers(state.visualization.state, framePublicAPI) - .find(({ type }) => type === layerType); - - const activeDatasource = datasourceMap[activeDatasourceId]; - - if (layerInfo?.initialDimensions && activeDatasource?.initializeDimension) { - const info = groupId - ? layerInfo.initialDimensions.find(({ groupId: id }) => id === groupId) - : // pick the first available one if not passed - layerInfo.initialDimensions[0]; - - if (info) { - return { - ...state, - datasourceStates: { - ...state.datasourceStates, - [activeDatasourceId]: { - ...state.datasourceStates[activeDatasourceId], - state: activeDatasource.initializeDimension( - state.datasourceStates[activeDatasourceId].state, - layerId, - { - ...info, - columnId: columnId || info.columnId, - } - ), - }, - }, - visualization: { - ...state.visualization, - state: activeVisualization.setDimension({ - groupId: info.groupId, - layerId, - columnId: columnId || info.columnId, - prevState: state.visualization.state, - frame: framePublicAPI, - }), - }, - }; - } - } - return state; -} diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_actions.test.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_actions.test.ts deleted file mode 100644 index 44cefb0bf8ec4..0000000000000 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_actions.test.ts +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import { layerTypes } from '../../../../common'; -import { LensAppState } from '../../../state_management/types'; -import { removeLayer, appendLayer } from './layer_actions'; - -function createTestArgs(initialLayerIds: string[]) { - const trackUiEvent = jest.fn(); - const testDatasource = (datasourceId: string) => ({ - id: datasourceId, - clearLayer: (layerIds: unknown, layerId: string) => - (layerIds as string[]).map((id: string) => - id === layerId ? `${datasourceId}_clear_${layerId}` : id - ), - removeLayer: (layerIds: unknown, layerId: string) => - (layerIds as string[]).filter((id: string) => id !== layerId), - insertLayer: (layerIds: unknown, layerId: string) => [...(layerIds as string[]), layerId], - }); - - const activeVisualization = { - clearLayer: (layerIds: unknown, layerId: string) => - (layerIds as string[]).map((id: string) => (id === layerId ? `vis_clear_${layerId}` : id)), - removeLayer: (layerIds: unknown, layerId: string) => - (layerIds as string[]).filter((id: string) => id !== layerId), - getLayerIds: (layerIds: unknown) => layerIds as string[], - appendLayer: (layerIds: unknown, layerId: string) => [...(layerIds as string[]), layerId], - }; - - const datasourceStates = { - ds1: { - isLoading: false, - state: initialLayerIds.slice(0, 1), - }, - ds2: { - isLoading: false, - state: initialLayerIds.slice(1), - }, - }; - - return { - state: { - activeDatasourceId: 'ds1', - datasourceStates, - title: 'foo', - visualization: { - activeId: 'testVis', - state: initialLayerIds, - }, - } as unknown as LensAppState, - activeVisualization, - datasourceMap: { - ds1: testDatasource('ds1'), - ds2: testDatasource('ds2'), - }, - trackUiEvent, - stagedPreview: { - visualization: { - activeId: 'testVis', - state: initialLayerIds, - }, - datasourceStates, - }, - }; -} - -describe('removeLayer', () => { - it('should clear the layer if it is the only layer', () => { - const { state, trackUiEvent, datasourceMap, activeVisualization } = createTestArgs(['layer1']); - const newState = removeLayer({ - activeVisualization, - datasourceMap, - layerId: 'layer1', - state, - trackUiEvent, - }); - - expect(newState.visualization.state).toEqual(['vis_clear_layer1']); - expect(newState.datasourceStates.ds1.state).toEqual(['ds1_clear_layer1']); - expect(newState.datasourceStates.ds2.state).toEqual([]); - expect(newState.stagedPreview).not.toBeDefined(); - expect(trackUiEvent).toHaveBeenCalledWith('layer_cleared'); - }); - - it('should remove the layer if it is not the only layer', () => { - const { state, trackUiEvent, datasourceMap, activeVisualization } = createTestArgs([ - 'layer1', - 'layer2', - ]); - const newState = removeLayer({ - activeVisualization, - datasourceMap, - layerId: 'layer1', - state, - trackUiEvent, - }); - - expect(newState.visualization.state).toEqual(['layer2']); - expect(newState.datasourceStates.ds1.state).toEqual([]); - expect(newState.datasourceStates.ds2.state).toEqual(['layer2']); - expect(newState.stagedPreview).not.toBeDefined(); - expect(trackUiEvent).toHaveBeenCalledWith('layer_removed'); - }); -}); - -describe('appendLayer', () => { - it('should add the layer to the datasource and visualization', () => { - const { state, trackUiEvent, datasourceMap, activeVisualization } = createTestArgs([ - 'layer1', - 'layer2', - ]); - const newState = appendLayer({ - activeDatasource: datasourceMap.ds1, - activeVisualization, - generateId: () => 'foo', - state, - trackUiEvent, - layerType: layerTypes.DATA, - }); - - expect(newState.visualization.state).toEqual(['layer1', 'layer2', 'foo']); - expect(newState.datasourceStates.ds1.state).toEqual(['layer1', 'foo']); - expect(newState.datasourceStates.ds2.state).toEqual(['layer2']); - expect(newState.stagedPreview).not.toBeDefined(); - expect(trackUiEvent).toHaveBeenCalledWith('layer_added'); - }); -}); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_actions.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_actions.ts deleted file mode 100644 index c0f0847e8ff5c..0000000000000 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_actions.ts +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { mapValues } from 'lodash'; -import type { LayerType } from '../../../../common'; -import { LensAppState } from '../../../state_management'; - -import { Datasource, Visualization } from '../../../types'; - -interface RemoveLayerOptions { - trackUiEvent: (name: string) => void; - state: LensAppState; - layerId: string; - activeVisualization: Pick; - datasourceMap: Record>; -} - -interface AppendLayerOptions { - trackUiEvent: (name: string) => void; - state: LensAppState; - generateId: () => string; - activeDatasource: Pick; - activeVisualization: Pick; - layerType: LayerType; -} - -export function removeLayer(opts: RemoveLayerOptions): LensAppState { - const { state, trackUiEvent: trackUiEvent, activeVisualization, layerId, datasourceMap } = opts; - const isOnlyLayer = activeVisualization - .getLayerIds(state.visualization.state) - .every((id) => id === opts.layerId); - - trackUiEvent(isOnlyLayer ? 'layer_cleared' : 'layer_removed'); - - return { - ...state, - datasourceStates: mapValues(state.datasourceStates, (datasourceState, datasourceId) => { - const datasource = datasourceMap[datasourceId!]; - return { - ...datasourceState, - state: isOnlyLayer - ? datasource.clearLayer(datasourceState.state, layerId) - : datasource.removeLayer(datasourceState.state, layerId), - }; - }), - visualization: { - ...state.visualization, - state: - isOnlyLayer || !activeVisualization.removeLayer - ? activeVisualization.clearLayer(state.visualization.state, layerId) - : activeVisualization.removeLayer(state.visualization.state, layerId), - }, - stagedPreview: undefined, - }; -} - -export function appendLayer({ - trackUiEvent, - activeVisualization, - state, - generateId, - activeDatasource, - layerType, -}: AppendLayerOptions): LensAppState { - trackUiEvent('layer_added'); - - if (!activeVisualization.appendLayer) { - return state; - } - - const layerId = generateId(); - - return { - ...state, - datasourceStates: { - ...state.datasourceStates, - [activeDatasource.id]: { - ...state.datasourceStates[activeDatasource.id], - state: activeDatasource.insertLayer( - state.datasourceStates[activeDatasource.id].state, - layerId - ), - }, - }, - visualization: { - ...state.visualization, - state: activeVisualization.appendLayer(state.visualization.state, layerId, layerType), - }, - stagedPreview: undefined, - }; -} diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx index d289b69f4105e..37191ffa89fdc 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.test.tsx @@ -38,6 +38,7 @@ import { createMockDatasource, DatasourceMock, createExpressionRendererMock, + mockStoreDeps, } from '../../mocks'; import { inspectorPluginMock } from 'src/plugins/inspector/public/mocks'; import { ReactExpressionRendererType } from 'src/plugins/expressions/public'; @@ -144,21 +145,19 @@ describe('editor_frame', () => { ExpressionRenderer: expressionRendererMock, }; - const lensStore = ( - await mountWithProvider(, { - preloadedState: { - activeDatasourceId: 'testDatasource', - datasourceStates: { - testDatasource: { - isLoading: true, - state: { - internalState1: '', - }, + const { lensStore } = await mountWithProvider(, { + preloadedState: { + activeDatasourceId: 'testDatasource', + datasourceStates: { + testDatasource: { + isLoading: true, + state: { + internalState1: '', }, }, }, - }) - ).lensStore; + }, + }); expect(mockDatasource.renderDataPanel).not.toHaveBeenCalled(); lensStore.dispatch( setState({ @@ -553,6 +552,7 @@ describe('editor_frame', () => { } beforeEach(async () => { + mockVisualization2.initialize.mockReturnValue({ initial: true }); mockDatasource.getLayers.mockReturnValue(['first', 'second']); mockDatasource.getDatasourceSuggestionsFromCurrentState.mockReturnValue([ { @@ -567,20 +567,27 @@ describe('editor_frame', () => { }, ]); + const visualizationMap = { + testVis: mockVisualization, + testVis2: mockVisualization2, + }; + + const datasourceMap = { + testDatasource: mockDatasource, + testDatasource2: mockDatasource2, + }; + const props = { ...getDefaultProps(), - visualizationMap: { - testVis: mockVisualization, - testVis2: mockVisualization2, - }, - datasourceMap: { - testDatasource: mockDatasource, - testDatasource2: mockDatasource2, - }, - + visualizationMap, + datasourceMap, ExpressionRenderer: expressionRendererMock, }; - instance = (await mountWithProvider()).instance; + instance = ( + await mountWithProvider(, { + storeDeps: mockStoreDeps({ datasourceMap, visualizationMap }), + }) + ).instance; // necessary to flush elements to dom synchronously instance.update(); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.test.tsx index 7cb97882a5e03..c325e6d516c8b 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.test.tsx @@ -9,8 +9,9 @@ import React from 'react'; import { ReactWrapper } from 'enzyme'; import { createMockVisualization, + mockStoreDeps, createMockFramePublicAPI, - createMockDatasource, + mockDatasourceMap, mockDatasourceStates, } from '../../../mocks'; import { mountWithProvider } from '../../../mocks'; @@ -71,7 +72,7 @@ describe('chart_switch', () => { * - Allows a switch to subvisC3 * - Allows a switch to subvisC1 */ - function mockVisualizations() { + function mockVisualizationMap() { return { visA: generateVisualization('visA'), visB: generateVisualization('visB'), @@ -143,27 +144,6 @@ describe('chart_switch', () => { } as FramePublicAPI; } - function mockDatasourceMap() { - const datasource = createMockDatasource('testDatasource'); - datasource.getDatasourceSuggestionsFromCurrentState.mockReturnValue([ - { - state: {}, - table: { - columns: [], - isMultiRow: true, - layerId: 'a', - changeType: 'unchanged', - }, - keptLayerIds: ['a'], - }, - ]); - - datasource.getLayers.mockReturnValue(['a']); - return { - testDatasource: datasource, - }; - } - function showFlyout(instance: ReactWrapper) { instance.find('[data-test-subj="lnsChartSwitchPopover"]').first().simulate('click'); } @@ -178,10 +158,10 @@ describe('chart_switch', () => { return instance.find(`[data-test-subj="lnsChartSwitchPopover_${subType}"]`).first(); } it('should use suggested state if there is a suggestion from the target visualization', async () => { - const visualizations = mockVisualizations(); + const visualizationMap = mockVisualizationMap(); const { instance, lensStore } = await mountWithProvider( , @@ -212,19 +192,20 @@ describe('chart_switch', () => { }); it('should use initial state if there is no suggestion from the target visualization', async () => { - const visualizations = mockVisualizations(); - visualizations.visB.getSuggestions.mockReturnValueOnce([]); + const visualizationMap = mockVisualizationMap(); + visualizationMap.visB.getSuggestions.mockReturnValueOnce([]); const frame = mockFrame(['a']); (frame.datasourceLayers.a.getTableSpec as jest.Mock).mockReturnValue([]); const datasourceMap = mockDatasourceMap(); const datasourceStates = mockDatasourceStates(); const { instance, lensStore } = await mountWithProvider( , { + storeDeps: mockStoreDeps({ datasourceMap, visualizationMap }), preloadedState: { datasourceStates, activeDatasourceId: 'testDatasource', @@ -249,18 +230,14 @@ describe('chart_switch', () => { }, }); expect(lensStore.dispatch).toHaveBeenCalledWith({ - type: 'lens/updateLayer', - payload: expect.objectContaining({ - datasourceId: 'testDatasource', - layerId: 'a', - }), + type: 'lens/removeLayers', + payload: { layerIds: ['a'], visualizationId: 'visA' }, }); }); it('should indicate data loss if not all columns will be used', async () => { - const visualizations = mockVisualizations(); + const visualizationMap = mockVisualizationMap(); const frame = mockFrame(['a']); - const datasourceMap = mockDatasourceMap(); datasourceMap.testDatasource.getDatasourceSuggestionsFromCurrentState.mockReturnValue([ { @@ -299,9 +276,9 @@ describe('chart_switch', () => { const { instance } = await mountWithProvider( , { preloadedState: { @@ -322,12 +299,11 @@ describe('chart_switch', () => { }); it('should indicate data loss if not all layers will be used', async () => { - const visualizations = mockVisualizations(); + const visualizationMap = mockVisualizationMap(); const frame = mockFrame(['a', 'b']); - const { instance } = await mountWithProvider( , @@ -350,9 +326,8 @@ describe('chart_switch', () => { }); it('should support multi-layer suggestions without data loss', async () => { - const visualizations = mockVisualizations(); + const visualizationMap = mockVisualizationMap(); const frame = mockFrame(['a', 'b']); - const datasourceMap = mockDatasourceMap(); datasourceMap.testDatasource.getDatasourceSuggestionsFromCurrentState.mockReturnValue([ { @@ -378,7 +353,7 @@ describe('chart_switch', () => { const { instance } = await mountWithProvider( , @@ -398,13 +373,13 @@ describe('chart_switch', () => { }); it('should indicate data loss if no data will be used', async () => { - const visualizations = mockVisualizations(); - visualizations.visB.getSuggestions.mockReturnValueOnce([]); + const visualizationMap = mockVisualizationMap(); + visualizationMap.visB.getSuggestions.mockReturnValueOnce([]); const frame = mockFrame(['a']); const { instance } = await mountWithProvider( , @@ -427,14 +402,14 @@ describe('chart_switch', () => { }); it('should not indicate data loss if there is no data', async () => { - const visualizations = mockVisualizations(); - visualizations.visB.getSuggestions.mockReturnValueOnce([]); + const visualizationMap = mockVisualizationMap(); + visualizationMap.visB.getSuggestions.mockReturnValueOnce([]); const frame = mockFrame(['a']); (frame.datasourceLayers.a.getTableSpec as jest.Mock).mockReturnValue([]); const { instance } = await mountWithProvider( , @@ -456,20 +431,19 @@ describe('chart_switch', () => { it('should not show a warning when the subvisualization is the same', async () => { const frame = mockFrame(['a', 'b', 'c']); - const visualizations = mockVisualizations(); - visualizations.visC.getVisualizationTypeId.mockReturnValue('subvisC2'); + const visualizationMap = mockVisualizationMap(); + visualizationMap.visC.getVisualizationTypeId.mockReturnValue('subvisC2'); const switchVisualizationType = jest.fn(() => ({ type: 'subvisC1' })); - visualizations.visC.switchVisualizationType = switchVisualizationType; + visualizationMap.visC.switchVisualizationType = switchVisualizationType; - const datasourceMap = mockDatasourceMap(); const datasourceStates = mockDatasourceStates(); const { instance } = await mountWithProvider( , { preloadedState: { @@ -491,8 +465,8 @@ describe('chart_switch', () => { }); it('should get suggestions when switching subvisualization', async () => { - const visualizations = mockVisualizations(); - visualizations.visB.getSuggestions.mockReturnValueOnce([]); + const visualizationMap = mockVisualizationMap(); + visualizationMap.visB.getSuggestions.mockReturnValueOnce([]); const frame = mockFrame(['a', 'b', 'c']); const datasourceMap = mockDatasourceMap(); datasourceMap.testDatasource.getLayers.mockReturnValue(['a', 'b', 'c']); @@ -500,11 +474,12 @@ describe('chart_switch', () => { const { instance, lensStore } = await mountWithProvider( , { + storeDeps: mockStoreDeps({ datasourceMap, visualizationMap }), preloadedState: { datasourceStates, visualization: { @@ -519,7 +494,7 @@ describe('chart_switch', () => { expect(datasourceMap.testDatasource.removeLayer).toHaveBeenCalledWith({}, 'a'); expect(datasourceMap.testDatasource.removeLayer).toHaveBeenCalledWith(undefined, 'b'); expect(datasourceMap.testDatasource.removeLayer).toHaveBeenCalledWith(undefined, 'c'); - expect(visualizations.visB.getSuggestions).toHaveBeenCalledWith( + expect(visualizationMap.visB.getSuggestions).toHaveBeenCalledWith( expect.objectContaining({ keptLayerIds: ['a'], }) @@ -540,19 +515,18 @@ describe('chart_switch', () => { }); it('should query main palette from active chart and pass into suggestions', async () => { - const visualizations = mockVisualizations(); + const visualizationMap = mockVisualizationMap(); const mockPalette: PaletteOutput = { type: 'palette', name: 'mock' }; - visualizations.visA.getMainPalette = jest.fn(() => mockPalette); - visualizations.visB.getSuggestions.mockReturnValueOnce([]); + visualizationMap.visA.getMainPalette = jest.fn(() => mockPalette); + visualizationMap.visB.getSuggestions.mockReturnValueOnce([]); const frame = mockFrame(['a', 'b', 'c']); const currentVisState = {}; - const datasourceMap = mockDatasourceMap(); datasourceMap.testDatasource.getLayers.mockReturnValue(['a', 'b', 'c']); const { instance } = await mountWithProvider( , @@ -563,14 +537,15 @@ describe('chart_switch', () => { state: currentVisState, }, }, + storeDeps: mockStoreDeps({ datasourceMap, visualizationMap }), } ); switchTo('visB', instance); - expect(visualizations.visA.getMainPalette).toHaveBeenCalledWith(currentVisState); + expect(visualizationMap.visA.getMainPalette).toHaveBeenCalledWith(currentVisState); - expect(visualizations.visB.getSuggestions).toHaveBeenCalledWith( + expect(visualizationMap.visB.getSuggestions).toHaveBeenCalledWith( expect.objectContaining({ keptLayerIds: ['a'], mainPalette: mockPalette, @@ -580,14 +555,14 @@ describe('chart_switch', () => { it('should not remove layers when switching between subtypes', async () => { const frame = mockFrame(['a', 'b', 'c']); - const visualizations = mockVisualizations(); + const visualizationMap = mockVisualizationMap(); const switchVisualizationType = jest.fn(() => 'switched'); - visualizations.visC.switchVisualizationType = switchVisualizationType; + visualizationMap.visC.switchVisualizationType = switchVisualizationType; const datasourceMap = mockDatasourceMap(); const { instance, lensStore } = await mountWithProvider( , @@ -598,6 +573,7 @@ describe('chart_switch', () => { state: { type: 'subvisC1' }, }, }, + storeDeps: mockStoreDeps({ datasourceMap, visualizationMap }), } ); @@ -622,13 +598,13 @@ describe('chart_switch', () => { it('should not remove layers and initialize with existing state when switching between subtypes without data', async () => { const frame = mockFrame(['a']); frame.datasourceLayers.a.getTableSpec = jest.fn().mockReturnValue([]); - const visualizations = mockVisualizations(); - visualizations.visC.getSuggestions = jest.fn().mockReturnValue([]); - visualizations.visC.switchVisualizationType = jest.fn(() => 'switched'); + const visualizationMap = mockVisualizationMap(); + visualizationMap.visC.getSuggestions = jest.fn().mockReturnValue([]); + visualizationMap.visC.switchVisualizationType = jest.fn(() => 'switched'); const datasourceMap = mockDatasourceMap(); const { instance } = await mountWithProvider( , @@ -639,21 +615,21 @@ describe('chart_switch', () => { state: { type: 'subvisC1' }, }, }, + storeDeps: mockStoreDeps({ datasourceMap, visualizationMap }), } ); switchTo('subvisC3', instance); - expect(visualizations.visC.switchVisualizationType).toHaveBeenCalledWith('subvisC3', { + expect(visualizationMap.visC.switchVisualizationType).toHaveBeenCalledWith('subvisC3', { type: 'subvisC1', }); expect(datasourceMap.testDatasource.removeLayer).not.toHaveBeenCalled(); }); it('should switch to the updated datasource state', async () => { - const visualizations = mockVisualizations(); + const visualizationMap = mockVisualizationMap(); const frame = mockFrame(['a', 'b']); - const datasourceMap = mockDatasourceMap(); datasourceMap.testDatasource.getDatasourceSuggestionsFromCurrentState.mockReturnValue([ { @@ -684,14 +660,14 @@ describe('chart_switch', () => { keptLayerIds: [], }, ]); - const { instance, lensStore } = await mountWithProvider( , { + storeDeps: mockStoreDeps({ datasourceMap, visualizationMap }), preloadedState: { visualization: { activeId: 'visA', @@ -718,20 +694,21 @@ describe('chart_switch', () => { }); it('should ensure the new visualization has the proper subtype', async () => { - const visualizations = mockVisualizations(); + const visualizationMap = mockVisualizationMap(); const switchVisualizationType = jest.fn( (visualizationType, state) => `${state} ${visualizationType}` ); - visualizations.visB.switchVisualizationType = switchVisualizationType; - + visualizationMap.visB.switchVisualizationType = switchVisualizationType; + const datasourceMap = mockDatasourceMap(); const { instance, lensStore } = await mountWithProvider( , { + storeDeps: mockStoreDeps({ datasourceMap, visualizationMap }), preloadedState: { visualization: { activeId: 'visA', @@ -758,16 +735,16 @@ describe('chart_switch', () => { }); it('should use the suggestion that matches the subtype', async () => { - const visualizations = mockVisualizations(); + const visualizationMap = mockVisualizationMap(); const switchVisualizationType = jest.fn(); - visualizations.visC.switchVisualizationType = switchVisualizationType; - + visualizationMap.visC.switchVisualizationType = switchVisualizationType; + const datasourceMap = mockDatasourceMap(); const { instance } = await mountWithProvider( , { preloadedState: { @@ -787,11 +764,12 @@ describe('chart_switch', () => { }); it('should show all visualization types', async () => { + const datasourceMap = mockDatasourceMap(); const { instance } = await mountWithProvider( , { preloadedState: { diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx index a5ba12941cf7f..427306cb54fb9 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx @@ -31,8 +31,8 @@ import { getSuggestions, switchToSuggestion, Suggestion } from '../suggestion_he import { trackUiEvent } from '../../../lens_ui_telemetry'; import { ToolbarButton } from '../../../../../../../src/plugins/kibana_react/public'; import { - updateLayer, - updateVisualizationState, + insertLayer, + removeLayers, useLensDispatch, useLensSelector, VisualizationState, @@ -120,41 +120,6 @@ export const ChartSwitch = memo(function ChartSwitch(props: Props) { const visualization = useLensSelector(selectVisualization); const datasourceStates = useLensSelector(selectDatasourceStates); - function removeLayers(layerIds: string[]) { - const activeVisualization = - visualization.activeId && props.visualizationMap[visualization.activeId]; - if (activeVisualization && activeVisualization.removeLayer && visualization.state) { - dispatchLens( - updateVisualizationState({ - visualizationId: activeVisualization.id, - updater: layerIds.reduce( - (acc, layerId) => - activeVisualization.removeLayer ? activeVisualization.removeLayer(acc, layerId) : acc, - visualization.state - ), - }) - ); - } - layerIds.forEach((layerId) => { - const [layerDatasourceId] = - Object.entries(props.datasourceMap).find(([datasourceId, datasource]) => { - return ( - datasourceStates[datasourceId] && - datasource.getLayers(datasourceStates[datasourceId].state).includes(layerId) - ); - }) ?? []; - if (layerDatasourceId) { - dispatchLens( - updateLayer({ - layerId, - datasourceId: layerDatasourceId, - updater: props.datasourceMap[layerDatasourceId].removeLayer, - }) - ); - } - }); - } - const commitSelection = (selection: VisualizationSelection) => { setFlyoutOpen(false); @@ -173,7 +138,12 @@ export const ChartSwitch = memo(function ChartSwitch(props: Props) { (!selection.datasourceId && !selection.sameDatasources) || selection.dataLoss === 'everything' ) { - removeLayers(Object.keys(props.framePublicAPI.datasourceLayers)); + dispatchLens( + removeLayers({ + visualizationId: visualization.activeId, + layerIds: Object.keys(props.framePublicAPI.datasourceLayers), + }) + ); } }; @@ -231,13 +201,11 @@ export const ChartSwitch = memo(function ChartSwitch(props: Props) { function addNewLayer() { const newLayerId = generateId(); dispatchLens( - updateLayer({ + insertLayer({ datasourceId: activeDatasourceId!, layerId: newLayerId, - updater: props.datasourceMap[activeDatasourceId!].insertLayer, }) ); - return newLayerId; } diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx index a03c9292d2da8..4b98b2842a01b 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx @@ -52,7 +52,7 @@ import { DefaultInspectorAdapters } from '../../../../../../../src/plugins/expre import { onActiveDataChange, useLensDispatch, - updateVisualizationState, + editVisualizationAction, updateDatasourceState, setSaveable, useLensSelector, @@ -246,9 +246,9 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({ } if (isLensEditEvent(event) && activeVisualization?.onEditAction) { dispatchLens( - updateVisualizationState({ + editVisualizationAction({ visualizationId: activeVisualization.id, - updater: (oldState: unknown) => activeVisualization.onEditAction!(oldState, event), + event, }) ); } diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel_wrapper.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel_wrapper.tsx index d8959e714d16e..f13ecb78593d9 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel_wrapper.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel_wrapper.tsx @@ -54,8 +54,7 @@ export function WorkspacePanelWrapper({ dispatchLens( updateVisualizationState({ visualizationId: activeVisualization.id, - updater: newState, - clearStagedPreview: false, + newState, }) ); }, diff --git a/x-pack/plugins/lens/public/state_management/index.ts b/x-pack/plugins/lens/public/state_management/index.ts index 0aa7185931c5a..5f3b60d95d77d 100644 --- a/x-pack/plugins/lens/public/state_management/index.ts +++ b/x-pack/plugins/lens/public/state_management/index.ts @@ -25,13 +25,18 @@ export const { updateState, updateDatasourceState, updateVisualizationState, - updateLayer, + insertLayer, switchVisualization, rollbackSuggestion, submitSuggestion, switchDatasource, setToggleFullscreen, initEmpty, + editVisualizationAction, + removeLayers, + removeOrClearLayer, + addLayer, + setLayerDefaultDimension, } = lensActions; export const makeConfigureStore = ( diff --git a/x-pack/plugins/lens/public/state_management/lens_slice.test.ts b/x-pack/plugins/lens/public/state_management/lens_slice.test.ts index 7d88e6ceb616c..85061f36ce35e 100644 --- a/x-pack/plugins/lens/public/state_management/lens_slice.test.ts +++ b/x-pack/plugins/lens/public/state_management/lens_slice.test.ts @@ -13,8 +13,13 @@ import { updateState, updateDatasourceState, updateVisualizationState, + removeOrClearLayer, + addLayer, + LensRootStore, } from '.'; -import { makeLensStore, defaultState } from '../mocks'; +import { layerTypes } from '../../common'; +import { makeLensStore, defaultState, mockStoreDeps } from '../mocks'; +import { DatasourceMap, VisualizationMap } from '../types'; describe('lensSlice', () => { const { store } = makeLensStore({}); @@ -31,7 +36,7 @@ describe('lensSlice', () => { it('updateState: updates state with updater', () => { const customUpdater = jest.fn((state) => ({ ...state, query: customQuery })); - store.dispatch(updateState({ subType: 'UPDATE', updater: customUpdater })); + store.dispatch(updateState({ updater: customUpdater })); const changedState = store.getState().lens; expect(changedState).toEqual({ ...defaultState, query: customQuery }); }); @@ -40,7 +45,7 @@ describe('lensSlice', () => { store.dispatch( updateVisualizationState({ visualizationId: 'testVis', - updater: newVisState, + newState: newVisState, }) ); @@ -117,7 +122,7 @@ describe('lensSlice', () => { expect(store.getState().lens.datasourceStates.testDatasource2.isLoading).toEqual(true); }); - it('not initialize already initialized datasource on switch', () => { + it('should not initialize already initialized datasource on switch', () => { const datasource2State = {}; const { store: customStore } = makeLensStore({ preloadedState: { @@ -146,5 +151,109 @@ describe('lensSlice', () => { datasource2State ); }); + + describe('adding or removing layer', () => { + const testDatasource = (datasourceId: string) => { + return { + id: datasourceId, + getPublicAPI: () => ({ + datasourceId: 'testDatasource', + getOperationForColumnId: jest.fn(), + getTableSpec: jest.fn(), + }), + getLayers: () => ['layer1'], + clearLayer: (layerIds: unknown, layerId: string) => + (layerIds as string[]).map((id: string) => + id === layerId ? `${datasourceId}_clear_${layerId}` : id + ), + removeLayer: (layerIds: unknown, layerId: string) => + (layerIds as string[]).filter((id: string) => id !== layerId), + insertLayer: (layerIds: unknown, layerId: string) => [...(layerIds as string[]), layerId], + }; + }; + const datasourceStates = { + testDatasource: { + isLoading: false, + state: ['layer1'], + }, + testDatasource2: { + isLoading: false, + state: ['layer2'], + }, + }; + const datasourceMap = { + testDatasource: testDatasource('testDatasource'), + testDatasource2: testDatasource('testDatasource2'), + }; + const visualizationMap = { + testVis: { + clearLayer: (layerIds: unknown, layerId: string) => + (layerIds as string[]).map((id: string) => + id === layerId ? `vis_clear_${layerId}` : id + ), + removeLayer: (layerIds: unknown, layerId: string) => + (layerIds as string[]).filter((id: string) => id !== layerId), + getLayerIds: (layerIds: unknown) => layerIds as string[], + appendLayer: (layerIds: unknown, layerId: string) => [...(layerIds as string[]), layerId], + getSupportedLayers: jest.fn(() => [{ type: layerTypes.DATA, label: 'Data Layer' }]), + }, + }; + + let customStore: LensRootStore; + beforeEach(() => { + customStore = makeLensStore({ + preloadedState: { + activeDatasourceId: 'testDatasource', + datasourceStates, + visualization: { + activeId: 'testVis', + state: ['layer1', 'layer2'], + }, + stagedPreview: { + visualization: { + activeId: 'testVis', + state: ['layer1', 'layer2'], + }, + datasourceStates, + }, + }, + storeDeps: mockStoreDeps({ + visualizationMap: visualizationMap as unknown as VisualizationMap, + datasourceMap: datasourceMap as unknown as DatasourceMap, + }), + }).store; + }); + + it('addLayer: should add the layer to the datasource and visualization', () => { + customStore.dispatch( + addLayer({ + layerId: 'foo', + layerType: layerTypes.DATA, + }) + ); + const state = customStore.getState().lens; + + expect(state.visualization.state).toEqual(['layer1', 'layer2', 'foo']); + expect(state.datasourceStates.testDatasource.state).toEqual(['layer1', 'foo']); + expect(state.datasourceStates.testDatasource2.state).toEqual(['layer2']); + expect(state.stagedPreview).not.toBeDefined(); + }); + + it('removeLayer: should remove the layer if it is not the only layer', () => { + customStore.dispatch( + removeOrClearLayer({ + visualizationId: 'testVis', + layerId: 'layer1', + layerIds: ['layer1', 'layer2'], + }) + ); + const state = customStore.getState().lens; + + expect(state.visualization.state).toEqual(['layer2']); + expect(state.datasourceStates.testDatasource.state).toEqual([]); + expect(state.datasourceStates.testDatasource2.state).toEqual(['layer2']); + expect(state.stagedPreview).not.toBeDefined(); + }); + }); }); }); diff --git a/x-pack/plugins/lens/public/state_management/lens_slice.ts b/x-pack/plugins/lens/public/state_management/lens_slice.ts index df178cadf6c30..af9897581fcf4 100644 --- a/x-pack/plugins/lens/public/state_management/lens_slice.ts +++ b/x-pack/plugins/lens/public/state_management/lens_slice.ts @@ -7,16 +7,22 @@ import { createAction, createReducer, current, PayloadAction } from '@reduxjs/toolkit'; import { VisualizeFieldContext } from 'src/plugins/ui_actions/public'; +import { mapValues } from 'lodash'; import { History } from 'history'; import { LensEmbeddableInput } from '..'; +import { getDatasourceLayers } from '../editor_frame_service/editor_frame'; import { TableInspectorAdapter } from '../editor_frame_service/types'; -import { getInitialDatasourceId, getResolvedDateRange } from '../utils'; -import { LensAppState, LensStoreDeps } from './types'; +import { getInitialDatasourceId, getResolvedDateRange, getRemoveOperation } from '../utils'; +import { LensAppState, LensStoreDeps, VisualizationState } from './types'; +import { Datasource, Visualization } from '../types'; import { generateId } from '../id_generator'; +import type { LayerType } from '../../common/types'; +import { getLayerType } from '../editor_frame_service/editor_frame/config_panel/add_layer'; import { getVisualizeFieldSuggestions, Suggestion, } from '../editor_frame_service/editor_frame/suggestion_helpers'; +import { FramePublicAPI, LensEditContextMapping, LensEditEvent } from '../types'; export const initialState: LensAppState = { persistedDoc: undefined, @@ -80,7 +86,6 @@ export const setState = createAction>('lens/setState'); export const onActiveDataChange = createAction('lens/onActiveDataChange'); export const setSaveable = createAction('lens/setSaveable'); export const updateState = createAction<{ - subType: string; updater: (prevState: LensAppState) => LensAppState; }>('lens/updateState'); export const updateDatasourceState = createAction<{ @@ -90,15 +95,13 @@ export const updateDatasourceState = createAction<{ }>('lens/updateDatasourceState'); export const updateVisualizationState = createAction<{ visualizationId: string; - updater: unknown; - clearStagedPreview?: boolean; + newState: unknown; }>('lens/updateVisualizationState'); -export const updateLayer = createAction<{ +export const insertLayer = createAction<{ layerId: string; datasourceId: string; - updater: (state: unknown, layerId: string) => unknown; -}>('lens/updateLayer'); +}>('lens/insertLayer'); export const switchVisualization = createAction<{ suggestion: { @@ -133,6 +136,29 @@ export const initEmpty = createAction( return { payload: { layerId: generateId(), newState, initialContext } }; } ); +export const editVisualizationAction = createAction<{ + visualizationId: string; + event: LensEditEvent; +}>('lens/editVisualizationAction'); +export const removeLayers = createAction<{ + visualizationId: VisualizationState['activeId']; + layerIds: string[]; +}>('lens/removeLayers'); +export const removeOrClearLayer = createAction<{ + visualizationId: string; + layerId: string; + layerIds: string[]; +}>('lens/removeOrClearLayer'); +export const addLayer = createAction<{ + layerId: string; + layerType: LayerType; +}>('lens/addLayer'); + +export const setLayerDefaultDimension = createAction<{ + layerId: string; + columnId: string; + groupId: string; +}>('lens/setLayerDefaultDimension'); export const lensActions = { setState, @@ -141,7 +167,7 @@ export const lensActions = { updateState, updateDatasourceState, updateVisualizationState, - updateLayer, + insertLayer, switchVisualization, rollbackSuggestion, setToggleFullscreen, @@ -150,6 +176,11 @@ export const lensActions = { navigateAway, loadInitial, initEmpty, + editVisualizationAction, + removeLayers, + removeOrClearLayer, + addLayer, + setLayerDefaultDimension, }; export const makeLensReducer = (storeDeps: LensStoreDeps) => { @@ -175,14 +206,58 @@ export const makeLensReducer = (storeDeps: LensStoreDeps) => { }, [updateState.type]: ( state, - action: { + { + payload: { updater }, + }: { payload: { - subType: string; updater: (prevState: LensAppState) => LensAppState; }; } ) => { - return action.payload.updater(current(state) as LensAppState); + const newState = updater(current(state) as LensAppState); + return { + ...newState, + stagedPreview: undefined, + }; + }, + [removeOrClearLayer.type]: ( + state, + { + payload: { visualizationId, layerId, layerIds }, + }: { + payload: { + visualizationId: string; + layerId: string; + layerIds: string[]; + }; + } + ) => { + const activeVisualization = visualizationMap[visualizationId]; + const isOnlyLayer = + getRemoveOperation( + activeVisualization, + state.visualization.state, + layerId, + layerIds.length + ) === 'clear'; + + state.datasourceStates = mapValues( + state.datasourceStates, + (datasourceState, datasourceId) => { + const datasource = datasourceMap[datasourceId!]; + return { + ...datasourceState, + state: isOnlyLayer + ? datasource.clearLayer(datasourceState.state, layerId) + : datasource.removeLayer(datasourceState.state, layerId), + }; + } + ); + state.stagedPreview = undefined; + state.visualization.state = + isOnlyLayer || !activeVisualization.removeLayer + ? activeVisualization.clearLayer(state.visualization.state, layerId) + : activeVisualization.removeLayer(state.visualization.state, layerId); }, [updateDatasourceState.type]: ( state, @@ -218,8 +293,7 @@ export const makeLensReducer = (storeDeps: LensStoreDeps) => { }: { payload: { visualizationId: string; - updater: unknown | ((state: unknown) => unknown); - clearStagedPreview?: boolean; + newState: unknown; }; } ) => { @@ -236,37 +310,7 @@ export const makeLensReducer = (storeDeps: LensStoreDeps) => { ...state, visualization: { ...state.visualization, - state: - typeof payload.updater === 'function' - ? payload.updater(current(state.visualization.state)) - : payload.updater, - }, - stagedPreview: payload.clearStagedPreview ? undefined : state.stagedPreview, - }; - }, - [updateLayer.type]: ( - state, - { - payload, - }: { - payload: { - layerId: string; - datasourceId: string; - updater: (state: unknown, layerId: string) => unknown; - }; - } - ) => { - return { - ...state, - datasourceStates: { - ...state.datasourceStates, - [payload.datasourceId]: { - ...state.datasourceStates[payload.datasourceId], - state: payload.updater( - current(state).datasourceStates[payload.datasourceId].state, - payload.layerId - ), - }, + state: payload.newState, }, }; }, @@ -433,5 +477,248 @@ export const makeLensReducer = (storeDeps: LensStoreDeps) => { } return newState; }, + [editVisualizationAction.type]: ( + state, + { + payload, + }: { + payload: { + visualizationId: string; + event: LensEditEvent; + }; + } + ) => { + if (!state.visualization.activeId) { + throw new Error('Invariant: visualization state got updated without active visualization'); + } + // This is a safeguard that prevents us from accidentally updating the + // wrong visualization. This occurs in some cases due to the uncoordinated + // way we manage state across plugins. + if (state.visualization.activeId !== payload.visualizationId) { + return state; + } + const activeVisualization = visualizationMap[payload.visualizationId]; + if (activeVisualization?.onEditAction) { + state.visualization.state = activeVisualization.onEditAction( + state.visualization.state, + payload.event + ); + } + }, + [insertLayer.type]: ( + state, + { + payload, + }: { + payload: { + layerId: string; + datasourceId: string; + }; + } + ) => { + const updater = datasourceMap[payload.datasourceId].insertLayer; + return { + ...state, + datasourceStates: { + ...state.datasourceStates, + [payload.datasourceId]: { + ...state.datasourceStates[payload.datasourceId], + state: updater( + current(state).datasourceStates[payload.datasourceId].state, + payload.layerId + ), + }, + }, + }; + }, + [removeLayers.type]: ( + state, + { + payload: { visualizationId, layerIds }, + }: { + payload: { + visualizationId: VisualizationState['activeId']; + layerIds: string[]; + }; + } + ) => { + if (!state.visualization.activeId) { + throw new Error('Invariant: visualization state got updated without active visualization'); + } + + const activeVisualization = visualizationId && visualizationMap[visualizationId]; + + // This is a safeguard that prevents us from accidentally updating the + // wrong visualization. This occurs in some cases due to the uncoordinated + // way we manage state across plugins. + if ( + state.visualization.activeId === visualizationId && + activeVisualization && + activeVisualization.removeLayer && + state.visualization.state + ) { + const updater = layerIds.reduce( + (acc, layerId) => + activeVisualization.removeLayer ? activeVisualization.removeLayer(acc, layerId) : acc, + state.visualization.state + ); + + state.visualization.state = + typeof updater === 'function' ? updater(current(state.visualization.state)) : updater; + } + layerIds.forEach((layerId) => { + const [layerDatasourceId] = + Object.entries(datasourceMap).find(([datasourceId, datasource]) => { + return ( + state.datasourceStates[datasourceId] && + datasource.getLayers(state.datasourceStates[datasourceId].state).includes(layerId) + ); + }) ?? []; + if (layerDatasourceId) { + state.datasourceStates[layerDatasourceId].state = datasourceMap[ + layerDatasourceId + ].removeLayer(current(state).datasourceStates[layerDatasourceId].state, layerId); + } + }); + }, + + [addLayer.type]: ( + state, + { + payload: { layerId, layerType }, + }: { + payload: { + layerId: string; + layerType: LayerType; + }; + } + ) => { + if (!state.activeDatasourceId || !state.visualization.activeId) { + return state; + } + + const activeDatasource = datasourceMap[state.activeDatasourceId]; + const activeVisualization = visualizationMap[state.visualization.activeId]; + + const datasourceState = activeDatasource.insertLayer( + state.datasourceStates[state.activeDatasourceId].state, + layerId + ); + + const visualizationState = activeVisualization.appendLayer!( + state.visualization.state, + layerId, + layerType + ); + + const { activeDatasourceState, activeVisualizationState } = addInitialValueIfAvailable({ + datasourceState, + visualizationState, + framePublicAPI: { + // any better idea to avoid `as`? + activeData: state.activeData as TableInspectorAdapter, + datasourceLayers: getDatasourceLayers(state.datasourceStates, datasourceMap), + }, + activeVisualization, + activeDatasource, + layerId, + layerType, + }); + + state.visualization.state = activeVisualizationState; + state.datasourceStates[state.activeDatasourceId].state = activeDatasourceState; + state.stagedPreview = undefined; + }, + [setLayerDefaultDimension.type]: ( + state, + { + payload: { layerId, columnId, groupId }, + }: { + payload: { + layerId: string; + columnId: string; + groupId: string; + }; + } + ) => { + if (!state.activeDatasourceId || !state.visualization.activeId) { + return state; + } + + const activeDatasource = datasourceMap[state.activeDatasourceId]; + const activeVisualization = visualizationMap[state.visualization.activeId]; + const layerType = getLayerType(activeVisualization, state.visualization.state, layerId); + const { activeDatasourceState, activeVisualizationState } = addInitialValueIfAvailable({ + datasourceState: state.datasourceStates[state.activeDatasourceId].state, + visualizationState: state.visualization.state, + framePublicAPI: { + // any better idea to avoid `as`? + activeData: state.activeData as TableInspectorAdapter, + datasourceLayers: getDatasourceLayers(state.datasourceStates, datasourceMap), + }, + activeVisualization, + activeDatasource, + layerId, + layerType, + columnId, + groupId, + }); + + state.visualization.state = activeVisualizationState; + state.datasourceStates[state.activeDatasourceId].state = activeDatasourceState; + }, }); }; + +function addInitialValueIfAvailable({ + visualizationState, + datasourceState, + activeVisualization, + activeDatasource, + framePublicAPI, + layerType, + layerId, + columnId, + groupId, +}: { + framePublicAPI: FramePublicAPI; + visualizationState: unknown; + datasourceState: unknown; + activeDatasource: Datasource; + activeVisualization: Visualization; + layerId: string; + layerType: string; + columnId?: string; + groupId?: string; +}) { + const layerInfo = activeVisualization + .getSupportedLayers(visualizationState, framePublicAPI) + .find(({ type }) => type === layerType); + + if (layerInfo?.initialDimensions && activeDatasource?.initializeDimension) { + const info = groupId + ? layerInfo.initialDimensions.find(({ groupId: id }) => id === groupId) + : // pick the first available one if not passed + layerInfo.initialDimensions[0]; + + if (info) { + return { + activeDatasourceState: activeDatasource.initializeDimension(datasourceState, layerId, { + ...info, + columnId: columnId || info.columnId, + }), + activeVisualizationState: activeVisualization.setDimension({ + groupId: info.groupId, + layerId, + columnId: columnId || info.columnId, + prevState: visualizationState, + frame: framePublicAPI, + }), + }; + } + } + return { + activeDatasourceState: datasourceState, + activeVisualizationState: visualizationState, + }; +} diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index e207f2938dd3c..975e44f703959 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -776,7 +776,7 @@ export interface LensBrushEvent { } // Use same technique as TriggerContext -interface LensEditContextMapping { +export interface LensEditContextMapping { [LENS_EDIT_SORT_ACTION]: LensSortActionData; [LENS_EDIT_RESIZE_ACTION]: LensResizeActionData; [LENS_TOGGLE_ACTION]: LensToggleActionData; diff --git a/x-pack/plugins/lens/public/utils.ts b/x-pack/plugins/lens/public/utils.ts index 993be9a06a2d9..921cc8fb364a2 100644 --- a/x-pack/plugins/lens/public/utils.ts +++ b/x-pack/plugins/lens/public/utils.ts @@ -16,8 +16,8 @@ import type { import type { IUiSettingsClient } from 'kibana/public'; import type { SavedObjectReference } from 'kibana/public'; import type { Document } from './persistence/saved_object_store'; -import type { Datasource, DatasourceMap } from './types'; -import type { DatasourceStates } from './state_management'; +import type { Datasource, DatasourceMap, Visualization } from './types'; +import type { DatasourceStates, VisualizationState } from './state_management'; export function getVisualizeGeoFieldMessage(fieldType: string) { return i18n.translate('xpack.lens.visualizeGeoFieldMessage', { @@ -94,3 +94,16 @@ export async function getIndexPatternsObjects( // return also the rejected ids in case we want to show something later on return { indexPatterns: fullfilled.map((response) => response.value), rejectedIds }; } + +export function getRemoveOperation( + activeVisualization: Visualization, + visualizationState: VisualizationState['state'], + layerId: string, + layerCount: number +) { + if (activeVisualization.getRemoveOperation) { + return activeVisualization.getRemoveOperation(visualizationState, layerId); + } + // fallback to generic count check + return layerCount === 1 ? 'clear' : 'remove'; +} From c84d20b8924e3a5dd3fc6ad8f0730fc6b8758298 Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Wed, 3 Nov 2021 13:00:21 +0300 Subject: [PATCH 34/41] [TSVB] Fix cannot override the timerange mode on the override index pattern series settings (#116955) Closes: #116745 Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../timeseries/common/types/panel_model.ts | 1 - .../application/components/aggs/agg.tsx | 22 +++++++++++++------ .../application/components/aggs/aggs.tsx | 2 +- .../components/aggs/histogram_support.test.js | 2 +- .../application/components/index_pattern.js | 10 ++++++--- .../lib/convert_series_to_datatable.test.ts | 1 - .../components/vis_types/timeseries/series.js | 2 -- 7 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/plugins/vis_types/timeseries/common/types/panel_model.ts b/src/plugins/vis_types/timeseries/common/types/panel_model.ts index f71602fdf0443..b4b167310a194 100644 --- a/src/plugins/vis_types/timeseries/common/types/panel_model.ts +++ b/src/plugins/vis_types/timeseries/common/types/panel_model.ts @@ -115,7 +115,6 @@ export interface Series { terms_size?: string; time_range_mode?: string; trend_arrows?: number; - type?: string; value_template?: string; var_name?: string; } diff --git a/src/plugins/vis_types/timeseries/public/application/components/aggs/agg.tsx b/src/plugins/vis_types/timeseries/public/application/components/aggs/agg.tsx index 08f8c072eef3b..bc74be2a562f9 100644 --- a/src/plugins/vis_types/timeseries/public/application/components/aggs/agg.tsx +++ b/src/plugins/vis_types/timeseries/public/application/components/aggs/agg.tsx @@ -33,13 +33,13 @@ interface AggProps extends HTMLAttributes { siblings: Metric[]; uiRestrictions: TimeseriesUIRestrictions; dragHandleProps: DragHandleProps; - onChange: (part: Partial) => void; + onModelChange: (part: Partial) => void; onAdd: () => void; onDelete: () => void; } export function Agg(props: AggProps) { - const { model, uiRestrictions, series, name, onChange, fields, siblings } = props; + const { model, uiRestrictions, series, name, onModelChange, fields, siblings } = props; let Component = aggToComponent[model.type]; @@ -72,8 +72,8 @@ export function Agg(props: AggProps) { const isKibanaIndexPattern = props.panel.use_kibana_indexes || indexPattern === ''; const onAggChange = useMemo( - () => seriesChangeHandler({ name, model: series, onChange }, siblings), - [name, onChange, siblings, series] + () => seriesChangeHandler({ name, model: series, onChange: onModelChange }, siblings), + [name, onModelChange, siblings, series] ); useEffect(() => { @@ -86,17 +86,25 @@ export function Agg(props: AggProps) { ); if (isNumberFormatter && !isNumericMetric) { - onChange({ formatter: DATA_FORMATTERS.DEFAULT }); + onModelChange({ formatter: DATA_FORMATTERS.DEFAULT }); } // in case of string index pattern mode, change default formatter depending on metric type // "number" formatter for numeric metric and "" as custom formatter for any other type if (formatterType === DATA_FORMATTERS.DEFAULT && !isKibanaIndexPattern) { - onChange({ + onModelChange({ formatter: isNumericMetric ? DATA_FORMATTERS.NUMBER : '', }); } } - }, [indexPattern, model, onChange, fields, series.formatter, isKibanaIndexPattern, siblings]); + }, [ + indexPattern, + model, + onModelChange, + fields, + series.formatter, + isKibanaIndexPattern, + siblings, + ]); return (
diff --git a/src/plugins/vis_types/timeseries/public/application/components/aggs/aggs.tsx b/src/plugins/vis_types/timeseries/public/application/components/aggs/aggs.tsx index 516e3551fb010..f0172eaba0189 100644 --- a/src/plugins/vis_types/timeseries/public/application/components/aggs/aggs.tsx +++ b/src/plugins/vis_types/timeseries/public/application/components/aggs/aggs.tsx @@ -51,7 +51,7 @@ export class Aggs extends PureComponent { name={name} model={row} onAdd={() => handleAdd(this.props, newMetricAggFn)} - onChange={onChange} + onModelChange={onChange} onDelete={() => handleDelete(this.props, row)} panel={panel} series={model} diff --git a/src/plugins/vis_types/timeseries/public/application/components/aggs/histogram_support.test.js b/src/plugins/vis_types/timeseries/public/application/components/aggs/histogram_support.test.js index c131ba2ae804c..ff96e476814ff 100644 --- a/src/plugins/vis_types/timeseries/public/application/components/aggs/histogram_support.test.js +++ b/src/plugins/vis_types/timeseries/public/application/components/aggs/histogram_support.test.js @@ -34,7 +34,7 @@ const runTest = (aggType, name, test, additionalProps = {}) => {
{ onChange({ @@ -126,7 +130,7 @@ export const IndexPattern = ({ const selectedTimeRangeOption = timeRangeOptions.find( ({ value }) => model[TIME_RANGE_MODE_KEY] === value ); - const isTimeSeries = model.type === PANEL_TYPES.TIMESERIES; + const isDataTimerangeModeInvalid = !disabled && selectedTimeRangeOption && diff --git a/src/plugins/vis_types/timeseries/public/application/components/lib/convert_series_to_datatable.test.ts b/src/plugins/vis_types/timeseries/public/application/components/lib/convert_series_to_datatable.test.ts index bffc9200c9d6f..3df52223c253a 100644 --- a/src/plugins/vis_types/timeseries/public/application/components/lib/convert_series_to_datatable.test.ts +++ b/src/plugins/vis_types/timeseries/public/application/components/lib/convert_series_to_datatable.test.ts @@ -154,7 +154,6 @@ describe('convert series to datatables', () => { ], split_mode: 'terms', terms_field: 'Cancelled', - type: 'timeseries', }, ], } as TimeseriesVisParams; diff --git a/src/plugins/vis_types/timeseries/public/application/components/vis_types/timeseries/series.js b/src/plugins/vis_types/timeseries/public/application/components/vis_types/timeseries/series.js index 53ded44353ddb..30a5867d799cb 100644 --- a/src/plugins/vis_types/timeseries/public/application/components/vis_types/timeseries/series.js +++ b/src/plugins/vis_types/timeseries/public/application/components/vis_types/timeseries/series.js @@ -24,7 +24,6 @@ import { import { Split } from '../../split'; import { createTextHandler } from '../../lib/create_text_handler'; import { FormattedMessage, injectI18n } from '@kbn/i18n/react'; -import { PANEL_TYPES } from '../../../../../common/enums'; const TimeseriesSeriesUI = injectI18n(function (props) { const { @@ -45,7 +44,6 @@ const TimeseriesSeriesUI = injectI18n(function (props) { const defaults = { label: '', - type: PANEL_TYPES.TIMESERIES, }; const model = { ...defaults, ...props.model }; From 2ba7cdcf3053452ec797eae502e95ede9af21281 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Wed, 3 Nov 2021 11:04:04 +0100 Subject: [PATCH 35/41] use more robust way of entering value (#116566) --- test/functional/apps/visualize/_tsvb_time_series.ts | 3 +-- test/functional/page_objects/visual_builder_page.ts | 10 ++++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/test/functional/apps/visualize/_tsvb_time_series.ts b/test/functional/apps/visualize/_tsvb_time_series.ts index 4354e8bb44172..009e4a07cd42a 100644 --- a/test/functional/apps/visualize/_tsvb_time_series.ts +++ b/test/functional/apps/visualize/_tsvb_time_series.ts @@ -193,8 +193,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); }); - // FLAKY: https://github.com/elastic/kibana/issues/115529 - describe.skip('Elastic charts', () => { + describe('Elastic charts', () => { beforeEach(async () => { await visualBuilder.toggleNewChartsLibraryWithDebug(true); await visualBuilder.clickPanelOptions('timeSeries'); diff --git a/test/functional/page_objects/visual_builder_page.ts b/test/functional/page_objects/visual_builder_page.ts index f6e6caf102004..b87962b34291c 100644 --- a/test/functional/page_objects/visual_builder_page.ts +++ b/test/functional/page_objects/visual_builder_page.ts @@ -664,7 +664,10 @@ export class VisualBuilderPageObject extends FtrService { public async setBackgroundColor(colorHex: string): Promise { await this.clickColorPicker(); await this.checkColorPickerPopUpIsPresent(); - await this.find.setValue('.euiColorPicker input', colorHex); + await this.testSubjects.setValue('euiColorPickerInput_top', colorHex, { + clearWithKeyboard: true, + typeCharByChar: true, + }); await this.clickColorPicker(); await this.visChart.waitForVisualizationRenderingStabilized(); } @@ -677,7 +680,10 @@ export class VisualBuilderPageObject extends FtrService { public async setColorPickerValue(colorHex: string, nth: number = 0): Promise { await this.clickColorPicker(nth); await this.checkColorPickerPopUpIsPresent(); - await this.find.setValue('.euiColorPicker input', colorHex); + await this.testSubjects.setValue('euiColorPickerInput_top', colorHex, { + clearWithKeyboard: true, + typeCharByChar: true, + }); await this.clickColorPicker(nth); await this.visChart.waitForVisualizationRenderingStabilized(); } From b56979a97fbb81405386aaf54433820673e913ca Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Wed, 3 Nov 2021 11:04:48 +0100 Subject: [PATCH 36/41] retry visualize link navigation (#116582) --- test/functional/page_objects/visualize_page.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/test/functional/page_objects/visualize_page.ts b/test/functional/page_objects/visualize_page.ts index 7356ea3fa44c3..7624699928666 100644 --- a/test/functional/page_objects/visualize_page.ts +++ b/test/functional/page_objects/visualize_page.ts @@ -315,7 +315,10 @@ export class VisualizePageObject extends FtrService { public async openSavedVisualization(vizName: string) { const dataTestSubj = `visListingTitleLink-${vizName.split(' ').join('-')}`; - await this.testSubjects.click(dataTestSubj, 20000); + await this.retry.try(async () => { + await this.testSubjects.click(dataTestSubj, 20000); + await this.notOnLandingPageOrFail(); + }); await this.header.waitUntilLoadingHasFinished(); } @@ -337,6 +340,11 @@ export class VisualizePageObject extends FtrService { return await this.testSubjects.exists('visualizationLandingPage'); } + public async notOnLandingPageOrFail() { + this.log.debug(`VisualizePage.notOnLandingPageOrFail`); + return await this.testSubjects.missingOrFail('visualizationLandingPage'); + } + public async gotoLandingPage() { this.log.debug('VisualizePage.gotoLandingPage'); const onPage = await this.onLandingPage(); From 0681348c3a652015ee69cf5428618df5499a4859 Mon Sep 17 00:00:00 2001 From: Gloria Hornero Date: Wed, 3 Nov 2021 12:44:21 +0100 Subject: [PATCH 37/41] [Security Solution] Fixes skipped test (#115757) * waits for the page to be loaded before continuing with the actions * changes the order of the wait * unskip data provider test Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../cypress/integration/timelines/data_providers.spec.ts | 2 +- x-pack/plugins/security_solution/cypress/tasks/timeline.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/cypress/integration/timelines/data_providers.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timelines/data_providers.spec.ts index eeb7f91f8c050..de754f8602667 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timelines/data_providers.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timelines/data_providers.spec.ts @@ -20,7 +20,7 @@ import { addDataProvider, closeTimeline, createNewTimeline } from '../../tasks/t import { HOSTS_URL } from '../../urls/navigation'; import { cleanKibana, scrollToBottom } from '../../tasks/common'; -describe.skip('timeline data providers', () => { +describe('timeline data providers', () => { before(() => { cleanKibana(); loginAndWaitForPage(HOSTS_URL); diff --git a/x-pack/plugins/security_solution/cypress/tasks/timeline.ts b/x-pack/plugins/security_solution/cypress/tasks/timeline.ts index 6841a9afdc42b..01a3b6f18be80 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/timeline.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/timeline.ts @@ -175,6 +175,7 @@ export const addFilter = (filter: TimelineFilter): Cypress.Chainable> => { cy.get(TIMELINE_ADD_FIELD_BUTTON).click(); + cy.get(LOADING_INDICATOR).should('not.exist'); cy.get(TIMELINE_DATA_PROVIDER_VALUE).should('have.focus'); // make sure the focus is ready before start typing cy.get(TIMELINE_DATA_PROVIDER_FIELD) From 38a51c80c341e76b44f91311f72e2800bf8f8b9a Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 3 Nov 2021 12:28:08 +0000 Subject: [PATCH 38/41] skip flaky suite (#112677) --- .../components/overview/query_bar/use_query_bar.test.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/uptime/public/components/overview/query_bar/use_query_bar.test.tsx b/x-pack/plugins/uptime/public/components/overview/query_bar/use_query_bar.test.tsx index e4c57dab0ffcf..217d8cdfc4c9e 100644 --- a/x-pack/plugins/uptime/public/components/overview/query_bar/use_query_bar.test.tsx +++ b/x-pack/plugins/uptime/public/components/overview/query_bar/use_query_bar.test.tsx @@ -17,7 +17,8 @@ import { UptimeUrlParams } from '../../../lib/helper/url_params'; const SAMPLE_ES_FILTERS = `{"bool":{"should":[{"match_phrase":{"monitor.id":"NodeServer"}}],"minimum_should_match":1}}`; -describe('useQueryBar', () => { +// FLAKY: https://github.com/elastic/kibana/issues/112677 +describe.skip('useQueryBar', () => { let DEFAULT_URL_PARAMS: UptimeUrlParams; let wrapper: any; let useUrlParamsSpy: jest.SpyInstance<[URL.GetUrlParams, URL.UpdateUrlParams]>; From 654e75a859b46d566eea6689f502fb54624291c1 Mon Sep 17 00:00:00 2001 From: Julia Bardi <90178898+juliaElastic@users.noreply.github.com> Date: Wed, 3 Nov 2021 13:30:24 +0100 Subject: [PATCH 39/41] [Fleet] Package policy upgrade telemetry with sender (#115180) * draft of upgrade usage collector * telemetry sender service * fixed tests and types * cleanup * type fix * removed collector * made required field message generic, added test * cleanup * cleanup * cleanup * removed v3-dev as outdated * removed conditional from telemetry url creation * supporting multiple channels in sender * fix types * refactor * using json content type * fix test * simplified telemetry url * fixed type * added back ndjson * moved telemetry to update, added dryrun * fix types * fix prettier * updated after review * fix imports * added error_message field * review fixes Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/plugins/fleet/kibana.json | 2 +- x-pack/plugins/fleet/server/mocks/index.ts | 2 + x-pack/plugins/fleet/server/plugin.ts | 14 ++ .../server/routes/package_policy/handlers.ts | 3 +- .../fleet/server/services/app_context.ts | 7 + .../server/services/package_policy.test.ts | 6 + .../fleet/server/services/package_policy.ts | 76 +++---- .../server/services/upgrade_usage.test.ts | 70 +++++++ .../fleet/server/services/upgrade_usage.ts | 65 ++++++ .../fleet/server/telemetry/__mocks__/index.ts | 26 +++ .../fleet/server/telemetry/queue.test.ts | 39 ++++ .../plugins/fleet/server/telemetry/queue.ts | 40 ++++ .../fleet/server/telemetry/sender.test.ts | 143 +++++++++++++ .../plugins/fleet/server/telemetry/sender.ts | 192 ++++++++++++++++++ .../plugins/fleet/server/telemetry/types.ts | 15 ++ 15 files changed, 661 insertions(+), 39 deletions(-) create mode 100644 x-pack/plugins/fleet/server/services/upgrade_usage.test.ts create mode 100644 x-pack/plugins/fleet/server/services/upgrade_usage.ts create mode 100644 x-pack/plugins/fleet/server/telemetry/__mocks__/index.ts create mode 100644 x-pack/plugins/fleet/server/telemetry/queue.test.ts create mode 100644 x-pack/plugins/fleet/server/telemetry/queue.ts create mode 100644 x-pack/plugins/fleet/server/telemetry/sender.test.ts create mode 100644 x-pack/plugins/fleet/server/telemetry/sender.ts create mode 100644 x-pack/plugins/fleet/server/telemetry/types.ts diff --git a/x-pack/plugins/fleet/kibana.json b/x-pack/plugins/fleet/kibana.json index 1ca88cac1cc11..d516827ebf9a7 100644 --- a/x-pack/plugins/fleet/kibana.json +++ b/x-pack/plugins/fleet/kibana.json @@ -9,7 +9,7 @@ "ui": true, "configPath": ["xpack", "fleet"], "requiredPlugins": ["licensing", "data", "encryptedSavedObjects", "navigation", "customIntegrations", "share"], - "optionalPlugins": ["security", "features", "cloud", "usageCollection", "home", "globalSearch"], + "optionalPlugins": ["security", "features", "cloud", "usageCollection", "home", "globalSearch", "telemetry"], "extraPublicDirs": ["common"], "requiredBundles": ["kibanaReact", "esUiShared", "home", "infra", "kibanaUtils", "usageCollection"] } diff --git a/x-pack/plugins/fleet/server/mocks/index.ts b/x-pack/plugins/fleet/server/mocks/index.ts index 282135c7b1bcd..943f100c94f72 100644 --- a/x-pack/plugins/fleet/server/mocks/index.ts +++ b/x-pack/plugins/fleet/server/mocks/index.ts @@ -19,6 +19,7 @@ import { securityMock } from '../../../security/server/mocks'; import type { PackagePolicyServiceInterface } from '../services/package_policy'; import type { AgentPolicyServiceInterface, AgentService } from '../services'; import type { FleetAppContext } from '../plugin'; +import { createMockTelemetryEventsSender } from '../telemetry/__mocks__'; // Export all mocks from artifacts export * from '../services/artifacts/mocks'; @@ -59,6 +60,7 @@ export const createAppContextStartContractMock = (): MockedFleetAppContext => { config$, kibanaVersion: '8.99.0', // Fake version :) kibanaBranch: 'main', + telemetryEventsSender: createMockTelemetryEventsSender(), }; }; diff --git a/x-pack/plugins/fleet/server/plugin.ts b/x-pack/plugins/fleet/server/plugin.ts index 410682a13733c..7cc1b8b1cfcc9 100644 --- a/x-pack/plugins/fleet/server/plugin.ts +++ b/x-pack/plugins/fleet/server/plugin.ts @@ -18,6 +18,8 @@ import type { } from 'kibana/server'; import type { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; +import type { TelemetryPluginSetup, TelemetryPluginStart } from 'src/plugins/telemetry/server'; + import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/server'; import type { PluginStart as DataPluginStart } from '../../../../src/plugins/data/server'; import type { LicensingPluginSetup, ILicense } from '../../licensing/server'; @@ -83,6 +85,7 @@ import { RouterWrappers } from './routes/security'; import { startFleetServerSetup } from './services/fleet_server'; import { FleetArtifactsClient } from './services/artifacts'; import type { FleetRouter } from './types/request_context'; +import { TelemetryEventsSender } from './telemetry/sender'; export interface FleetSetupDeps { licensing: LicensingPluginSetup; @@ -91,12 +94,14 @@ export interface FleetSetupDeps { encryptedSavedObjects: EncryptedSavedObjectsPluginSetup; cloud?: CloudSetup; usageCollection?: UsageCollectionSetup; + telemetry?: TelemetryPluginSetup; } export interface FleetStartDeps { data: DataPluginStart; encryptedSavedObjects: EncryptedSavedObjectsPluginStart; security?: SecurityPluginStart; + telemetry?: TelemetryPluginStart; } export interface FleetAppContext { @@ -115,6 +120,7 @@ export interface FleetAppContext { cloud?: CloudSetup; logger?: Logger; httpSetup?: HttpServiceSetup; + telemetryEventsSender: TelemetryEventsSender; } export type FleetSetupContract = void; @@ -176,6 +182,7 @@ export class FleetPlugin private httpSetup?: HttpServiceSetup; private securitySetup?: SecurityPluginSetup; private encryptedSavedObjectsSetup?: EncryptedSavedObjectsPluginSetup; + private readonly telemetryEventsSender: TelemetryEventsSender; constructor(private readonly initializerContext: PluginInitializerContext) { this.config$ = this.initializerContext.config.create(); @@ -184,6 +191,7 @@ export class FleetPlugin this.kibanaBranch = this.initializerContext.env.packageInfo.branch; this.logger = this.initializerContext.logger.get(); this.configInitialValue = this.initializerContext.config.get(); + this.telemetryEventsSender = new TelemetryEventsSender(this.logger.get('telemetry_events')); } public setup(core: CoreSetup, deps: FleetSetupDeps) { @@ -302,6 +310,8 @@ export class FleetPlugin }); } } + + this.telemetryEventsSender.setup(deps.telemetry); } public start(core: CoreStart, plugins: FleetStartDeps): FleetStartContract { @@ -321,11 +331,14 @@ export class FleetPlugin httpSetup: this.httpSetup, cloud: this.cloud, logger: this.logger, + telemetryEventsSender: this.telemetryEventsSender, }); licenseService.start(this.licensing$); const fleetServerSetup = startFleetServerSetup(); + this.telemetryEventsSender.start(plugins.telemetry, core); + return { fleetSetupCompleted: () => new Promise((resolve) => { @@ -362,5 +375,6 @@ export class FleetPlugin public async stop() { appContextService.stop(); licenseService.stop(); + this.telemetryEventsSender.stop(); } } diff --git a/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts b/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts index 77e7a2c4ede1a..58463bfa5569d 100644 --- a/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts @@ -146,7 +146,8 @@ export const updatePackagePolicyHandler: RequestHandler< esClient, request.params.packagePolicyId, { ...newData, package: pkg, inputs }, - { user } + { user }, + packagePolicy.package?.version ); return response.ok({ body: { item: updatedPackagePolicy }, diff --git a/x-pack/plugins/fleet/server/services/app_context.ts b/x-pack/plugins/fleet/server/services/app_context.ts index a1e6ef4545aef..7ec1607598b8a 100644 --- a/x-pack/plugins/fleet/server/services/app_context.ts +++ b/x-pack/plugins/fleet/server/services/app_context.ts @@ -33,6 +33,7 @@ import type { } from '../types'; import type { FleetAppContext } from '../plugin'; import type { CloudSetup } from '../../../cloud/server'; +import type { TelemetryEventsSender } from '../telemetry/sender'; class AppContextService { private encryptedSavedObjects: EncryptedSavedObjectsClient | undefined; @@ -51,6 +52,7 @@ class AppContextService { private logger: Logger | undefined; private httpSetup?: HttpServiceSetup; private externalCallbacks: ExternalCallbacksStorage = new Map(); + private telemetryEventsSender: TelemetryEventsSender | undefined; public start(appContext: FleetAppContext) { this.data = appContext.data; @@ -66,6 +68,7 @@ class AppContextService { this.kibanaVersion = appContext.kibanaVersion; this.kibanaBranch = appContext.kibanaBranch; this.httpSetup = appContext.httpSetup; + this.telemetryEventsSender = appContext.telemetryEventsSender; if (appContext.config$) { this.config$ = appContext.config$; @@ -203,6 +206,10 @@ class AppContextService { >; } } + + public getTelemetryEventsSender() { + return this.telemetryEventsSender; + } } export const appContextService = new AppContextService(); diff --git a/x-pack/plugins/fleet/server/services/package_policy.test.ts b/x-pack/plugins/fleet/server/services/package_policy.test.ts index b6207316829ee..dcc00251e70f4 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.test.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.test.ts @@ -134,6 +134,12 @@ jest.mock('./epm/packages/cleanup', () => { }; }); +jest.mock('./upgrade_usage', () => { + return { + sendTelemetryEvents: jest.fn(), + }; +}); + const mockedFetchInfo = fetchInfo as jest.Mock>; type CombinedExternalCallback = PutPackagePolicyUpdateCallback | PostPackagePolicyCreateCallback; diff --git a/x-pack/plugins/fleet/server/services/package_policy.ts b/x-pack/plugins/fleet/server/services/package_policy.ts index 39902d35feb08..8968b1b4af3fd 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.ts @@ -9,7 +9,7 @@ import { omit, partition } from 'lodash'; import { i18n } from '@kbn/i18n'; import semverLte from 'semver/functions/lte'; import { getFlattenedObject } from '@kbn/std'; -import type { KibanaRequest, LogMeta } from 'src/core/server'; +import type { KibanaRequest } from 'src/core/server'; import type { ElasticsearchClient, RequestHandlerContext, @@ -68,6 +68,8 @@ import { compileTemplate } from './epm/agent/agent'; import { normalizeKuery } from './saved_object'; import { appContextService } from '.'; import { removeOldAssets } from './epm/packages/cleanup'; +import type { PackagePolicyUpgradeUsage } from './upgrade_usage'; +import { sendTelemetryEvents } from './upgrade_usage'; export type InputsOverride = Partial & { vars?: Array; @@ -84,17 +86,6 @@ export const DATA_STREAM_ALLOWED_INDEX_PRIVILEGES = new Set([ 'read_cross_cluster', ]); -interface PackagePolicyUpgradeLogMeta extends LogMeta { - package_policy_upgrade: { - package_name: string; - current_version: string; - new_version: string; - status: 'success' | 'failure'; - error?: any[]; - dryRun?: boolean; - }; -} - class PackagePolicyService { public async create( soClient: SavedObjectsClientContract, @@ -369,7 +360,8 @@ class PackagePolicyService { esClient: ElasticsearchClient, id: string, packagePolicy: UpdatePackagePolicy, - options?: { user?: AuthenticatedUser } + options?: { user?: AuthenticatedUser }, + currentVersion?: string ): Promise { const oldPackagePolicy = await this.get(soClient, id); const { version, ...restOfPackagePolicy } = packagePolicy; @@ -445,22 +437,22 @@ class PackagePolicyService { currentVersion: packagePolicy.package.version, }); - const upgradeMeta: PackagePolicyUpgradeLogMeta = { - package_policy_upgrade: { + if (packagePolicy.package.version !== currentVersion) { + const upgradeTelemetry: PackagePolicyUpgradeUsage = { package_name: packagePolicy.package.name, + current_version: currentVersion || 'unknown', new_version: packagePolicy.package.version, - current_version: 'unknown', status: 'success', dryRun: false, - }, - }; - - appContextService - .getLogger() - .info( - `Package policy successfully upgraded ${JSON.stringify(upgradeMeta)}`, - upgradeMeta + }; + sendTelemetryEvents( + appContextService.getLogger(), + appContextService.getTelemetryEventsSender(), + upgradeTelemetry ); + appContextService.getLogger().info(`Package policy upgraded successfully`); + appContextService.getLogger().debug(JSON.stringify(upgradeTelemetry)); + } } return newPolicy; @@ -629,7 +621,14 @@ class PackagePolicyService { ); updatePackagePolicy.elasticsearch = registryPkgInfo.elasticsearch; - await this.update(soClient, esClient, id, updatePackagePolicy, options); + await this.update( + soClient, + esClient, + id, + updatePackagePolicy, + options, + packagePolicy.package.version + ); result.push({ id, name: packagePolicy.name, @@ -691,24 +690,27 @@ class PackagePolicyService { const hasErrors = 'errors' in updatedPackagePolicy; if (packagePolicy.package.version !== packageInfo.version) { - const upgradeMeta: PackagePolicyUpgradeLogMeta = { - package_policy_upgrade: { - package_name: packageInfo.name, - current_version: packagePolicy.package.version, - new_version: packageInfo.version, - status: hasErrors ? 'failure' : 'success', - error: hasErrors ? updatedPackagePolicy.errors : undefined, - dryRun: true, - }, + const upgradeTelemetry: PackagePolicyUpgradeUsage = { + package_name: packageInfo.name, + current_version: packagePolicy.package.version, + new_version: packageInfo.version, + status: hasErrors ? 'failure' : 'success', + error: hasErrors ? updatedPackagePolicy.errors : undefined, + dryRun: true, }; + sendTelemetryEvents( + appContextService.getLogger(), + appContextService.getTelemetryEventsSender(), + upgradeTelemetry + ); appContextService .getLogger() - .info( + .info( `Package policy upgrade dry run ${ hasErrors ? 'resulted in errors' : 'ran successfully' - } ${JSON.stringify(upgradeMeta)}`, - upgradeMeta + }` ); + appContextService.getLogger().debug(JSON.stringify(upgradeTelemetry)); } return { diff --git a/x-pack/plugins/fleet/server/services/upgrade_usage.test.ts b/x-pack/plugins/fleet/server/services/upgrade_usage.test.ts new file mode 100644 index 0000000000000..5445ad233eddc --- /dev/null +++ b/x-pack/plugins/fleet/server/services/upgrade_usage.test.ts @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Logger } from 'src/core/server'; +import { loggingSystemMock } from 'src/core/server/mocks'; + +import type { TelemetryEventsSender } from '../telemetry/sender'; +import { createMockTelemetryEventsSender } from '../telemetry/__mocks__'; + +import { sendTelemetryEvents, capErrorSize } from './upgrade_usage'; +import type { PackagePolicyUpgradeUsage } from './upgrade_usage'; + +describe('sendTelemetryEvents', () => { + let eventsTelemetryMock: jest.Mocked; + let loggerMock: jest.Mocked; + + beforeEach(() => { + eventsTelemetryMock = createMockTelemetryEventsSender(); + loggerMock = loggingSystemMock.createLogger(); + }); + + it('should queue telemetry events with generic error', () => { + const upgardeMessage: PackagePolicyUpgradeUsage = { + package_name: 'aws', + current_version: '0.6.1', + new_version: '1.3.0', + status: 'failure', + error: [ + { key: 'queueUrl', message: ['Queue URL is required'] }, + { message: 'Invalid format' }, + ], + dryRun: true, + }; + + sendTelemetryEvents(loggerMock, eventsTelemetryMock, upgardeMessage); + + expect(eventsTelemetryMock.queueTelemetryEvents).toHaveBeenCalledWith('fleet-upgrades', [ + { + current_version: '0.6.1', + error: [ + { + key: 'queueUrl', + message: ['Queue URL is required'], + }, + { + message: 'Invalid format', + }, + ], + error_message: ['Field is required', 'Invalid format'], + new_version: '1.3.0', + package_name: 'aws', + status: 'failure', + dryRun: true, + }, + ]); + }); + + it('should cap error size', () => { + const maxSize = 2; + const errors = [{ message: '1' }, { message: '2' }, { message: '3' }]; + + const result = capErrorSize(errors, maxSize); + + expect(result.length).toEqual(maxSize); + }); +}); diff --git a/x-pack/plugins/fleet/server/services/upgrade_usage.ts b/x-pack/plugins/fleet/server/services/upgrade_usage.ts new file mode 100644 index 0000000000000..68bb126496e01 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/upgrade_usage.ts @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Logger } from 'src/core/server'; + +import type { TelemetryEventsSender } from '../telemetry/sender'; + +export interface PackagePolicyUpgradeUsage { + package_name: string; + current_version: string; + new_version: string; + status: 'success' | 'failure'; + error?: UpgradeError[]; + dryRun?: boolean; + error_message?: string[]; +} + +export interface UpgradeError { + key?: string; + message: string | string[]; +} + +export const MAX_ERROR_SIZE = 100; +export const FLEET_UPGRADES_CHANNEL_NAME = 'fleet-upgrades'; + +export function sendTelemetryEvents( + logger: Logger, + eventsTelemetry: TelemetryEventsSender | undefined, + upgradeUsage: PackagePolicyUpgradeUsage +) { + if (eventsTelemetry === undefined) { + return; + } + + try { + const cappedErrors = capErrorSize(upgradeUsage.error || [], MAX_ERROR_SIZE); + eventsTelemetry.queueTelemetryEvents(FLEET_UPGRADES_CHANNEL_NAME, [ + { + ...upgradeUsage, + error: upgradeUsage.error ? cappedErrors : undefined, + error_message: makeErrorGeneric(cappedErrors), + }, + ]); + } catch (exc) { + logger.error(`queing telemetry events failed ${exc}`); + } +} + +export function capErrorSize(errors: UpgradeError[], maxSize: number): UpgradeError[] { + return errors.length > maxSize ? errors?.slice(0, maxSize) : errors; +} + +function makeErrorGeneric(errors: UpgradeError[]): string[] { + return errors.map((error) => { + if (Array.isArray(error.message)) { + const firstMessage = error.message[0]; + return firstMessage?.indexOf('is required') > -1 ? 'Field is required' : firstMessage; + } + return error.message as string; + }); +} diff --git a/x-pack/plugins/fleet/server/telemetry/__mocks__/index.ts b/x-pack/plugins/fleet/server/telemetry/__mocks__/index.ts new file mode 100644 index 0000000000000..2070aeca20861 --- /dev/null +++ b/x-pack/plugins/fleet/server/telemetry/__mocks__/index.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { TelemetryEventsSender } from '../sender'; + +/** + * Creates a mocked Telemetry Events Sender + */ +export const createMockTelemetryEventsSender = ( + enableTelemetry?: boolean +): jest.Mocked => { + return { + setup: jest.fn(), + start: jest.fn(), + stop: jest.fn(), + fetchTelemetryUrl: jest.fn(), + queueTelemetryEvents: jest.fn(), + isTelemetryOptedIn: jest.fn().mockReturnValue(enableTelemetry ?? jest.fn()), + sendIfDue: jest.fn(), + sendEvents: jest.fn(), + } as unknown as jest.Mocked; +}; diff --git a/x-pack/plugins/fleet/server/telemetry/queue.test.ts b/x-pack/plugins/fleet/server/telemetry/queue.test.ts new file mode 100644 index 0000000000000..510b898387036 --- /dev/null +++ b/x-pack/plugins/fleet/server/telemetry/queue.test.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +/* eslint-disable dot-notation */ +import { TelemetryQueue } from './queue'; + +describe('TelemetryQueue', () => { + describe('queueTelemetryEvents', () => { + it('queues two events', () => { + const queue = new TelemetryQueue(); + queue.addEvents([{ 'event.kind': '1' }, { 'event.kind': '2' }]); + expect(queue['queue'].length).toBe(2); + }); + + it('queues more than maxQueueSize events', () => { + const queue = new TelemetryQueue(); + queue.addEvents([{ 'event.kind': '1' }, { 'event.kind': '2' }]); + queue['maxQueueSize'] = 5; + queue.addEvents([{ 'event.kind': '3' }, { 'event.kind': '4' }]); + queue.addEvents([{ 'event.kind': '5' }, { 'event.kind': '6' }]); + queue.addEvents([{ 'event.kind': '7' }, { 'event.kind': '8' }]); + expect(queue['queue'].length).toBe(5); + }); + + it('get and clear events', async () => { + const queue = new TelemetryQueue(); + queue.addEvents([{ 'event.kind': '1' }, { 'event.kind': '2' }]); + + expect(queue.getEvents().length).toBe(2); + + queue.clearEvents(); + + expect(queue['queue'].length).toBe(0); + }); + }); +}); diff --git a/x-pack/plugins/fleet/server/telemetry/queue.ts b/x-pack/plugins/fleet/server/telemetry/queue.ts new file mode 100644 index 0000000000000..3496cfb94915d --- /dev/null +++ b/x-pack/plugins/fleet/server/telemetry/queue.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const TELEMETRY_MAX_QUEUE_SIZE = 100; + +export class TelemetryQueue { + private maxQueueSize = TELEMETRY_MAX_QUEUE_SIZE; + private queue: T[] = []; + + public addEvents(events: T[]) { + const qlength = this.queue.length; + + if (events.length === 0) { + return; + } + + if (qlength >= this.maxQueueSize) { + // we're full already + return; + } + + if (events.length > this.maxQueueSize - qlength) { + this.queue.push(...events.slice(0, this.maxQueueSize - qlength)); + } else { + this.queue.push(...events); + } + } + + public clearEvents() { + this.queue = []; + } + + public getEvents(): T[] { + return this.queue; + } +} diff --git a/x-pack/plugins/fleet/server/telemetry/sender.test.ts b/x-pack/plugins/fleet/server/telemetry/sender.test.ts new file mode 100644 index 0000000000000..8fe4c6e150ff9 --- /dev/null +++ b/x-pack/plugins/fleet/server/telemetry/sender.test.ts @@ -0,0 +1,143 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/* eslint-disable dot-notation */ + +import { URL } from 'url'; + +import axios from 'axios'; + +import type { InfoResponse } from '@elastic/elasticsearch/lib/api/types'; + +import { loggingSystemMock } from 'src/core/server/mocks'; + +import { TelemetryEventsSender } from './sender'; + +jest.mock('axios', () => { + return { + post: jest.fn(), + }; +}); + +describe('TelemetryEventsSender', () => { + let logger: ReturnType; + let sender: TelemetryEventsSender; + + beforeEach(() => { + logger = loggingSystemMock.createLogger(); + sender = new TelemetryEventsSender(logger); + sender.start(undefined, { + elasticsearch: { client: { asInternalUser: { info: jest.fn(async () => ({})) } } }, + } as any); + }); + + describe('queueTelemetryEvents', () => { + it('queues two events', () => { + sender.queueTelemetryEvents('fleet-upgrades', [ + { package_name: 'system', current_version: '0.3', new_version: '1.0', status: 'success' }, + ]); + expect(sender['queuesPerChannel']['fleet-upgrades']).toBeDefined(); + }); + + it('should send events when due', async () => { + sender['telemetryStart'] = { + getIsOptedIn: jest.fn(async () => true), + }; + sender['telemetrySetup'] = { + getTelemetryUrl: jest.fn( + async () => new URL('https://telemetry-staging.elastic.co/v3/send/snapshot') + ), + }; + + sender.queueTelemetryEvents('fleet-upgrades', [ + { package_name: 'apache', current_version: '0.3', new_version: '1.0', status: 'success' }, + ]); + sender['sendEvents'] = jest.fn(); + + await sender['sendIfDue'](); + + expect(sender['sendEvents']).toHaveBeenCalledWith( + 'https://telemetry-staging.elastic.co/v3/send/fleet-upgrades', + undefined, + expect.anything() + ); + }); + + it("shouldn't send when telemetry is disabled", async () => { + const telemetryStart = { + getIsOptedIn: jest.fn(async () => false), + }; + sender['telemetryStart'] = telemetryStart; + + sender.queueTelemetryEvents('fleet-upgrades', [ + { package_name: 'system', current_version: '0.3', new_version: '1.0', status: 'success' }, + ]); + sender['sendEvents'] = jest.fn(); + + await sender['sendIfDue'](); + + expect(sender['sendEvents']).toBeCalledTimes(0); + }); + + it('should send events to separate channels', async () => { + sender['telemetryStart'] = { + getIsOptedIn: jest.fn(async () => true), + }; + sender['telemetrySetup'] = { + getTelemetryUrl: jest.fn( + async () => new URL('https://telemetry.elastic.co/v3/send/snapshot') + ), + }; + + sender['fetchClusterInfo'] = jest.fn(async () => { + return { + cluster_uuid: '1', + cluster_name: 'name', + version: { + number: '8.0.0', + }, + } as InfoResponse; + }); + + const myChannelEvents = [{ 'event.kind': '1' }, { 'event.kind': '2' }]; + // @ts-ignore + sender.queueTelemetryEvents('my-channel', myChannelEvents); + sender['queuesPerChannel']['my-channel']['getEvents'] = jest.fn(() => myChannelEvents); + + expect(sender['queuesPerChannel']['my-channel']['queue'].length).toBe(2); + + const myChannel2Events = [{ 'event.kind': '3' }]; + // @ts-ignore + sender.queueTelemetryEvents('my-channel2', myChannel2Events); + sender['queuesPerChannel']['my-channel2']['getEvents'] = jest.fn(() => myChannel2Events); + + expect(sender['queuesPerChannel']['my-channel2']['queue'].length).toBe(1); + + await sender['sendIfDue'](); + + expect(sender['queuesPerChannel']['my-channel']['getEvents']).toBeCalledTimes(1); + expect(sender['queuesPerChannel']['my-channel2']['getEvents']).toBeCalledTimes(1); + const headers = { + headers: { + 'Content-Type': 'application/x-ndjson', + 'X-Elastic-Cluster-ID': '1', + 'X-Elastic-Stack-Version': '8.0.0', + }, + }; + expect(axios.post).toHaveBeenCalledWith( + 'https://telemetry.elastic.co/v3/send/my-channel', + '{"event.kind":"1"}\n{"event.kind":"2"}\n', + headers + ); + expect(axios.post).toHaveBeenCalledWith( + 'https://telemetry.elastic.co/v3/send/my-channel2', + '{"event.kind":"3"}\n', + headers + ); + }); + }); +}); diff --git a/x-pack/plugins/fleet/server/telemetry/sender.ts b/x-pack/plugins/fleet/server/telemetry/sender.ts new file mode 100644 index 0000000000000..3bda17fbd1d79 --- /dev/null +++ b/x-pack/plugins/fleet/server/telemetry/sender.ts @@ -0,0 +1,192 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { CoreStart, ElasticsearchClient, Logger } from 'src/core/server'; +import type { TelemetryPluginStart, TelemetryPluginSetup } from 'src/plugins/telemetry/server'; + +import { cloneDeep } from 'lodash'; + +import axios from 'axios'; + +import type { InfoResponse } from '@elastic/elasticsearch/lib/api/types'; + +import { TelemetryQueue } from './queue'; + +import type { FleetTelemetryChannel, FleetTelemetryChannelEvents } from './types'; + +/** + * Simplified version of https://github.com/elastic/kibana/blob/master/x-pack/plugins/security_solution/server/lib/telemetry/sender.ts + * Sends batched events to telemetry v3 api + */ +export class TelemetryEventsSender { + private readonly initialCheckDelayMs = 10 * 1000; + private readonly checkIntervalMs = 30 * 1000; + private readonly logger: Logger; + + private telemetryStart?: TelemetryPluginStart; + private telemetrySetup?: TelemetryPluginSetup; + private intervalId?: NodeJS.Timeout; + private isSending = false; + private queuesPerChannel: { [channel: string]: TelemetryQueue } = {}; + private isOptedIn?: boolean = true; // Assume true until the first check + private esClient?: ElasticsearchClient; + + constructor(logger: Logger) { + this.logger = logger; + } + + public setup(telemetrySetup?: TelemetryPluginSetup) { + this.telemetrySetup = telemetrySetup; + } + + public async start(telemetryStart?: TelemetryPluginStart, core?: CoreStart) { + this.telemetryStart = telemetryStart; + this.esClient = core?.elasticsearch.client.asInternalUser; + + this.logger.debug(`Starting local task`); + setTimeout(() => { + this.sendIfDue(); + this.intervalId = setInterval(() => this.sendIfDue(), this.checkIntervalMs); + }, this.initialCheckDelayMs); + } + + public stop() { + if (this.intervalId) { + clearInterval(this.intervalId); + } + } + + public queueTelemetryEvents( + channel: T, + events: Array + ) { + if (!this.queuesPerChannel[channel]) { + this.queuesPerChannel[channel] = new TelemetryQueue(); + } + this.queuesPerChannel[channel].addEvents(cloneDeep(events)); + } + + public async isTelemetryOptedIn() { + this.isOptedIn = await this.telemetryStart?.getIsOptedIn(); + return this.isOptedIn === true; + } + + private async sendIfDue() { + if (this.isSending) { + return; + } + + this.isSending = true; + + this.isOptedIn = await this.isTelemetryOptedIn(); + if (!this.isOptedIn) { + this.logger.debug(`Telemetry is not opted-in.`); + for (const channel of Object.keys(this.queuesPerChannel)) { + this.queuesPerChannel[channel].clearEvents(); + } + this.isSending = false; + return; + } + + const clusterInfo = await this.fetchClusterInfo(); + + for (const channel of Object.keys(this.queuesPerChannel)) { + await this.sendEvents( + await this.fetchTelemetryUrl(channel), + clusterInfo, + this.queuesPerChannel[channel] + ); + } + + this.isSending = false; + } + + private async fetchClusterInfo(): Promise { + if (this.esClient === undefined || this.esClient === null) { + throw Error('elasticsearch client is unavailable: cannot retrieve cluster infomation'); + } + + const { body } = await this.esClient.info(); + return body; + } + + public async sendEvents( + telemetryUrl: string, + clusterInfo: InfoResponse | undefined, + queue: TelemetryQueue + ) { + const events = queue.getEvents(); + if (events.length === 0) { + return; + } + + try { + this.logger.debug(`Telemetry URL: ${telemetryUrl}`); + + queue.clearEvents(); + + this.logger.debug(JSON.stringify(events)); + + await this.send( + events, + telemetryUrl, + clusterInfo?.cluster_uuid, + clusterInfo?.version?.number + ); + } catch (err) { + this.logger.warn(`Error sending telemetry events data: ${err}`); + queue.clearEvents(); + } + } + + // Forms URLs like: + // https://telemetry.elastic.co/v3/send/my-channel-name or + // https://telemetry-staging.elastic.co/v3/send/my-channel-name + private async fetchTelemetryUrl(channel: string): Promise { + const telemetryUrl = await this.telemetrySetup?.getTelemetryUrl(); + if (!telemetryUrl) { + throw Error("Couldn't get telemetry URL"); + } + telemetryUrl.pathname = `/v3/send/${channel}`; + return telemetryUrl.toString(); + } + + private async send( + events: unknown[], + telemetryUrl: string, + clusterUuid: string | undefined, + clusterVersionNumber: string | undefined + ) { + // using ndjson so that each line will be wrapped in json envelope on server side + // see https://github.com/elastic/infra/blob/master/docs/telemetry/telemetry-next-dataflow.md#json-envelope + const ndjson = this.transformDataToNdjson(events); + + try { + const resp = await axios.post(telemetryUrl, ndjson, { + headers: { + 'Content-Type': 'application/x-ndjson', + 'X-Elastic-Cluster-ID': clusterUuid, + 'X-Elastic-Stack-Version': clusterVersionNumber ? clusterVersionNumber : '7.16.0', + }, + }); + this.logger.debug(`Events sent!. Response: ${resp.status} ${JSON.stringify(resp.data)}`); + } catch (err) { + this.logger.warn( + `Error sending events: ${err.response.status} ${JSON.stringify(err.response.data)}` + ); + } + } + + private transformDataToNdjson = (data: unknown[]): string => { + if (data.length !== 0) { + const dataString = data.map((dataItem) => JSON.stringify(dataItem)).join('\n'); + return `${dataString}\n`; + } else { + return ''; + } + }; +} diff --git a/x-pack/plugins/fleet/server/telemetry/types.ts b/x-pack/plugins/fleet/server/telemetry/types.ts new file mode 100644 index 0000000000000..4351546ecdf02 --- /dev/null +++ b/x-pack/plugins/fleet/server/telemetry/types.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { PackagePolicyUpgradeUsage } from '../services/upgrade_usage'; + +export interface FleetTelemetryChannelEvents { + // channel name => event type + 'fleet-upgrades': PackagePolicyUpgradeUsage; +} + +export type FleetTelemetryChannel = keyof FleetTelemetryChannelEvents; From 5fc70f5f21059293c9863ff9426b1d307bc2d511 Mon Sep 17 00:00:00 2001 From: Marco Vettorello Date: Wed, 3 Nov 2021 13:35:34 +0100 Subject: [PATCH 40/41] [charts] Fix multilayer time axis styles (#116749) This commit fixes a set of issues to the new multilayer time axis: - the tickLine is now removed from ticks without a label - the axis/tick/label style is restored to the original EUI one - the multilayer time axis style is now moved into the charts plugin and reused - Lens: use the single-layer time axis when bars cluster is used - TSVB: I reduced a bit the number of ticks on the Y axis, to reduce the noise of gridlines with multilayer axis - Discover: I reduced by 8px the height of the histogram and moved the top padding to the bottom to separate a bit the time range text and the time axis Coming from @elastic/charts update: - multilayer time axis tick/grid is shown only when tick is inside domain (this removes the black vertical axis line at the beginning of the chart) fix(xy): show mouse cursors on charts with opaque background elastic-charts#1447 - Fix the invisible cursor on charts with opaque backgrounds fix(xy): multilayer time axis tick/grid only when tick is inside domain elastic-charts#1446 - Add missing last tick and rarify gridlines fix(xy): adding missing last tick and other tick improvements elastic-charts#1448 --- package.json | 2 +- src/plugins/charts/common/index.ts | 1 + src/plugins/charts/common/static/index.ts | 2 + .../charts/common/static/styles/index.ts | 9 +++ .../static/styles/multilayer_timeaxis.ts | 26 +++++++ src/plugins/charts/public/static/index.ts | 1 + .../apps/main/components/chart/histogram.tsx | 50 +------------- .../components/layout/discover_layout.scss | 4 +- .../visualizations/views/timeseries/index.js | 68 +++++-------------- .../vis_types/xy/public/config/get_axis.ts | 33 ++------- .../__snapshots__/expression.test.tsx.snap | 21 ++++-- .../public/xy_visualization/expression.tsx | 57 ++++++---------- .../__snapshots__/donut_chart.test.tsx.snap | 18 +++++ yarn.lock | 8 +-- 14 files changed, 124 insertions(+), 176 deletions(-) create mode 100644 src/plugins/charts/common/static/styles/index.ts create mode 100644 src/plugins/charts/common/static/styles/multilayer_timeaxis.ts diff --git a/package.json b/package.json index f35800746095a..ae8c9621b1893 100644 --- a/package.json +++ b/package.json @@ -98,7 +98,7 @@ "@elastic/apm-synthtrace": "link:bazel-bin/packages/elastic-apm-synthtrace", "@elastic/apm-rum": "^5.9.1", "@elastic/apm-rum-react": "^1.3.1", - "@elastic/charts": "38.1.0", + "@elastic/charts": "38.1.3", "@elastic/datemath": "link:bazel-bin/packages/elastic-datemath", "@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@^8.0.0-canary.35", "@elastic/ems-client": "8.0.0", diff --git a/src/plugins/charts/common/index.ts b/src/plugins/charts/common/index.ts index d06dbc73e29b1..936ad2d10ac64 100644 --- a/src/plugins/charts/common/index.ts +++ b/src/plugins/charts/common/index.ts @@ -34,6 +34,7 @@ export { ColorMode, LabelRotation, defaultCountLabel, + MULTILAYER_TIME_AXIS_STYLE, } from './static'; export { ColorSchemaParams, Labels, Style } from './types'; diff --git a/src/plugins/charts/common/static/index.ts b/src/plugins/charts/common/static/index.ts index 9cde3bafe59e4..ac706ac7869c5 100644 --- a/src/plugins/charts/common/static/index.ts +++ b/src/plugins/charts/common/static/index.ts @@ -19,3 +19,5 @@ export { } from './color_maps'; export { ColorMode, LabelRotation, defaultCountLabel } from './components'; + +export * from './styles'; diff --git a/src/plugins/charts/common/static/styles/index.ts b/src/plugins/charts/common/static/styles/index.ts new file mode 100644 index 0000000000000..688103322c53f --- /dev/null +++ b/src/plugins/charts/common/static/styles/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './multilayer_timeaxis'; diff --git a/src/plugins/charts/common/static/styles/multilayer_timeaxis.ts b/src/plugins/charts/common/static/styles/multilayer_timeaxis.ts new file mode 100644 index 0000000000000..02a5533f53fca --- /dev/null +++ b/src/plugins/charts/common/static/styles/multilayer_timeaxis.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { Position, RecursivePartial, AxisStyle } from '@elastic/charts'; + +export const MULTILAYER_TIME_AXIS_STYLE: RecursivePartial = { + tickLabel: { + visible: true, + padding: 0, + rotation: 0, + alignment: { + vertical: Position.Bottom, + horizontal: Position.Left, + }, + }, + tickLine: { + visible: true, + size: 0.0001, + padding: 4, + }, +}; diff --git a/src/plugins/charts/public/static/index.ts b/src/plugins/charts/public/static/index.ts index 6f5c87ce0df4d..53078eebe9c56 100644 --- a/src/plugins/charts/public/static/index.ts +++ b/src/plugins/charts/public/static/index.ts @@ -9,3 +9,4 @@ export * from './colors'; export * from './components'; export * from './utils'; +export * from '../../common/static/styles'; diff --git a/src/plugins/discover/public/application/apps/main/components/chart/histogram.tsx b/src/plugins/discover/public/application/apps/main/components/chart/histogram.tsx index d1bc7a7cd7dbc..e35201651d5a7 100644 --- a/src/plugins/discover/public/application/apps/main/components/chart/histogram.tsx +++ b/src/plugins/discover/public/application/apps/main/components/chart/histogram.tsx @@ -23,9 +23,6 @@ import { Settings, TooltipType, XYChartElementEvent, - GridLineStyle, - AxisStyle, - RecursivePartial, } from '@elastic/charts'; import { IUiSettingsClient } from 'kibana/public'; import { @@ -38,7 +35,7 @@ import { DataCharts$, DataChartsMessage } from '../../services/use_saved_search' import { FetchStatus } from '../../../../types'; import { DiscoverServices } from '../../../../../build_services'; import { useDataState } from '../../utils/use_data_state'; -import { LEGACY_TIME_AXIS } from '../../../../../../../charts/common'; +import { LEGACY_TIME_AXIS, MULTILAYER_TIME_AXIS_STYLE } from '../../../../../../../charts/common'; export interface DiscoverHistogramProps { savedSearchData$: DataCharts$; @@ -184,46 +181,6 @@ export function DiscoverHistogram({ const xAxisFormatter = services.data.fieldFormats.deserialize(chartData.yAxisFormat); const useLegacyTimeAxis = uiSettings.get(LEGACY_TIME_AXIS, false); - const gridLineStyle: RecursivePartial = useLegacyTimeAxis - ? {} - : { strokeWidth: 0.1, stroke: isDarkMode ? 'white' : 'black' }; - const verticalAxisStyle: RecursivePartial = useLegacyTimeAxis - ? {} - : { - axisLine: { - visible: false, - }, - tickLabel: { - fontSize: 11, - }, - }; - const xAxisStyle: RecursivePartial = useLegacyTimeAxis - ? {} - : { - axisLine: { - stroke: isDarkMode ? 'lightgray' : 'darkgray', - strokeWidth: 1, - }, - tickLine: { - size: 12, - strokeWidth: 0.15, - stroke: isDarkMode ? 'white' : 'black', - padding: -10, - visible: true, - }, - tickLabel: { - fontSize: 11, - padding: 0, - alignment: { - vertical: Position.Bottom, - horizontal: Position.Left, - }, - offset: { - x: 1.5, - y: 0, - }, - }, - }; return ( @@ -244,16 +201,13 @@ export function DiscoverHistogram({ ticks={2} integersOnly tickFormat={(value) => xAxisFormatter.convert(value)} - gridLine={gridLineStyle} - style={verticalAxisStyle} /> (bars?.show && stack !== STACKED_OPTIONS.NONE) || lines?.show + ) && !useLegacyTimeAxis; return ( @@ -361,10 +331,8 @@ export const TimeSeries = ({ position={position} domain={domain} hide={hide} - gridLine={{ - ...GRID_LINE_CONFIG, - visible: showGrid, - }} + gridLine={gridLineStyle} + ticks={5} tickFormat={tickFormatter} /> ))} @@ -375,8 +343,8 @@ export const TimeSeries = ({ title={getAxisLabelString(interval)} tickFormat={xAxisFormatter} gridLine={gridLineStyle} - style={xAxisStyle} - timeAxisLayerCount={useLegacyTimeAxis ? 0 : 3} + style={shouldUseNewTimeAxis ? MULTILAYER_TIME_AXIS_STYLE : undefined} + timeAxisLayerCount={shouldUseNewTimeAxis ? 3 : 0} /> ); diff --git a/src/plugins/vis_types/xy/public/config/get_axis.ts b/src/plugins/vis_types/xy/public/config/get_axis.ts index 796636ef2cb61..23605feddd381 100644 --- a/src/plugins/vis_types/xy/public/config/get_axis.ts +++ b/src/plugins/vis_types/xy/public/config/get_axis.ts @@ -8,16 +8,11 @@ import { identity } from 'lodash'; -import { - AxisSpec, - TickFormatter, - YDomainRange, - ScaleType as ECScaleType, - Position, -} from '@elastic/charts'; +import { AxisSpec, TickFormatter, YDomainRange, ScaleType as ECScaleType } from '@elastic/charts'; import { LabelRotation } from '../../../../charts/public'; import { BUCKET_TYPES } from '../../../../data/public'; +import { MULTILAYER_TIME_AXIS_STYLE } from '../../../../charts/common'; import { Aspect, @@ -164,29 +159,13 @@ function getAxisStyle( ): AxisSpec['style'] { return isMultiLayerTimeAxis ? { + ...MULTILAYER_TIME_AXIS_STYLE, tickLabel: { + ...MULTILAYER_TIME_AXIS_STYLE.tickLabel, visible: Boolean(ticks?.show), - rotation: 0, // rotation is disabled on new time axis - fontSize: 11, - padding: 0, - alignment: { - vertical: Position.Bottom, - horizontal: Position.Left, - }, - offset: { - x: 1.5, - y: 0, - }, - }, - axisLine: { - stroke: darkMode ? 'lightgray' : 'darkgray', - strokeWidth: 1, }, tickLine: { - size: 12, - strokeWidth: 0.15, - stroke: darkMode ? 'white' : 'black', - padding: -10, + ...MULTILAYER_TIME_AXIS_STYLE.tickLine, visible: Boolean(ticks?.show), }, axisTitle: { @@ -198,7 +177,7 @@ function getAxisStyle( visible: (title ?? '').trim().length > 0, }, tickLabel: { - visible: ticks?.show, + visible: Boolean(ticks?.show), rotation: -(ticks?.rotation ?? rotationFallback), }, }; diff --git a/x-pack/plugins/lens/public/xy_visualization/__snapshots__/expression.test.tsx.snap b/x-pack/plugins/lens/public/xy_visualization/__snapshots__/expression.test.tsx.snap index 6601167e1f83a..e2566aa22ce9e 100644 --- a/x-pack/plugins/lens/public/xy_visualization/__snapshots__/expression.test.tsx.snap +++ b/x-pack/plugins/lens/public/xy_visualization/__snapshots__/expression.test.tsx.snap @@ -46,7 +46,7 @@ exports[`xy_expression XYChart component it renders area 1`] = ` + isHistogram && + (seriesType.includes('stacked') || !splitAccessor) && + (seriesType.includes('stacked') || + !seriesType.includes('bar') || + !chartHasMoreThanOneBarSeries) + ); - const gridLineStyle: RecursivePartial = shouldUseNewTimeAxis - ? { - visible: gridlinesVisibilitySettings?.x, - strokeWidth: 0.1, - stroke: darkMode ? 'white' : 'black', - } - : { - visible: gridlinesVisibilitySettings?.x, - strokeWidth: 2, - }; + const shouldUseNewTimeAxis = + isTimeViz && isHistogramModeEnabled && !useLegacyTimeAxis && !shouldRotate; + + const gridLineStyle = { + visible: gridlinesVisibilitySettings?.x, + strokeWidth: 1, + }; const xAxisStyle: RecursivePartial = shouldUseNewTimeAxis ? { + ...MULTILAYER_TIME_AXIS_STYLE, tickLabel: { + ...MULTILAYER_TIME_AXIS_STYLE.tickLabel, visible: Boolean(tickLabelsVisibilitySettings?.x), - rotation: 0, // rotation is disabled on new time axis - fontSize: 11, - padding: - referenceLinePaddings.bottom != null ? { inner: referenceLinePaddings.bottom } : 0, - alignment: { - vertical: Position.Bottom, - horizontal: Position.Left, - }, - offset: { - x: 1.5, - y: 0, - }, - }, - axisLine: { - stroke: darkMode ? 'lightgray' : 'darkgray', - strokeWidth: 1, }, tickLine: { - size: 12, - strokeWidth: 0.15, - stroke: darkMode ? 'white' : 'black', - padding: -10, + ...MULTILAYER_TIME_AXIS_STYLE.tickLine, visible: Boolean(tickLabelsVisibilitySettings?.x), }, axisTitle: { visible: axisTitlesVisibilitySettings.x, - padding: - !tickLabelsVisibilitySettings?.x && referenceLinePaddings.bottom != null - ? { inner: referenceLinePaddings.bottom } - : undefined, }, } : { @@ -715,6 +697,7 @@ export function XYChart({ tickFormat={(d) => axis.formatter?.convert(d) || ''} style={getYAxesStyle(axis.groupId as 'left' | 'right')} domain={getYAxisDomain(axis)} + ticks={5} /> ); })} diff --git a/x-pack/plugins/uptime/public/components/common/charts/__snapshots__/donut_chart.test.tsx.snap b/x-pack/plugins/uptime/public/components/common/charts/__snapshots__/donut_chart.test.tsx.snap index c9299bf7bf3ea..4ee227c645253 100644 --- a/x-pack/plugins/uptime/public/components/common/charts/__snapshots__/donut_chart.test.tsx.snap +++ b/x-pack/plugins/uptime/public/components/common/charts/__snapshots__/donut_chart.test.tsx.snap @@ -80,6 +80,24 @@ exports[`DonutChart component passes correct props without errors for valid prop "strokeWidth": 1, "visible": false, }, + "lumaSteps": Array [ + 224, + 184, + 128, + 96, + 64, + 32, + 16, + 8, + 4, + 2, + 1, + 0, + 0, + 0, + 0, + 0, + ], "vertical": Object { "dash": Array [ 0, diff --git a/yarn.lock b/yarn.lock index 1e99d54dcf423..0b3e852ff0d9b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2337,10 +2337,10 @@ dependencies: object-hash "^1.3.0" -"@elastic/charts@38.1.0": - version "38.1.0" - resolved "https://registry.yarnpkg.com/@elastic/charts/-/charts-38.1.0.tgz#52146564c0e399da2267c10ec4536c33e50969e4" - integrity sha512-u2hQ8+daCvqapKQiFqN8QHWTz3OXby5Xf/ta1Dv59KTDTFIinCZD/M8PyD3MapbKx4b67hL7UxbErqr4rZ8jeA== +"@elastic/charts@38.1.3": + version "38.1.3" + resolved "https://registry.yarnpkg.com/@elastic/charts/-/charts-38.1.3.tgz#0bf27021c54176e87d38269613205f3d6219da96" + integrity sha512-p6bJQWmfJ5SLkcgAoAMB3eTah/2a3/r3uo3ZskEN/xdPiqU8P+GANF8+6F4dWNfejbrpSUyCUldl7S4nWFGg3Q== dependencies: "@popperjs/core" "^2.4.0" chroma-js "^2.1.0" From 1efcfb62577c92a27311f019d975325dd84fcf41 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 3 Nov 2021 12:38:21 +0000 Subject: [PATCH 41/41] skip flaky suite (#115473 , #115474 , #116889 , #117081 , #116891 , #116890) --- .../management/users/edit_user/edit_user_page.test.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security/public/management/users/edit_user/edit_user_page.test.tsx b/x-pack/plugins/security/public/management/users/edit_user/edit_user_page.test.tsx index f61b2ac2f8056..b8e98042b1cff 100644 --- a/x-pack/plugins/security/public/management/users/edit_user/edit_user_page.test.tsx +++ b/x-pack/plugins/security/public/management/users/edit_user/edit_user_page.test.tsx @@ -28,7 +28,13 @@ const userMock = { roles: ['superuser'], }; -describe('EditUserPage', () => { +// FLAKY: https://github.com/elastic/kibana/issues/115473 +// FLAKY: https://github.com/elastic/kibana/issues/115474 +// FLAKY: https://github.com/elastic/kibana/issues/116889 +// FLAKY: https://github.com/elastic/kibana/issues/117081 +// FLAKY: https://github.com/elastic/kibana/issues/116891 +// FLAKY: https://github.com/elastic/kibana/issues/116890 +describe.skip('EditUserPage', () => { const coreStart = coreMock.createStart(); let history = createMemoryHistory({ initialEntries: ['/edit/jdoe'] }); const authc = securityMock.createSetup().authc;