From 7d9f460a9ceaa5f48e84fc0122bc5bb3883ac0b1 Mon Sep 17 00:00:00 2001 From: Brian Seeders Date: Wed, 18 Nov 2020 11:17:41 -0500 Subject: [PATCH 01/49] [CI] Build docker image during packer_cache (#82145) --- .ci/build_docker.sh | 10 ++++++++++ .ci/packer_cache_for_branch.sh | 2 ++ vars/kibanaPipeline.groovy | 7 +------ 3 files changed, 13 insertions(+), 6 deletions(-) create mode 100755 .ci/build_docker.sh diff --git a/.ci/build_docker.sh b/.ci/build_docker.sh new file mode 100755 index 000000000000..1f45182aad84 --- /dev/null +++ b/.ci/build_docker.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +set -euo pipefail + +cd "$(dirname "${0}")" + +cp /usr/local/bin/runbld ./ +cp /usr/local/bin/bash_standard_lib.sh ./ + +docker build -t kibana-ci -f ./Dockerfile . diff --git a/.ci/packer_cache_for_branch.sh b/.ci/packer_cache_for_branch.sh index b8b5f7d3c3f0..0d9b22b04dbd 100755 --- a/.ci/packer_cache_for_branch.sh +++ b/.ci/packer_cache_for_branch.sh @@ -53,6 +53,8 @@ tar -cf "$HOME/.kibana/bootstrap_cache/$branch.tar" \ echo "created $HOME/.kibana/bootstrap_cache/$branch.tar" +.ci/build_docker.sh + if [[ "$branch" != "master" ]]; then rm --preserve-root -rf "$checkoutDir" fi diff --git a/vars/kibanaPipeline.groovy b/vars/kibanaPipeline.groovy index eea3ff18f345..005129370471 100644 --- a/vars/kibanaPipeline.groovy +++ b/vars/kibanaPipeline.groovy @@ -390,12 +390,7 @@ def scriptTaskDocker(description, script) { def buildDocker() { sh( - script: """ - cp /usr/local/bin/runbld .ci/ - cp /usr/local/bin/bash_standard_lib.sh .ci/ - cd .ci - docker build -t kibana-ci -f ./Dockerfile . - """, + script: "./.ci/build_docker.sh", label: 'Build CI Docker image' ) } From e07d6d0b389eb8dcd553778aea570c6a082843f9 Mon Sep 17 00:00:00 2001 From: Joel Griffith Date: Wed, 18 Nov 2020 08:19:41 -0800 Subject: [PATCH 02/49] Derive the port from the protocol in cases where it's not explicitly stated (#83583) --- .../chromium/driver/chromium_driver.ts | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts b/x-pack/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts index 500185cd7e14..5a1cdfe86759 100644 --- a/x-pack/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts +++ b/x-pack/plugins/reporting/server/browsers/chromium/driver/chromium_driver.ts @@ -333,13 +333,32 @@ export class HeadlessChromiumDriver { private _shouldUseCustomHeaders(conditions: ConditionalHeadersConditions, url: string) { const { hostname, protocol, port, pathname } = parseUrl(url); - if (port === null) throw new Error(`URL missing port: ${url}`); + // `port` is null in URLs that don't explicitly state it, + // however we can derive the port from the protocol (http/https) + // IE: https://feeds-staging.elastic.co/kibana/v8.0.0.json + const derivedPort = (() => { + if (port) { + return port; + } + + if (protocol === 'http:') { + return '80'; + } + + if (protocol === 'https:') { + return '443'; + } + + return null; + })(); + + if (derivedPort === null) throw new Error(`URL missing port: ${url}`); if (pathname === null) throw new Error(`URL missing pathname: ${url}`); return ( hostname === conditions.hostname && protocol === `${conditions.protocol}:` && - this._shouldUseCustomHeadersForPort(conditions, port) && + this._shouldUseCustomHeadersForPort(conditions, derivedPort) && pathname.startsWith(`${conditions.basePath}/`) ); } From 21c0258e6bec3d0834cf3126b3b012208a9ae0cc Mon Sep 17 00:00:00 2001 From: Zacqary Adam Xeper Date: Wed, 18 Nov 2020 10:41:28 -0600 Subject: [PATCH 03/49] [Metrics UI] Add Process tab to Enhanced Node Details (#83477) --- .../common/http_api/host_details/index.ts | 7 + .../http_api/host_details/process_list.ts | 20 ++ x-pack/plugins/infra/common/http_api/index.ts | 1 + .../components/bottom_drawer.tsx | 6 +- .../components/node_details/overlay.tsx | 66 ++-- .../node_details/tabs/processes.tsx | 21 -- .../node_details/tabs/processes/index.tsx | 102 +++++++ .../tabs/processes/parse_process_list.ts | 55 ++++ .../tabs/processes/process_row.tsx | 267 ++++++++++++++++ .../tabs/processes/processes_table.tsx | 288 ++++++++++++++++++ .../tabs/processes/state_badge.tsx | 28 ++ .../node_details/tabs/processes/states.ts | 33 ++ .../tabs/processes/summary_table.tsx | 81 +++++ .../node_details/tabs/processes/types.ts | 22 ++ .../components/node_details/tabs/shared.tsx | 9 +- .../inventory_view/hooks/use_process_list.ts | 55 ++++ x-pack/plugins/infra/server/infra_server.ts | 2 + .../server/lib/host_details/process_list.ts | 64 ++++ .../infra/server/routes/process_list/index.ts | 50 +++ .../server/utils/get_all_metrics_data.ts | 34 +++ 20 files changed, 1163 insertions(+), 48 deletions(-) create mode 100644 x-pack/plugins/infra/common/http_api/host_details/index.ts create mode 100644 x-pack/plugins/infra/common/http_api/host_details/process_list.ts delete mode 100644 x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/processes.tsx create mode 100644 x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/processes/index.tsx create mode 100644 x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/processes/parse_process_list.ts create mode 100644 x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/processes/process_row.tsx create mode 100644 x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/processes/processes_table.tsx create mode 100644 x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/processes/state_badge.tsx create mode 100644 x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/processes/states.ts create mode 100644 x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/processes/summary_table.tsx create mode 100644 x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/processes/types.ts create mode 100644 x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_process_list.ts create mode 100644 x-pack/plugins/infra/server/lib/host_details/process_list.ts create mode 100644 x-pack/plugins/infra/server/routes/process_list/index.ts create mode 100644 x-pack/plugins/infra/server/utils/get_all_metrics_data.ts diff --git a/x-pack/plugins/infra/common/http_api/host_details/index.ts b/x-pack/plugins/infra/common/http_api/host_details/index.ts new file mode 100644 index 000000000000..b323ed8e9e32 --- /dev/null +++ b/x-pack/plugins/infra/common/http_api/host_details/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './process_list'; diff --git a/x-pack/plugins/infra/common/http_api/host_details/process_list.ts b/x-pack/plugins/infra/common/http_api/host_details/process_list.ts new file mode 100644 index 000000000000..4b4a0a54b9d1 --- /dev/null +++ b/x-pack/plugins/infra/common/http_api/host_details/process_list.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as rt from 'io-ts'; +import { MetricsAPITimerangeRT, MetricsAPISeriesRT } from '../metrics_api'; + +export const ProcessListAPIRequestRT = rt.type({ + hostTerm: rt.record(rt.string, rt.string), + timerange: MetricsAPITimerangeRT, + indexPattern: rt.string, +}); + +export const ProcessListAPIResponseRT = rt.array(MetricsAPISeriesRT); + +export type ProcessListAPIRequest = rt.TypeOf; + +export type ProcessListAPIResponse = rt.TypeOf; diff --git a/x-pack/plugins/infra/common/http_api/index.ts b/x-pack/plugins/infra/common/http_api/index.ts index 4c729d11ba8c..914011454a73 100644 --- a/x-pack/plugins/infra/common/http_api/index.ts +++ b/x-pack/plugins/infra/common/http_api/index.ts @@ -11,3 +11,4 @@ export * from './metrics_explorer'; export * from './metrics_api'; export * from './log_alerts'; export * from './snapshot_api'; +export * from './host_details'; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/bottom_drawer.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/bottom_drawer.tsx index 7c6e58125b48..5c6e124914f3 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/bottom_drawer.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/bottom_drawer.tsx @@ -38,7 +38,11 @@ export const BottomDrawer: React.FC<{ - + {isOpen ? hideHistory : showHistory} diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/overlay.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/overlay.tsx index dd0060f773b4..af712c061157 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/overlay.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/overlay.tsx @@ -4,11 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiTabbedContent } from '@elastic/eui'; +import { EuiPortal, EuiTabs, EuiTab, EuiPanel, EuiTitle } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiPanel } from '@elastic/eui'; -import React, { CSSProperties, useMemo } from 'react'; -import { EuiText } from '@elastic/eui'; +import React, { CSSProperties, useMemo, useState } from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiButtonEmpty } from '@elastic/eui'; import { euiStyled } from '../../../../../../../observability/public'; import { InfraWaffleMapNode, InfraWaffleMapOptions } from '../../../../../lib/lib'; @@ -17,6 +15,7 @@ import { MetricsTab } from './tabs/metrics'; import { LogsTab } from './tabs/logs'; import { ProcessesTab } from './tabs/processes'; import { PropertiesTab } from './tabs/properties'; +import { OVERLAY_Y_START, OVERLAY_BOTTOM_MARGIN, OVERLAY_HEADER_SIZE } from './tabs/shared'; interface Props { isOpen: boolean; @@ -48,46 +47,63 @@ export const NodeContextPopover = ({ }); }, [tabConfigs, node, nodeType, currentTime, options]); + const [selectedTab, setSelectedTab] = useState(0); + if (!isOpen) { return null; } return ( - - - - - -

{node.name}

-
-
- - - - - -
-
- -
+ + + + + + +

{node.name}

+
+
+ + + + + +
+ + {tabs.map((tab, i) => ( + setSelectedTab(i)}> + {tab.name} + + ))} + +
+ {tabs[selectedTab].content} +
+
); }; const OverlayHeader = euiStyled.div` border-color: ${(props) => props.theme.eui.euiBorderColor}; border-bottom-width: ${(props) => props.theme.eui.euiBorderWidthThick}; - padding: ${(props) => props.theme.eui.euiSizeS}; padding-bottom: 0; overflow: hidden; + background-color: ${(props) => props.theme.eui.euiColorLightestShade}; + height: ${OVERLAY_HEADER_SIZE}px; +`; + +const OverlayHeaderTitleWrapper = euiStyled(EuiFlexGroup).attrs({ alignItems: 'center' })` + padding: ${(props) => props.theme.eui.paddingSizes.s} ${(props) => + props.theme.eui.paddingSizes.m} 0; `; const panelStyle: CSSProperties = { position: 'absolute', right: 10, - top: -100, + top: OVERLAY_Y_START, width: '50%', - maxWidth: 600, + maxWidth: 730, zIndex: 2, - height: '50vh', + height: `calc(100vh - ${OVERLAY_Y_START + OVERLAY_BOTTOM_MARGIN}px)`, overflow: 'hidden', }; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/processes.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/processes.tsx deleted file mode 100644 index 94ba1150c20d..000000000000 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/processes.tsx +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { i18n } from '@kbn/i18n'; -import { TabContent, TabProps } from './shared'; - -const TabComponent = (props: TabProps) => { - return Processes Placeholder; -}; - -export const ProcessesTab = { - id: 'processes', - name: i18n.translate('xpack.infra.nodeDetails.tabs.processes', { - defaultMessage: 'Processes', - }), - content: TabComponent, -}; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/processes/index.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/processes/index.tsx new file mode 100644 index 000000000000..836d491e6210 --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/processes/index.tsx @@ -0,0 +1,102 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useMemo, useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiSearchBar, EuiSpacer, EuiEmptyPrompt, EuiButton, Query } from '@elastic/eui'; +import { useProcessList } from '../../../../hooks/use_process_list'; +import { TabContent, TabProps } from '../shared'; +import { STATE_NAMES } from './states'; +import { SummaryTable } from './summary_table'; +import { ProcessesTable } from './processes_table'; + +const TabComponent = ({ currentTime, node, nodeType, options }: TabProps) => { + const [searchFilter, setSearchFilter] = useState(EuiSearchBar.Query.MATCH_ALL); + + const hostTerm = useMemo(() => { + const field = + options.fields && Reflect.has(options.fields, nodeType) + ? Reflect.get(options.fields, nodeType) + : nodeType; + return { [field]: node.name }; + }, [options, node, nodeType]); + + const { loading, error, response, makeRequest: reload } = useProcessList( + hostTerm, + 'metricbeat-*', + options.fields!.timestamp, + currentTime + ); + + if (error) { + return ( + + + {i18n.translate('xpack.infra.metrics.nodeDetails.processListError', { + defaultMessage: 'Unable to show process data', + })} + + } + actions={ + + {i18n.translate('xpack.infra.metrics.nodeDetails.processListRetry', { + defaultMessage: 'Try again', + })} + + } + /> + + ); + } + + return ( + + + + setSearchFilter(query ?? EuiSearchBar.Query.MATCH_ALL)} + box={{ + incremental: true, + placeholder: i18n.translate('xpack.infra.metrics.nodeDetails.searchForProcesses', { + defaultMessage: 'Search for processes…', + }), + }} + filters={[ + { + type: 'field_value_selection', + field: 'state', + name: 'State', + operator: 'exact', + multiSelect: false, + options: Object.entries(STATE_NAMES).map(([value, view]: [string, string]) => ({ + value, + view, + })), + }, + ]} + /> + + + + ); +}; + +export const ProcessesTab = { + id: 'processes', + name: i18n.translate('xpack.infra.metrics.nodeDetails.tabs.processes', { + defaultMessage: 'Processes', + }), + content: TabComponent, +}; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/processes/parse_process_list.ts b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/processes/parse_process_list.ts new file mode 100644 index 000000000000..88584ef2987e --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/processes/parse_process_list.ts @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ProcessListAPIResponse } from '../../../../../../../../common/http_api'; +import { Process } from './types'; + +export const parseProcessList = (processList: ProcessListAPIResponse) => + processList.map((process) => { + const command = process.id; + let mostRecentPoint; + for (let i = process.rows.length - 1; i >= 0; i--) { + const point = process.rows[i]; + if (point && Array.isArray(point.meta) && point.meta?.length) { + mostRecentPoint = point; + break; + } + } + if (!mostRecentPoint) return { command, cpu: null, memory: null, startTime: null, state: null }; + + const { cpu, memory } = mostRecentPoint; + const { system, process: processMeta, user } = (mostRecentPoint.meta as any[])[0]; + const startTime = system.process.cpu.start_time; + const state = system.process.state; + + const timeseries = { + cpu: pickTimeseries(process.rows, 'cpu'), + memory: pickTimeseries(process.rows, 'memory'), + }; + + return { + command, + cpu, + memory, + startTime, + state, + pid: processMeta.pid, + user: user.name, + timeseries, + } as Process; + }); + +const pickTimeseries = (rows: any[], metricID: string) => ({ + rows: rows.map((row) => ({ + timestamp: row.timestamp, + metric_0: row[metricID], + })), + columns: [ + { name: 'timestamp', type: 'date' }, + { name: 'metric_0', type: 'number' }, + ], + id: metricID, +}); diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/processes/process_row.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/processes/process_row.tsx new file mode 100644 index 000000000000..bbf4a25fc49a --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/processes/process_row.tsx @@ -0,0 +1,267 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useState, useMemo } from 'react'; +import moment from 'moment'; +import { first, last } from 'lodash'; +import { i18n } from '@kbn/i18n'; +import { + EuiTableRow, + EuiTableRowCell, + EuiButtonEmpty, + EuiCode, + EuiDescriptionList, + EuiDescriptionListTitle, + EuiDescriptionListDescription, + EuiFlexGrid, + EuiFlexGroup, + EuiFlexItem, + EuiButton, + EuiSpacer, +} from '@elastic/eui'; +import { Axis, Chart, Settings, Position, TooltipValue, niceTimeFormatter } from '@elastic/charts'; +import { AutoSizer } from '../../../../../../../components/auto_sizer'; +import { createFormatter } from '../../../../../../../../common/formatters'; +import { useUiSetting } from '../../../../../../../../../../../src/plugins/kibana_react/public'; +import { getChartTheme } from '../../../../../metrics_explorer/components/helpers/get_chart_theme'; +import { calculateDomain } from '../../../../../metrics_explorer/components/helpers/calculate_domain'; +import { MetricsExplorerChartType } from '../../../../../metrics_explorer/hooks/use_metrics_explorer_options'; +import { MetricExplorerSeriesChart } from '../../../../../metrics_explorer/components/series_chart'; +import { MetricsExplorerAggregation } from '../../../../../../../../common/http_api'; +import { Color } from '../../../../../../../../common/color_palette'; +import { euiStyled } from '../../../../../../../../../observability/public'; +import { Process } from './types'; + +interface Props { + cells: React.ReactNode[]; + item: Process; +} + +export const ProcessRow = ({ cells, item }: Props) => { + const [isExpanded, setIsExpanded] = useState(false); + + return ( + <> + + + setIsExpanded(!isExpanded)} + /> + + {cells} + + + {isExpanded && ( + + {({ measureRef, bounds: { height = 0 } }) => ( + + + + + +
+ + {i18n.translate( + 'xpack.infra.metrics.nodeDetails.processes.expandedRowLabelCommand', + { + defaultMessage: 'Command', + } + )} + + + {item.command} + +
+
+ {item.apmTrace && ( + + + {i18n.translate( + 'xpack.infra.metrics.nodeDetails.processes.viewTraceInAPM', + { + defaultMessage: 'View trace in APM', + } + )} + + + )} +
+ + + + {i18n.translate( + 'xpack.infra.metrics.nodeDetails.processes.expandedRowLabelPID', + { + defaultMessage: 'PID', + } + )} + + + {item.pid} + + + + + {i18n.translate( + 'xpack.infra.metrics.nodeDetails.processes.expandedRowLabelUser', + { + defaultMessage: 'User', + } + )} + + + {item.user} + + + + {cpuMetricLabel} + + + + + + {memoryMetricLabel} + + + + + +
+
+ )} +
+ )} +
+ + ); +}; + +interface ProcessChartProps { + timeseries: Process['timeseries']['x']; + color: Color; + label: string; +} +const ProcessChart = ({ timeseries, color, label }: ProcessChartProps) => { + const chartMetric = { + color, + aggregation: 'avg' as MetricsExplorerAggregation, + label, + }; + const isDarkMode = useUiSetting('theme:darkMode'); + + const dateFormatter = useMemo(() => { + if (!timeseries) return () => ''; + const firstTimestamp = first(timeseries.rows)?.timestamp; + const lastTimestamp = last(timeseries.rows)?.timestamp; + + if (firstTimestamp == null || lastTimestamp == null) { + return (value: number) => `${value}`; + } + + return niceTimeFormatter([firstTimestamp, lastTimestamp]); + }, [timeseries]); + + const yAxisFormatter = createFormatter('percent'); + + const tooltipProps = { + headerFormatter: (tooltipValue: TooltipValue) => + moment(tooltipValue.value).format('Y-MM-DD HH:mm:ss.SSS'), + }; + + const dataDomain = calculateDomain(timeseries, [chartMetric], false); + const domain = dataDomain + ? { + max: dataDomain.max * 1.1, // add 10% headroom. + min: dataDomain.min, + } + : { max: 0, min: 0 }; + + return ( + + + + + + + + + ); +}; + +export const CodeLine = euiStyled(EuiCode).attrs({ + transparentBackground: true, +})` + text-overflow: ellipsis; + overflow: hidden; + padding: 0 !important; + & code.euiCodeBlock__code { + white-space: nowrap !important; + vertical-align: middle; + } +`; + +const ExpandedCommandLine = euiStyled(EuiCode).attrs({ + transparentBackground: true, +})` + padding: 0 !important; + margin-bottom: ${(props) => props.theme.eui.euiSizeS}; +`; + +const ExpandedRowCell = euiStyled(EuiTableRowCell).attrs({ + textOnly: false, + colSpan: 6, +})<{ commandHeight: number }>` + height: ${(props) => props.commandHeight + 240}px; + padding: 0 ${(props) => props.theme.eui.paddingSizes.m}; + background-color: ${(props) => props.theme.eui.euiColorLightestShade}; +`; + +const ChartContainer = euiStyled.div` + width: 300px; + height: 140px; +`; + +const cpuMetricLabel = i18n.translate( + 'xpack.infra.metrics.nodeDetails.processes.expandedRowLabelCPU', + { + defaultMessage: 'CPU', + } +); + +const memoryMetricLabel = i18n.translate( + 'xpack.infra.metrics.nodeDetails.processes.expandedRowLabelMemory', + { + defaultMessage: 'Memory', + } +); diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/processes/processes_table.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/processes/processes_table.tsx new file mode 100644 index 000000000000..43f3a333fda8 --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/processes/processes_table.tsx @@ -0,0 +1,288 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useMemo, useState, useEffect, useCallback } from 'react'; +import { omit } from 'lodash'; +import { i18n } from '@kbn/i18n'; +import { + EuiTable, + EuiTableHeader, + EuiTableBody, + EuiTableHeaderCell, + EuiTableRowCell, + EuiSpacer, + EuiTablePagination, + EuiLoadingChart, + Query, + SortableProperties, + LEFT_ALIGNMENT, + RIGHT_ALIGNMENT, +} from '@elastic/eui'; +import { ProcessListAPIResponse } from '../../../../../../../../common/http_api'; +import { FORMATTERS } from '../../../../../../../../common/formatters'; +import { euiStyled } from '../../../../../../../../../observability/public'; +import { Process } from './types'; +import { ProcessRow, CodeLine } from './process_row'; +import { parseProcessList } from './parse_process_list'; +import { StateBadge } from './state_badge'; +import { STATE_ORDER } from './states'; + +interface TableProps { + processList: ProcessListAPIResponse; + currentTime: number; + isLoading: boolean; + searchFilter: Query; +} + +function useSortableProperties( + sortablePropertyItems: Array<{ + name: string; + getValue: (obj: T) => any; + isAscending: boolean; + }>, + defaultSortProperty: string +) { + const [sortableProperties] = useState>( + new SortableProperties(sortablePropertyItems, defaultSortProperty) + ); + const [sortedColumn, setSortedColumn] = useState( + omit(sortableProperties.getSortedProperty(), 'getValue') + ); + + return { + setSortedColumn: useCallback( + (property) => { + sortableProperties.sortOn(property); + setSortedColumn(omit(sortableProperties.getSortedProperty(), 'getValue')); + }, + [sortableProperties] + ), + sortedColumn, + sortItems: (items: T[]) => sortableProperties.sortItems(items), + }; +} + +export const ProcessesTable = ({ + processList, + currentTime, + isLoading, + searchFilter, +}: TableProps) => { + const [currentPage, setCurrentPage] = useState(0); + const [itemsPerPage, setItemsPerPage] = useState(10); + useEffect(() => setCurrentPage(0), [processList, searchFilter, itemsPerPage]); + + const { sortedColumn, sortItems, setSortedColumn } = useSortableProperties( + [ + { + name: 'state', + getValue: (item: any) => STATE_ORDER.indexOf(item.state), + isAscending: true, + }, + { + name: 'command', + getValue: (item: any) => item.command.toLowerCase(), + isAscending: true, + }, + { + name: 'startTime', + getValue: (item: any) => Date.parse(item.startTime), + isAscending: false, + }, + { + name: 'cpu', + getValue: (item: any) => item.cpu, + isAscending: false, + }, + { + name: 'memory', + getValue: (item: any) => item.memory, + isAscending: false, + }, + ], + 'state' + ); + + const currentItems = useMemo(() => { + const filteredItems = Query.execute(searchFilter, parseProcessList(processList)) as Process[]; + if (!filteredItems.length) return []; + const sortedItems = sortItems(filteredItems); + return sortedItems; + }, [processList, searchFilter, sortItems]); + + const pageCount = useMemo(() => Math.ceil(currentItems.length / itemsPerPage), [ + itemsPerPage, + currentItems, + ]); + + const pageStartIdx = useMemo(() => currentPage * itemsPerPage + (currentPage > 0 ? 1 : 0), [ + currentPage, + itemsPerPage, + ]); + const currentItemsPage = useMemo( + () => currentItems.slice(pageStartIdx, pageStartIdx + itemsPerPage), + [pageStartIdx, currentItems, itemsPerPage] + ); + + if (isLoading) return ; + + return ( + <> + + + + {columns.map((column) => ( + setSortedColumn(column.field) : undefined} + isSorted={sortedColumn.name === column.field} + isSortAscending={sortedColumn.name === column.field && sortedColumn.isAscending} + > + {column.name} + + ))} + + + + + + + + + ); +}; + +const LoadingPlaceholder = () => { + return ( +
+ +
+ ); +}; + +interface TableBodyProps { + items: Process[]; + currentTime: number; +} +const ProcessesTableBody = ({ items, currentTime }: TableBodyProps) => ( + <> + {items.map((item, i) => { + const cells = columns.map((column) => ( + + {column.render ? column.render(item[column.field], currentTime) : item[column.field]} + + )); + return ; + })} + +); + +const StyledTableBody = euiStyled(EuiTableBody)` + & .euiTableCellContent { + padding-top: 0; + padding-bottom: 0; + + } +`; + +const ONE_MINUTE = 60 * 1000; +const ONE_HOUR = ONE_MINUTE * 60; +const RuntimeCell = ({ startTime, currentTime }: { startTime: string; currentTime: number }) => { + const runtimeLength = currentTime - Date.parse(startTime); + let remainingRuntimeMS = runtimeLength; + const runtimeHours = Math.floor(remainingRuntimeMS / ONE_HOUR); + remainingRuntimeMS -= runtimeHours * ONE_HOUR; + const runtimeMinutes = Math.floor(remainingRuntimeMS / ONE_MINUTE); + remainingRuntimeMS -= runtimeMinutes * ONE_MINUTE; + const runtimeSeconds = Math.floor(remainingRuntimeMS / 1000); + remainingRuntimeMS -= runtimeSeconds * 1000; + + const runtimeDisplayHours = runtimeHours ? `${runtimeHours}:` : ''; + const runtimeDisplayMinutes = runtimeMinutes < 10 ? `0${runtimeMinutes}:` : `${runtimeMinutes}:`; + const runtimeDisplaySeconds = runtimeSeconds < 10 ? `0${runtimeSeconds}` : runtimeSeconds; + + return <>{`${runtimeDisplayHours}${runtimeDisplayMinutes}${runtimeDisplaySeconds}`}; +}; + +const columns: Array<{ + field: keyof Process; + name: string; + sortable: boolean; + render?: Function; + width?: string | number; + textOnly?: boolean; + align?: typeof RIGHT_ALIGNMENT | typeof LEFT_ALIGNMENT; +}> = [ + { + field: 'state', + name: i18n.translate('xpack.infra.metrics.nodeDetails.processes.columnLabelState', { + defaultMessage: 'State', + }), + sortable: true, + render: (state: string) => , + width: 84, + textOnly: false, + }, + { + field: 'command', + name: i18n.translate('xpack.infra.metrics.nodeDetails.processes.columnLabelCommand', { + defaultMessage: 'Command', + }), + sortable: true, + width: '40%', + render: (command: string) => {command}, + }, + { + field: 'startTime', + name: i18n.translate('xpack.infra.metrics.nodeDetails.processes.columnLabelTime', { + defaultMessage: 'Time', + }), + align: RIGHT_ALIGNMENT, + sortable: true, + render: (startTime: string, currentTime: number) => ( + + ), + }, + { + field: 'cpu', + name: i18n.translate('xpack.infra.metrics.nodeDetails.processes.columnLabelCPU', { + defaultMessage: 'CPU', + }), + sortable: true, + render: (value: number) => FORMATTERS.percent(value), + }, + { + field: 'memory', + name: i18n.translate('xpack.infra.metrics.nodeDetails.processes.columnLabelMemory', { + defaultMessage: 'Mem.', + }), + sortable: true, + render: (value: number) => FORMATTERS.percent(value), + }, +]; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/processes/state_badge.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/processes/state_badge.tsx new file mode 100644 index 000000000000..17306abdb60a --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/processes/state_badge.tsx @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { EuiBadge } from '@elastic/eui'; +import { STATE_NAMES } from './states'; + +export const StateBadge = ({ state }: { state: string }) => { + switch (state) { + case 'running': + return {STATE_NAMES.running}; + case 'sleeping': + return {STATE_NAMES.sleeping}; + case 'dead': + return {STATE_NAMES.dead}; + case 'stopped': + return {STATE_NAMES.stopped}; + case 'idle': + return {STATE_NAMES.idle}; + case 'zombie': + return {STATE_NAMES.zombie}; + default: + return {STATE_NAMES.unknown}; + } +}; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/processes/states.ts b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/processes/states.ts new file mode 100644 index 000000000000..b5e32420709e --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/processes/states.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const STATE_NAMES = { + running: i18n.translate('xpack.infra.metrics.nodeDetails.processes.stateRunning', { + defaultMessage: 'Running', + }), + sleeping: i18n.translate('xpack.infra.metrics.nodeDetails.processes.stateSleeping', { + defaultMessage: 'Sleeping', + }), + dead: i18n.translate('xpack.infra.metrics.nodeDetails.processes.stateDead', { + defaultMessage: 'Dead', + }), + stopped: i18n.translate('xpack.infra.metrics.nodeDetails.processes.stateStopped', { + defaultMessage: 'Stopped', + }), + idle: i18n.translate('xpack.infra.metrics.nodeDetails.processes.stateIdle', { + defaultMessage: 'Idle', + }), + zombie: i18n.translate('xpack.infra.metrics.nodeDetails.processes.stateZombie', { + defaultMessage: 'Zombie', + }), + unknown: i18n.translate('xpack.infra.metrics.nodeDetails.processes.stateUnknown', { + defaultMessage: 'Unknown', + }), +}; + +export const STATE_ORDER = ['running', 'sleeping', 'stopped', 'idle', 'dead', 'zombie', 'unknown']; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/processes/summary_table.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/processes/summary_table.tsx new file mode 100644 index 000000000000..59becb0bf534 --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/processes/summary_table.tsx @@ -0,0 +1,81 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useMemo } from 'react'; +import { mapValues, countBy } from 'lodash'; +import { i18n } from '@kbn/i18n'; +import { EuiBasicTable, EuiLoadingSpinner, EuiBasicTableColumn } from '@elastic/eui'; +import { euiStyled } from '../../../../../../../../../observability/public'; +import { ProcessListAPIResponse } from '../../../../../../../../common/http_api'; +import { parseProcessList } from './parse_process_list'; +import { STATE_NAMES } from './states'; + +interface Props { + processList: ProcessListAPIResponse; + isLoading: boolean; +} + +type SummaryColumn = { + total: number; +} & Record; + +export const SummaryTable = ({ processList, isLoading }: Props) => { + const parsedList = parseProcessList(processList); + const processCount = useMemo( + () => + [ + { + total: isLoading ? -1 : parsedList.length, + ...mapValues(STATE_NAMES, () => (isLoading ? -1 : 0)), + ...(isLoading ? [] : countBy(parsedList, 'state')), + }, + ] as SummaryColumn[], + [parsedList, isLoading] + ); + return ( + + + + ); +}; + +const loadingRenderer = (value: number) => (value === -1 ? : value); + +const columns = [ + { + field: 'total', + name: i18n.translate('xpack.infra.metrics.nodeDetails.processes.headingTotalProcesses', { + defaultMessage: 'Total processes', + }), + width: 125, + render: loadingRenderer, + }, + ...Object.entries(STATE_NAMES).map(([field, name]) => ({ field, name, render: loadingRenderer })), +] as Array>; + +const LoadingSpinner = euiStyled(EuiLoadingSpinner).attrs({ size: 'm' })` + margin-top: 2px; + margin-bottom: 3px; +`; + +const StyleWrapper = euiStyled.div` + & .euiTableHeaderCell { + border-bottom: none; + & .euiTableCellContent { + padding-bottom: 0; + } + & .euiTableCellContent__text { + font-size: ${(props) => props.theme.eui.euiFontSizeS}; + } + } + + & .euiTableRowCell { + border-top: none; + & .euiTableCellContent { + padding-top: 0; + } + } +`; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/processes/types.ts b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/processes/types.ts new file mode 100644 index 000000000000..d483fe510c94 --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/processes/types.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { MetricsExplorerSeries } from '../../../../../../../../common/http_api'; +import { STATE_NAMES } from './states'; + +export interface Process { + command: string; + cpu: number; + memory: number; + startTime: number; + state: keyof typeof STATE_NAMES; + pid: number; + user: string; + timeseries: { + [x: string]: MetricsExplorerSeries; + }; + apmTrace?: string; // Placeholder +} diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/shared.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/shared.tsx index 241ad7104836..7386fa64aca9 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/shared.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/shared.tsx @@ -15,6 +15,13 @@ export interface TabProps { nodeType: InventoryItemType; } +export const OVERLAY_Y_START = 266; +export const OVERLAY_BOTTOM_MARGIN = 16; +export const OVERLAY_HEADER_SIZE = 96; +const contentHeightOffset = OVERLAY_Y_START + OVERLAY_BOTTOM_MARGIN + OVERLAY_HEADER_SIZE; export const TabContent = euiStyled.div` - padding: ${(props) => props.theme.eui.paddingSizes.l}; + padding: ${(props) => props.theme.eui.paddingSizes.s}; + height: calc(100vh - ${contentHeightOffset}px); + overflow-y: auto; + overflow-x: hidden; `; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_process_list.ts b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_process_list.ts new file mode 100644 index 000000000000..8e0843fe8b27 --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_process_list.ts @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { fold } from 'fp-ts/lib/Either'; +import { identity } from 'fp-ts/lib/function'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { useEffect } from 'react'; +import { ProcessListAPIResponse, ProcessListAPIResponseRT } from '../../../../../common/http_api'; +import { throwErrors, createPlainError } from '../../../../../common/runtime_types'; +import { useHTTPRequest } from '../../../../hooks/use_http_request'; + +export function useProcessList( + hostTerm: Record, + indexPattern: string, + timefield: string, + to: number +) { + const decodeResponse = (response: any) => { + return pipe( + ProcessListAPIResponseRT.decode(response), + fold(throwErrors(createPlainError), identity) + ); + }; + + const timerange = { + field: timefield, + interval: 'modules', + to, + from: to - 15 * 60 * 1000, // 15 minutes + }; + + const { error, loading, response, makeRequest } = useHTTPRequest( + '/api/metrics/process_list', + 'POST', + JSON.stringify({ + hostTerm, + timerange, + indexPattern, + }), + decodeResponse + ); + + useEffect(() => { + makeRequest(); + }, [makeRequest]); + + return { + error, + loading, + response, + makeRequest, + }; +} diff --git a/x-pack/plugins/infra/server/infra_server.ts b/x-pack/plugins/infra/server/infra_server.ts index 49fe55e3dee0..2bf5687da7e0 100644 --- a/x-pack/plugins/infra/server/infra_server.ts +++ b/x-pack/plugins/infra/server/infra_server.ts @@ -41,6 +41,7 @@ import { initLogSourceConfigurationRoutes, initLogSourceStatusRoutes } from './r import { initSourceRoute } from './routes/source'; import { initAlertPreviewRoute } from './routes/alerting'; import { initGetLogAlertsChartPreviewDataRoute } from './routes/log_alerts'; +import { initProcessListRoute } from './routes/process_list'; export const initInfraServer = (libs: InfraBackendLibs) => { const schema = makeExecutableSchema({ @@ -82,4 +83,5 @@ export const initInfraServer = (libs: InfraBackendLibs) => { initLogSourceStatusRoutes(libs); initAlertPreviewRoute(libs); initGetLogAlertsChartPreviewDataRoute(libs); + initProcessListRoute(libs); }; diff --git a/x-pack/plugins/infra/server/lib/host_details/process_list.ts b/x-pack/plugins/infra/server/lib/host_details/process_list.ts new file mode 100644 index 000000000000..99e8b2e8f6ab --- /dev/null +++ b/x-pack/plugins/infra/server/lib/host_details/process_list.ts @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ProcessListAPIRequest, MetricsAPIRequest } from '../../../common/http_api'; +import { getAllMetricsData } from '../../utils/get_all_metrics_data'; +import { query } from '../metrics'; +import { ESSearchClient } from '../metrics/types'; + +export const getProcessList = async ( + client: ESSearchClient, + { hostTerm, timerange, indexPattern }: ProcessListAPIRequest +) => { + const queryBody = { + timerange, + modules: ['system.cpu', 'system.memory'], + groupBy: ['system.process.cmdline'], + filters: [{ term: hostTerm }], + indexPattern, + limit: 9, + metrics: [ + { + id: 'cpu', + aggregations: { + cpu: { + avg: { + field: 'system.process.cpu.total.norm.pct', + }, + }, + }, + }, + { + id: 'memory', + aggregations: { + memory: { + avg: { + field: 'system.process.memory.rss.pct', + }, + }, + }, + }, + { + id: 'meta', + aggregations: { + meta: { + top_hits: { + size: 1, + sort: [{ [timerange.field]: { order: 'desc' } }], + _source: [ + 'system.process.cpu.start_time', + 'system.process.state', + 'process.pid', + 'user.name', + ], + }, + }, + }, + }, + ], + } as MetricsAPIRequest; + return await getAllMetricsData((body: MetricsAPIRequest) => query(client, body), queryBody); +}; diff --git a/x-pack/plugins/infra/server/routes/process_list/index.ts b/x-pack/plugins/infra/server/routes/process_list/index.ts new file mode 100644 index 000000000000..9851613255d8 --- /dev/null +++ b/x-pack/plugins/infra/server/routes/process_list/index.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Boom from '@hapi/boom'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { fold } from 'fp-ts/lib/Either'; +import { identity } from 'fp-ts/lib/function'; +import { schema } from '@kbn/config-schema'; +import { InfraBackendLibs } from '../../lib/infra_types'; +import { throwErrors } from '../../../common/runtime_types'; +import { createSearchClient } from '../../lib/create_search_client'; +import { getProcessList } from '../../lib/host_details/process_list'; +import { ProcessListAPIRequestRT, ProcessListAPIResponseRT } from '../../../common/http_api'; + +const escapeHatch = schema.object({}, { unknowns: 'allow' }); + +export const initProcessListRoute = (libs: InfraBackendLibs) => { + const { framework } = libs; + framework.registerRoute( + { + method: 'post', + path: '/api/metrics/process_list', + validate: { + body: escapeHatch, + }, + }, + async (requestContext, request, response) => { + try { + const options = pipe( + ProcessListAPIRequestRT.decode(request.body), + fold(throwErrors(Boom.badRequest), identity) + ); + + const client = createSearchClient(requestContext, framework); + const processListResponse = await getProcessList(client, options); + + return response.ok({ + body: ProcessListAPIResponseRT.encode(processListResponse), + }); + } catch (error) { + return response.internalError({ + body: error.message, + }); + } + } + ); +}; diff --git a/x-pack/plugins/infra/server/utils/get_all_metrics_data.ts b/x-pack/plugins/infra/server/utils/get_all_metrics_data.ts new file mode 100644 index 000000000000..cec58494d1b9 --- /dev/null +++ b/x-pack/plugins/infra/server/utils/get_all_metrics_data.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { MetricsAPIResponse, MetricsAPISeries } from '../../common/http_api/metrics_api'; + +export const getAllMetricsData = async ( + query: (options: Options) => Promise, + options: Options, + previousBuckets: MetricsAPISeries[] = [] +): Promise => { + const response = await query(options); + + // Nothing available, return the previous buckets. + if (response.series.length === 0) { + return previousBuckets; + } + + const currentBuckets = response.series; + + // if there are no currentBuckets then we are finished paginating through the results + if (!response.info.afterKey) { + return previousBuckets.concat(currentBuckets); + } + + // There is possibly more data, concat previous and current buckets and call ourselves recursively. + const newOptions = { + ...options, + afterKey: response.info.afterKey, + }; + return getAllMetricsData(query, newOptions, previousBuckets.concat(currentBuckets)); +}; From 7a7057eba7270042a230ff4ac4f2404145357312 Mon Sep 17 00:00:00 2001 From: Quynh Nguyen <43350163+qn895@users.noreply.github.com> Date: Wed, 18 Nov 2020 10:49:26 -0600 Subject: [PATCH 04/49] [ML] Performance improvements to annotations editing in Single Metric Viewer & buttons placement (#83216) --- .../__snapshots__/index.test.tsx.snap | 3 - .../annotation_flyout/index.test.tsx | 122 ++++++++++++------ .../annotations/annotation_flyout/index.tsx | 61 +++++---- .../annotations_table.test.js.snap | 6 +- .../annotations_table/annotations_table.js | 22 ++-- .../ml/ml_annotation_updates_context.ts | 14 ++ .../application/routing/routes/explorer.tsx | 9 +- .../application/routing/routes/jobs_list.tsx | 9 +- .../routing/routes/timeseriesexplorer.tsx | 16 ++- .../services/annotations_service.test.tsx | 19 ++- .../services/annotations_service.tsx | 26 +++- .../timeseries_chart/timeseries_chart.d.ts | 8 +- .../timeseries_chart/timeseries_chart.js | 27 ++-- .../timeseries_chart_annotations.ts | 14 +- .../timeseries_chart_with_tooltip.tsx | 6 +- .../timeseriesexplorer/timeseriesexplorer.js | 1 - 16 files changed, 244 insertions(+), 119 deletions(-) delete mode 100644 x-pack/plugins/ml/public/application/components/annotations/annotation_flyout/__snapshots__/index.test.tsx.snap create mode 100644 x-pack/plugins/ml/public/application/contexts/ml/ml_annotation_updates_context.ts diff --git a/x-pack/plugins/ml/public/application/components/annotations/annotation_flyout/__snapshots__/index.test.tsx.snap b/x-pack/plugins/ml/public/application/components/annotations/annotation_flyout/__snapshots__/index.test.tsx.snap deleted file mode 100644 index dba73c246c3d..000000000000 --- a/x-pack/plugins/ml/public/application/components/annotations/annotation_flyout/__snapshots__/index.test.tsx.snap +++ /dev/null @@ -1,3 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`AnnotationFlyout Initialization. 1`] = `""`; diff --git a/x-pack/plugins/ml/public/application/components/annotations/annotation_flyout/index.test.tsx b/x-pack/plugins/ml/public/application/components/annotations/annotation_flyout/index.test.tsx index a4d2cd6b091a..5ad175e2792b 100644 --- a/x-pack/plugins/ml/public/application/components/annotations/annotation_flyout/index.test.tsx +++ b/x-pack/plugins/ml/public/application/components/annotations/annotation_flyout/index.test.tsx @@ -5,58 +5,102 @@ */ import useObservable from 'react-use/lib/useObservable'; - import mockAnnotations from '../annotations_table/__mocks__/mock_annotations.json'; - import React from 'react'; -import { mountWithIntl, shallowWithIntl } from '@kbn/test/jest'; +import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { IntlProvider } from 'react-intl'; import { Annotation } from '../../../../../common/types/annotations'; -import { annotation$ } from '../../../services/annotations_service'; +import { AnnotationUpdatesService } from '../../../services/annotations_service'; import { AnnotationFlyout } from './index'; +import { MlAnnotationUpdatesContext } from '../../../contexts/ml/ml_annotation_updates_context'; + +jest.mock('../../../util/dependency_cache', () => ({ + getToastNotifications: () => ({ addSuccess: jest.fn(), addDanger: jest.fn() }), +})); + +const MlAnnotationUpdatesContextProvider = ({ + annotationUpdatesService, + children, +}: { + annotationUpdatesService: AnnotationUpdatesService; + children: React.ReactElement; +}) => { + return ( + + {children} + + ); +}; + +const ObservableComponent = (props: any) => { + const { annotationUpdatesService } = props; + const annotationProp = useObservable(annotationUpdatesService!.isAnnotationInitialized$()); + if (annotationProp === undefined) { + return null; + } + return ( + + ); +}; describe('AnnotationFlyout', () => { - test('Initialization.', () => { - const wrapper = shallowWithIntl(); - expect(wrapper).toMatchSnapshot(); + let annotationUpdatesService: AnnotationUpdatesService | null = null; + beforeEach(() => { + annotationUpdatesService = new AnnotationUpdatesService(); }); - test('Update button is disabled with empty annotation', () => { + test('Update button is disabled with empty annotation', async () => { const annotation = mockAnnotations[1] as Annotation; - annotation$.next(annotation); - - // useObservable wraps the observable in a new component - const ObservableComponent = (props: any) => { - const annotationProp = useObservable(annotation$); - if (annotationProp === undefined) { - return null; - } - return ; - }; - - const wrapper = mountWithIntl(); - const updateBtn = wrapper.find('EuiButton').first(); - expect(updateBtn.prop('isDisabled')).toEqual(true); + + annotationUpdatesService!.setValue(annotation); + + const { getByTestId } = render( + + + + ); + const updateBtn = getByTestId('annotationFlyoutUpdateButton'); + expect(updateBtn).toBeDisabled(); }); - test('Error displayed and update button displayed if annotation text is longer than max chars', () => { + test('Error displayed and update button displayed if annotation text is longer than max chars', async () => { const annotation = mockAnnotations[2] as Annotation; - annotation$.next(annotation); - - // useObservable wraps the observable in a new component - const ObservableComponent = (props: any) => { - const annotationProp = useObservable(annotation$); - if (annotationProp === undefined) { - return null; - } - return ; - }; - - const wrapper = mountWithIntl(); - const updateBtn = wrapper.find('EuiButton').first(); - expect(updateBtn.prop('isDisabled')).toEqual(true); - - expect(wrapper.find('EuiFormErrorText')).toHaveLength(1); + annotationUpdatesService!.setValue(annotation); + + const { getByTestId } = render( + + + + ); + const updateBtn = getByTestId('annotationFlyoutUpdateButton'); + expect(updateBtn).toBeDisabled(); + await waitFor(() => { + const errorText = screen.queryByText(/characters above maximum length/); + expect(errorText).not.toBe(undefined); + }); + }); + + test('Flyout disappears when annotation is updated', async () => { + const annotation = mockAnnotations[0] as Annotation; + + annotationUpdatesService!.setValue(annotation); + + const { getByTestId } = render( + + + + ); + const updateBtn = getByTestId('annotationFlyoutUpdateButton'); + expect(updateBtn).not.toBeDisabled(); + expect(screen.queryByTestId('mlAnnotationFlyout')).toBeInTheDocument(); + + await fireEvent.click(updateBtn); + expect(screen.queryByTestId('mlAnnotationFlyout')).not.toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/ml/public/application/components/annotations/annotation_flyout/index.tsx b/x-pack/plugins/ml/public/application/components/annotations/annotation_flyout/index.tsx index 84abe3ed8a82..88996772f49d 100644 --- a/x-pack/plugins/ml/public/application/components/annotations/annotation_flyout/index.tsx +++ b/x-pack/plugins/ml/public/application/components/annotations/annotation_flyout/index.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Component, FC, ReactNode, useCallback } from 'react'; +import React, { Component, FC, ReactNode, useCallback, useContext } from 'react'; import useObservable from 'react-use/lib/useObservable'; import * as Rx from 'rxjs'; import { cloneDeep } from 'lodash'; @@ -28,15 +28,14 @@ import { import { CommonProps } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { distinctUntilChanged } from 'rxjs/operators'; import { ANNOTATION_MAX_LENGTH_CHARS, ANNOTATION_EVENT_USER, } from '../../../../../common/constants/annotations'; import { - annotation$, annotationsRefreshed, AnnotationState, + AnnotationUpdatesService, } from '../../../services/annotations_service'; import { AnnotationDescriptionList } from '../annotation_description_list'; import { DeleteAnnotationModal } from '../delete_annotation_modal'; @@ -48,6 +47,7 @@ import { } from '../../../../../common/types/annotations'; import { PartitionFieldsType } from '../../../../../common/types/anomalies'; import { PARTITION_FIELDS } from '../../../../../common/constants/anomalies'; +import { MlAnnotationUpdatesContext } from '../../../contexts/ml/ml_annotation_updates_context'; interface ViewableDetector { index: number; @@ -67,6 +67,7 @@ interface Props { }; detectorIndex: number; detectors: ViewableDetector[]; + annotationUpdatesService: AnnotationUpdatesService; } interface State { @@ -85,7 +86,8 @@ export class AnnotationFlyoutUI extends Component { public annotationSub: Rx.Subscription | null = null; componentDidMount() { - this.annotationSub = annotation$.subscribe((v) => { + const { annotationUpdatesService } = this.props; + this.annotationSub = annotationUpdatesService.update$().subscribe((v) => { this.setState({ annotationState: v, }); @@ -100,15 +102,17 @@ export class AnnotationFlyoutUI extends Component { if (this.state.annotationState === null) { return; } + const { annotationUpdatesService } = this.props; - annotation$.next({ + annotationUpdatesService.setValue({ ...this.state.annotationState, annotation: e.target.value, }); }; public cancelEditingHandler = () => { - annotation$.next(null); + const { annotationUpdatesService } = this.props; + annotationUpdatesService.setValue(null); }; public deleteConfirmHandler = () => { @@ -148,7 +152,10 @@ export class AnnotationFlyoutUI extends Component { } this.closeDeleteModal(); - annotation$.next(null); + + const { annotationUpdatesService } = this.props; + + annotationUpdatesService.setValue(null); annotationsRefreshed(); }; @@ -193,7 +200,8 @@ export class AnnotationFlyoutUI extends Component { public saveOrUpdateAnnotation = () => { const { annotationState: originalAnnotation } = this.state; - const { chartDetails, detectorIndex } = this.props; + const { chartDetails, detectorIndex, annotationUpdatesService } = this.props; + if (originalAnnotation === null) { return; } @@ -218,8 +226,7 @@ export class AnnotationFlyoutUI extends Component { } // Mark the annotation created by `user` if and only if annotation is being created, not updated annotation.event = annotation.event ?? ANNOTATION_EVENT_USER; - - annotation$.next(null); + annotationUpdatesService.setValue(null); ml.annotations .indexAnnotation(annotation) @@ -356,16 +363,16 @@ export class AnnotationFlyoutUI extends Component { - + - + - + {isExistingAnnotation && ( { )} - + {isExistingAnnotation ? ( { } export const AnnotationFlyout: FC = (props) => { - const annotationProp = useObservable( - annotation$.pipe( - distinctUntilChanged((prev, curr) => { - // prevent re-rendering - return prev !== null && curr !== null; - }) - ) - ); + const annotationUpdatesService = useContext(MlAnnotationUpdatesContext); + const annotationProp = useObservable(annotationUpdatesService.isAnnotationInitialized$()); const cancelEditingHandler = useCallback(() => { - annotation$.next(null); + annotationUpdatesService.setValue(null); }, []); if (annotationProp === undefined || annotationProp === null) { @@ -423,7 +429,12 @@ export const AnnotationFlyout: FC = (props) => { const isExistingAnnotation = typeof annotationProp._id !== 'undefined'; return ( - +

@@ -441,7 +452,7 @@ export const AnnotationFlyout: FC = (props) => {

- +
); }; diff --git a/x-pack/plugins/ml/public/application/components/annotations/annotations_table/__snapshots__/annotations_table.test.js.snap b/x-pack/plugins/ml/public/application/components/annotations/annotations_table/__snapshots__/annotations_table.test.js.snap index 114a6b235d1a..0c6fa6669c2e 100644 --- a/x-pack/plugins/ml/public/application/components/annotations/annotations_table/__snapshots__/annotations_table.test.js.snap +++ b/x-pack/plugins/ml/public/application/components/annotations/annotations_table/__snapshots__/annotations_table.test.js.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`AnnotationsTable Initialization with annotations prop. 1`] = ` - annotation$.next(originalAnnotation ?? annotation)} + onClick={() => annotationUpdatesService.setValue(originalAnnotation ?? annotation)} iconType="pencil" aria-label={editAnnotationsTooltipAriaLabelText} /> @@ -693,4 +694,7 @@ class AnnotationsTableUI extends Component { } } -export const AnnotationsTable = withKibana(AnnotationsTableUI); +export const AnnotationsTable = withKibana((props) => { + const annotationUpdatesService = useContext(MlAnnotationUpdatesContext); + return ; +}); diff --git a/x-pack/plugins/ml/public/application/contexts/ml/ml_annotation_updates_context.ts b/x-pack/plugins/ml/public/application/contexts/ml/ml_annotation_updates_context.ts new file mode 100644 index 000000000000..37dea3029c8a --- /dev/null +++ b/x-pack/plugins/ml/public/application/contexts/ml/ml_annotation_updates_context.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { createContext } from 'react'; +import { AnnotationUpdatesService } from '../../services/annotations_service'; + +export type MlAnnotationUpdatesContextValue = AnnotationUpdatesService; + +export const MlAnnotationUpdatesContext = createContext( + new AnnotationUpdatesService() +); 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 cb6944e0ecf0..b91a5bd4a1aa 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/explorer.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/explorer.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { FC, useEffect, useState, useCallback } from 'react'; +import React, { FC, useEffect, useState, useCallback, useMemo } from 'react'; import useObservable from 'react-use/lib/useObservable'; import { i18n } from '@kbn/i18n'; @@ -34,6 +34,8 @@ import { getBreadcrumbWithUrlForApp } from '../breadcrumbs'; import { useTimefilter } from '../../contexts/kibana'; import { isViewBySwimLaneData } from '../../explorer/swimlane_container'; import { JOB_ID } from '../../../../common/constants/anomalies'; +import { MlAnnotationUpdatesContext } from '../../contexts/ml/ml_annotation_updates_context'; +import { AnnotationUpdatesService } from '../../services/annotations_service'; export const explorerRouteFactory = ( navigateToPath: NavigateToPath, @@ -59,10 +61,13 @@ const PageWrapper: FC = ({ 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 2863e59508e3..d91ec27d9a50 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 @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useEffect, FC } from 'react'; +import React, { useEffect, FC, useMemo } from 'react'; import useObservable from 'react-use/lib/useObservable'; import { i18n } from '@kbn/i18n'; @@ -19,6 +19,8 @@ import { basicResolvers } from '../resolvers'; import { JobsPage } from '../../jobs/jobs_list'; import { useTimefilter } from '../../contexts/kibana'; import { getBreadcrumbWithUrlForApp } from '../breadcrumbs'; +import { AnnotationUpdatesService } from '../../services/annotations_service'; +import { MlAnnotationUpdatesContext } from '../../contexts/ml/ml_annotation_updates_context'; export const jobListRouteFactory = (navigateToPath: NavigateToPath, basePath: string): MlRoute => ({ path: '/jobs', @@ -57,10 +59,13 @@ const PageWrapper: FC = ({ deps }) => { setGlobalState({ refreshInterval }); timefilter.setRefreshInterval(refreshInterval); }, []); + const annotationUpdatesService = useMemo(() => new AnnotationUpdatesService(), []); return ( - + + + ); }; 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 9331fdc04b7b..2653781ce1a3 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx @@ -5,7 +5,7 @@ */ import { isEqual } from 'lodash'; -import React, { FC, useCallback, useEffect, useState } from 'react'; +import React, { FC, useCallback, useEffect, useMemo, useState } from 'react'; import usePrevious from 'react-use/lib/usePrevious'; import moment from 'moment'; @@ -39,7 +39,8 @@ import { basicResolvers } from '../resolvers'; import { getBreadcrumbWithUrlForApp } from '../breadcrumbs'; import { useTimefilter } from '../../contexts/kibana'; import { useToastNotificationService } from '../../services/toast_notification_service'; - +import { AnnotationUpdatesService } from '../../services/annotations_service'; +import { MlAnnotationUpdatesContext } from '../../contexts/ml/ml_annotation_updates_context'; export const timeSeriesExplorerRouteFactory = ( navigateToPath: NavigateToPath, basePath: string @@ -64,13 +65,16 @@ const PageWrapper: FC = ({ deps }) => { jobs: mlJobService.loadJobsWrapper, jobsWithTimeRange: () => ml.jobs.jobsWithTimerange(getDateFormatTz()), }); + const annotationUpdatesService = useMemo(() => new AnnotationUpdatesService(), []); return ( - + + + ); }; diff --git a/x-pack/plugins/ml/public/application/services/annotations_service.test.tsx b/x-pack/plugins/ml/public/application/services/annotations_service.test.tsx index 2ba54d243ed1..969748acc6af 100644 --- a/x-pack/plugins/ml/public/application/services/annotations_service.test.tsx +++ b/x-pack/plugins/ml/public/application/services/annotations_service.test.tsx @@ -7,20 +7,29 @@ import mockAnnotations from '../components/annotations/annotations_table/__mocks__/mock_annotations.json'; import { Annotation } from '../../../common/types/annotations'; -import { annotation$, annotationsRefresh$, annotationsRefreshed } from './annotations_service'; - +import { + annotationsRefresh$, + annotationsRefreshed, + AnnotationUpdatesService, +} from './annotations_service'; describe('annotations_service', () => { - test('annotation$', () => { + let annotationUpdatesService: AnnotationUpdatesService | null = null; + + beforeEach(() => { + annotationUpdatesService = new AnnotationUpdatesService(); + }); + + test('annotationUpdatesService', () => { const subscriber = jest.fn(); - annotation$.subscribe(subscriber); + annotationUpdatesService!.update$().subscribe(subscriber); // the subscriber should have been triggered with the initial value of null expect(subscriber.mock.calls).toHaveLength(1); expect(subscriber.mock.calls[0][0]).toBe(null); const annotation = mockAnnotations[0] as Annotation; - annotation$.next(annotation); + annotationUpdatesService!.setValue(annotation); // the subscriber should have been triggered with the updated annotation value expect(subscriber.mock.calls).toHaveLength(2); 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 6493770156cb..208c3b6ca582 100644 --- a/x-pack/plugins/ml/public/application/services/annotations_service.tsx +++ b/x-pack/plugins/ml/public/application/services/annotations_service.tsx @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { BehaviorSubject } from 'rxjs'; - +import { BehaviorSubject, Observable } from 'rxjs'; +import { distinctUntilChanged } from 'rxjs/operators'; import { Annotation } from '../../../common/types/annotations'; /* @@ -79,3 +79,25 @@ export const annotation$ = new BehaviorSubject(null); */ export const annotationsRefresh$ = new BehaviorSubject(Date.now()); export const annotationsRefreshed = () => annotationsRefresh$.next(Date.now()); + +export class AnnotationUpdatesService { + private _annotation$: BehaviorSubject = new BehaviorSubject( + null + ); + + public update$() { + return this._annotation$.asObservable(); + } + public isAnnotationInitialized$(): Observable { + return this._annotation$.asObservable().pipe( + distinctUntilChanged((prev, curr) => { + // prevent re-rendering + return prev !== null && curr !== null; + }) + ); + } + + public setValue(annotation: AnnotationState) { + this._annotation$.next(annotation); + } +} diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.d.ts b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.d.ts index 04b666b4fc68..f58a399f5e3d 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.d.ts +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.d.ts @@ -10,6 +10,7 @@ import React from 'react'; import { Annotation } from '../../../../../common/types/annotations'; import { CombinedJob } from '../../../../../common/types/anomaly_detection_jobs'; import { ChartTooltipService } from '../../../components/chart_tooltip'; +import { AnnotationState, AnnotationUpdatesService } from '../../../services/annotations_service'; interface Props { selectedJob: CombinedJob; @@ -47,6 +48,11 @@ interface TimeseriesChartProps { tooltipService: object; } -declare class TimeseriesChart extends React.Component { +interface TimeseriesChartIntProps { + annotationUpdatesService: AnnotationUpdatesService; + annotationProps: AnnotationState; +} + +declare class TimeseriesChart extends React.Component { focusXScale: d3.scale.Ordinal<{}, number>; } diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js index b2d054becbb1..6f2beb8fe906 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js @@ -10,7 +10,7 @@ */ import PropTypes from 'prop-types'; -import React, { Component } from 'react'; +import React, { Component, useContext } from 'react'; import useObservable from 'react-use/lib/useObservable'; import { isEqual, reduce, each, get } from 'lodash'; import d3 from 'd3'; @@ -21,7 +21,6 @@ import { getSeverityWithLow, getMultiBucketImpactLabel, } from '../../../../../common/util/anomaly_utils'; -import { annotation$ } from '../../../services/annotations_service'; import { formatValue } from '../../../formatters/format_value'; import { LINE_CHART_ANOMALY_RADIUS, @@ -51,7 +50,7 @@ import { unhighlightFocusChartAnnotation, ANNOTATION_MIN_WIDTH, } from './timeseries_chart_annotations'; -import { distinctUntilChanged } from 'rxjs/operators'; +import { MlAnnotationUpdatesContext } from '../../../contexts/ml/ml_annotation_updates_context'; const focusZoomPanelHeight = 25; const focusChartHeight = 310; @@ -571,7 +570,6 @@ class TimeseriesChartIntl extends Component { } renderFocusChart() { - console.log('renderFocusChart'); const { focusAggregationInterval, focusAnnotationData: focusAnnotationDataOriginalPropValue, @@ -742,7 +740,8 @@ class TimeseriesChartIntl extends Component { this.focusXScale, showAnnotations, showFocusChartTooltip, - hideFocusChartTooltip + hideFocusChartTooltip, + this.props.annotationUpdatesService ); // disable brushing (creation of annotations) when annotations aren't shown @@ -1800,17 +1799,17 @@ class TimeseriesChartIntl extends Component { } export const TimeseriesChart = (props) => { - const annotationProp = useObservable( - annotation$.pipe( - distinctUntilChanged((prev, curr) => { - // prevent re-rendering - return prev !== null && curr !== null; - }) - ) - ); + const annotationUpdatesService = useContext(MlAnnotationUpdatesContext); + const annotationProp = useObservable(annotationUpdatesService.isAnnotationInitialized$()); if (annotationProp === undefined) { return null; } - return ; + return ( + + ); }; diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart_annotations.ts b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart_annotations.ts index bd86d07dcd8b..8757fbad19df 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart_annotations.ts +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart_annotations.ts @@ -13,13 +13,14 @@ import { Dictionary } from '../../../../../common/types/common'; import { TimeseriesChart } from './timeseries_chart'; -import { annotation$ } from '../../../services/annotations_service'; +import { AnnotationUpdatesService } from '../../../services/annotations_service'; export const ANNOTATION_MASK_ID = 'mlAnnotationMask'; // getAnnotationBrush() is expected to be called like getAnnotationBrush.call(this) // so it gets passed on the context of the component it gets called from. export function getAnnotationBrush(this: TimeseriesChart) { + const { annotationUpdatesService } = this.props; const focusXScale = this.focusXScale; const annotateBrush = d3.svg.brush().x(focusXScale).on('brushend', brushend.bind(this)); @@ -35,7 +36,7 @@ export function getAnnotationBrush(this: TimeseriesChart) { const endTimestamp = extent[1].getTime(); if (timestamp === endTimestamp) { - annotation$.next(null); + annotationUpdatesService.setValue(null); return; } @@ -47,7 +48,7 @@ export function getAnnotationBrush(this: TimeseriesChart) { type: ANNOTATION_TYPE.ANNOTATION, }; - annotation$.next(annotation); + annotationUpdatesService.setValue(annotation); } return annotateBrush; @@ -105,7 +106,8 @@ export function renderAnnotations( focusXScale: TimeseriesChart['focusXScale'], showAnnotations: boolean, showFocusChartTooltip: (d: Annotation, t: object) => {}, - hideFocusChartTooltip: () => void + hideFocusChartTooltip: () => void, + annotationUpdatesService: AnnotationUpdatesService ) { const upperRectMargin = ANNOTATION_UPPER_RECT_MARGIN; const upperTextMargin = ANNOTATION_UPPER_TEXT_MARGIN; @@ -153,9 +155,9 @@ export function renderAnnotations( // clear a possible existing annotation set up for editing before setting the new one. // this needs to be done explicitly here because a new annotation created using the brush tool // could still be present in the chart. - annotation$.next(null); + annotationUpdatesService.setValue(null); // set the actual annotation and trigger the flyout - annotation$.next(d); + annotationUpdatesService.setValue(d); }); rects diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart_with_tooltip.tsx b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart_with_tooltip.tsx index 89e7d292dbdf..23e7740dd048 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart_with_tooltip.tsx +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart_with_tooltip.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { FC, useEffect, useState, useCallback } from 'react'; +import React, { FC, useEffect, useState, useCallback, useContext } from 'react'; import { i18n } from '@kbn/i18n'; import { MlTooltipComponent } from '../../../components/chart_tooltip'; import { TimeseriesChart } from './timeseries_chart'; @@ -16,6 +16,7 @@ import { useMlKibana, useNotifications } from '../../../contexts/kibana'; import { getBoundsRoundedToInterval } from '../../../util/time_buckets'; import { ANNOTATION_EVENT_USER } from '../../../../../common/constants/annotations'; import { getControlsForDetector } from '../../get_controls_for_detector'; +import { MlAnnotationUpdatesContext } from '../../../contexts/ml/ml_annotation_updates_context'; interface TimeSeriesChartWithTooltipsProps { bounds: any; @@ -50,6 +51,8 @@ export const TimeSeriesChartWithTooltips: FC = }, } = useMlKibana(); + const annotationUpdatesService = useContext(MlAnnotationUpdatesContext); + const [annotationData, setAnnotationData] = useState([]); const showAnnotationErrorToastNotification = useCallback((error?: string) => { @@ -123,6 +126,7 @@ export const TimeSeriesChartWithTooltips: FC = {(tooltipService) => ( {fieldNamesWithEmptyValues.length > 0 && ( From 77da781144c9d694da4b605c36364237191f1167 Mon Sep 17 00:00:00 2001 From: Quynh Nguyen <43350163+qn895@users.noreply.github.com> Date: Wed, 18 Nov 2020 10:57:22 -0600 Subject: [PATCH 05/49] [ML] Persist URL state for Anomaly detection jobs using metric function (#83507) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../ml/common/types/ml_url_generator.ts | 2 + .../routing/routes/timeseriesexplorer.tsx | 15 +++ .../plot_function_controls.tsx | 66 +++++++++++- .../series_controls/series_controls.tsx | 5 +- .../get_function_description.ts | 19 +++- .../timeseriesexplorer/timeseriesexplorer.js | 100 ++++++------------ .../timeseriesexplorer_constants.ts | 1 + .../get_viewable_detectors.ts | 29 +++++ .../anomaly_detection_urls_generator.ts | 5 + 9 files changed, 167 insertions(+), 75 deletions(-) create mode 100644 x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/get_viewable_detectors.ts diff --git a/x-pack/plugins/ml/common/types/ml_url_generator.ts b/x-pack/plugins/ml/common/types/ml_url_generator.ts index b188ac0a8757..9a3d8fc4a4f0 100644 --- a/x-pack/plugins/ml/common/types/ml_url_generator.ts +++ b/x-pack/plugins/ml/common/types/ml_url_generator.ts @@ -132,6 +132,7 @@ export interface TimeSeriesExplorerAppState { forecastId?: string; detectorIndex?: number; entities?: Record; + functionDescription?: string; }; query?: any; } @@ -145,6 +146,7 @@ export interface TimeSeriesExplorerPageState entities?: Record; forecastId?: string; globalState?: MlCommonGlobalState; + functionDescription?: string; } export type TimeSeriesExplorerUrlState = MLPageState< 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 2653781ce1a3..f0fb4558bcfa 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx @@ -161,6 +161,11 @@ export const TimeSeriesExplorerUrlStateManager: FC ); diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/plot_function_controls/plot_function_controls.tsx b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/plot_function_controls/plot_function_controls.tsx index 0356c20fecb9..8e26a912a605 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/plot_function_controls/plot_function_controls.tsx +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/plot_function_controls/plot_function_controls.tsx @@ -3,9 +3,14 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { useCallback, useEffect } from 'react'; import { EuiFlexItem, EuiFormRow, EuiSelect } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { mlJobService } from '../../../services/job_service'; +import { getFunctionDescription, isMetricDetector } from '../../get_function_description'; +import { useToastNotificationService } from '../../../services/toast_notification_service'; +import { ML_JOB_AGGREGATION } from '../../../../../common/constants/aggregation_types'; +import type { CombinedJob } from '../../../../../common/types/anomaly_detection_jobs'; const plotByFunctionOptions = [ { @@ -30,11 +35,70 @@ const plotByFunctionOptions = [ export const PlotByFunctionControls = ({ functionDescription, setFunctionDescription, + selectedDetectorIndex, + selectedJobId, + selectedEntities, }: { functionDescription: undefined | string; setFunctionDescription: (func: string) => void; + selectedDetectorIndex: number; + selectedJobId: string; + selectedEntities: Record; }) => { + const toastNotificationService = useToastNotificationService(); + + const getFunctionDescriptionToPlot = useCallback( + async ( + _selectedDetectorIndex: number, + _selectedEntities: Record, + _selectedJobId: string, + _selectedJob: CombinedJob + ) => { + const functionToPlot = await getFunctionDescription( + { + selectedDetectorIndex: _selectedDetectorIndex, + selectedEntities: _selectedEntities, + selectedJobId: _selectedJobId, + selectedJob: _selectedJob, + }, + toastNotificationService + ); + setFunctionDescription(functionToPlot); + }, + [setFunctionDescription, toastNotificationService] + ); + + useEffect(() => { + if (functionDescription !== undefined) { + return; + } + const selectedJob = mlJobService.getJob(selectedJobId); + if ( + // set if only entity controls are picked + selectedEntities !== undefined && + functionDescription === undefined && + isMetricDetector(selectedJob, selectedDetectorIndex) + ) { + const detector = selectedJob.analysis_config.detectors[selectedDetectorIndex]; + if (detector?.function === ML_JOB_AGGREGATION.METRIC) { + getFunctionDescriptionToPlot( + selectedDetectorIndex, + selectedEntities, + selectedJobId, + selectedJob + ); + } + } + }, [ + setFunctionDescription, + selectedDetectorIndex, + selectedEntities, + selectedJobId, + functionDescription, + ]); + if (functionDescription === undefined) return null; + return ( = selectedDetectorIndex) { + const detector = selectedJob.analysis_config.detectors[selectedDetectorIndex]; + if (detector?.function === ML_JOB_AGGREGATION.METRIC) { + return true; + } + } + return false; +} /** * Get the function description from the record with the highest anomaly score @@ -31,11 +43,7 @@ export const getFunctionDescription = async ( ) => { // if the detector's function is metric, fetch the highest scoring anomaly record // and set to plot the function_description (avg/min/max) of that record by default - if ( - selectedJob?.analysis_config?.detectors[selectedDetectorIndex]?.function !== - ML_JOB_AGGREGATION.METRIC - ) - return; + if (!isMetricDetector(selectedJob, selectedDetectorIndex)) return; const entityControls = getControlsForDetector( selectedDetectorIndex, @@ -43,6 +51,7 @@ export const getFunctionDescription = async ( selectedJobId ); const criteriaFields = getCriteriaFields(selectedDetectorIndex, entityControls); + try { const resp = await mlResultsService .getRecordsForCriteria([selectedJob.job_id], criteriaFields, 0, null, null, 1) diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js index e3b6e38f47ba..f22cc191ef84 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js @@ -8,7 +8,7 @@ * React component for rendering Single Metric Viewer. */ -import { each, find, get, has, isEqual } from 'lodash'; +import { find, get, has, isEqual } from 'lodash'; import moment from 'moment-timezone'; import { Subject, Subscription, forkJoin } from 'rxjs'; import { map, debounceTime, switchMap, tap, withLatestFrom } from 'rxjs/operators'; @@ -40,7 +40,6 @@ import { isModelPlotEnabled, isModelPlotChartableForDetector, isSourceDataChartableForDetector, - isTimeSeriesViewDetector, mlFunctionToESAggregation, } from '../../../common/util/job_utils'; @@ -84,7 +83,8 @@ import { SeriesControls } from './components/series_controls'; import { TimeSeriesChartWithTooltips } from './components/timeseries_chart/timeseries_chart_with_tooltip'; import { PlotByFunctionControls } from './components/plot_function_controls'; import { aggregationTypeTransform } from '../../../common/util/anomaly_utils'; -import { getFunctionDescription } from './get_function_description'; +import { isMetricDetector } from './get_function_description'; +import { getViewableDetectors } from './timeseriesexplorer_utils/get_viewable_detectors'; // Used to indicate the chart is being plotted across // all partition field values, where the cardinality of the field cannot be @@ -93,20 +93,6 @@ const allValuesLabel = i18n.translate('xpack.ml.timeSeriesExplorer.allPartitionV defaultMessage: 'all', }); -export function getViewableDetectors(selectedJob) { - const jobDetectors = selectedJob.analysis_config.detectors; - const viewableDetectors = []; - each(jobDetectors, (dtr, index) => { - if (isTimeSeriesViewDetector(selectedJob, index)) { - viewableDetectors.push({ - index, - detector_description: dtr.detector_description, - }); - } - }); - return viewableDetectors; -} - function getTimeseriesexplorerDefaultState() { return { chartDetails: undefined, @@ -143,8 +129,6 @@ function getTimeseriesexplorerDefaultState() { zoomTo: undefined, zoomFromFocusLoaded: undefined, zoomToFocusLoaded: undefined, - // Sets function to plot by if original function is metric - functionDescription: undefined, }; } @@ -223,9 +207,7 @@ export class TimeSeriesExplorer extends React.Component { }; setFunctionDescription = (selectedFuction) => { - this.setState({ - functionDescription: selectedFuction, - }); + this.props.appStateHandler(APP_STATE_ACTION.SET_FUNCTION_DESCRIPTION, selectedFuction); }; previousChartProps = {}; @@ -280,9 +262,17 @@ export class TimeSeriesExplorer extends React.Component { * Gets focus data for the current component state/ */ getFocusData(selection) { - const { selectedJobId, selectedForecastId, selectedDetectorIndex } = this.props; - const { modelPlotEnabled, functionDescription } = this.state; + const { + selectedJobId, + selectedForecastId, + selectedDetectorIndex, + functionDescription, + } = this.props; + const { modelPlotEnabled } = this.state; const selectedJob = mlJobService.getJob(selectedJobId); + if (isMetricDetector(selectedJob, selectedDetectorIndex) && functionDescription === undefined) { + return; + } const entityControls = this.getControlsForDetector(); // Calculate the aggregation interval for the focus chart. @@ -333,8 +323,8 @@ export class TimeSeriesExplorer extends React.Component { selectedJobId, tableInterval, tableSeverity, + functionDescription, } = this.props; - const { functionDescription } = this.state; const selectedJob = mlJobService.getJob(selectedJobId); const entityControls = this.getControlsForDetector(); @@ -394,24 +384,6 @@ export class TimeSeriesExplorer extends React.Component { ); }; - getFunctionDescription = async () => { - const { selectedDetectorIndex, selectedEntities, selectedJobId } = this.props; - const selectedJob = mlJobService.getJob(selectedJobId); - - const functionDescriptionToPlot = await getFunctionDescription( - { - selectedDetectorIndex, - selectedEntities, - selectedJobId, - selectedJob, - }, - this.props.toastNotificationService - ); - if (!this.unmounted) { - this.setFunctionDescription(functionDescriptionToPlot); - } - }; - setForecastId = (forecastId) => { this.props.appStateHandler(APP_STATE_ACTION.SET_FORECAST_ID, forecastId); }; @@ -424,14 +396,22 @@ export class TimeSeriesExplorer extends React.Component { selectedForecastId, selectedJobId, zoom, + functionDescription, } = this.props; - const { loadCounter: currentLoadCounter, functionDescription } = this.state; + const { loadCounter: currentLoadCounter } = this.state; const currentSelectedJob = mlJobService.getJob(selectedJobId); if (currentSelectedJob === undefined) { return; } + if ( + isMetricDetector(currentSelectedJob, selectedDetectorIndex) && + functionDescription === undefined + ) { + return; + } + const functionToPlotByIfMetric = aggregationTypeTransform.toES(functionDescription); this.contextChartSelectedInitCallDone = false; @@ -845,7 +825,7 @@ export class TimeSeriesExplorer extends React.Component { this.componentDidUpdate(); } - componentDidUpdate(previousProps, previousState) { + componentDidUpdate(previousProps) { if (previousProps === undefined || previousProps.selectedJobId !== this.props.selectedJobId) { this.contextChartSelectedInitCallDone = false; this.setState({ fullRefresh: false, loading: true }, () => { @@ -853,15 +833,6 @@ export class TimeSeriesExplorer extends React.Component { }); } - if ( - previousProps === undefined || - previousProps.selectedJobId !== this.props.selectedJobId || - previousProps.selectedDetectorIndex !== this.props.selectedDetectorIndex || - !isEqual(previousProps.selectedEntities, this.props.selectedEntities) - ) { - this.getFunctionDescription(); - } - if ( previousProps === undefined || previousProps.selectedForecastId !== this.props.selectedForecastId @@ -885,7 +856,7 @@ export class TimeSeriesExplorer extends React.Component { !isEqual(previousProps.selectedEntities, this.props.selectedEntities) || previousProps.selectedForecastId !== this.props.selectedForecastId || previousProps.selectedJobId !== this.props.selectedJobId || - previousState.functionDescription !== this.state.functionDescription + previousProps.functionDescription !== this.props.functionDescription ) { const fullRefresh = previousProps === undefined || @@ -894,7 +865,7 @@ export class TimeSeriesExplorer extends React.Component { !isEqual(previousProps.selectedEntities, this.props.selectedEntities) || previousProps.selectedForecastId !== this.props.selectedForecastId || previousProps.selectedJobId !== this.props.selectedJobId || - previousState.functionDescription !== this.state.functionDescription; + previousProps.functionDescription !== this.props.functionDescription; this.loadSingleMetricData(fullRefresh); } @@ -965,7 +936,6 @@ export class TimeSeriesExplorer extends React.Component { zoomTo, zoomFromFocusLoaded, zoomToFocusLoaded, - functionDescription, } = this.state; const chartProps = { modelPlotEnabled, @@ -1044,15 +1014,13 @@ export class TimeSeriesExplorer extends React.Component { selectedEntities={this.props.selectedEntities} bounds={bounds} > - {functionDescription && ( - - )} + {arePartitioningFieldsProvided && ( diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_constants.ts b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_constants.ts index a801a1c5ce6f..6cd58f42f929 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_constants.ts +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_constants.ts @@ -15,6 +15,7 @@ export const APP_STATE_ACTION = { SET_FORECAST_ID: 'SET_FORECAST_ID', SET_ZOOM: 'SET_ZOOM', UNSET_ZOOM: 'UNSET_ZOOM', + SET_FUNCTION_DESCRIPTION: 'SET_FUNCTION_DESCRIPTION', }; export const CHARTS_POINT_TARGET = 500; diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/get_viewable_detectors.ts b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/get_viewable_detectors.ts new file mode 100644 index 000000000000..25d7751da827 --- /dev/null +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/get_viewable_detectors.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CombinedJob } from '../../../../common/types/anomaly_detection_jobs'; +import { isTimeSeriesViewDetector } from '../../../../common/util/job_utils'; + +interface ViewableDetector { + index: number; + detector_description: string | undefined; + function: string; +} +export function getViewableDetectors(selectedJob: CombinedJob): ViewableDetector[] { + const jobDetectors = selectedJob.analysis_config.detectors; + const viewableDetectors: ViewableDetector[] = []; + jobDetectors.forEach((dtr, index) => { + if (isTimeSeriesViewDetector(selectedJob, index)) { + viewableDetectors.push({ + index, + detector_description: dtr.detector_description, + function: dtr.function, + }); + } + }); + + return viewableDetectors; +} diff --git a/x-pack/plugins/ml/public/ml_url_generator/anomaly_detection_urls_generator.ts b/x-pack/plugins/ml/public/ml_url_generator/anomaly_detection_urls_generator.ts index 6d7e286a2947..d53dfa8fd19c 100644 --- a/x-pack/plugins/ml/public/ml_url_generator/anomaly_detection_urls_generator.ts +++ b/x-pack/plugins/ml/public/ml_url_generator/anomaly_detection_urls_generator.ts @@ -163,6 +163,7 @@ export function createSingleMetricViewerUrl( forecastId, entities, globalState, + functionDescription, } = params; let queryState: Partial = {}; @@ -189,6 +190,10 @@ export function createSingleMetricViewerUrl( if (entities !== undefined) { mlTimeSeriesExplorer.entities = entities; } + if (functionDescription !== undefined) { + mlTimeSeriesExplorer.functionDescription = functionDescription; + } + appState.mlTimeSeriesExplorer = mlTimeSeriesExplorer; if (zoom) appState.zoom = zoom; From 4917df30b93769f21a2cdba9faa6fddd25b2344d Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Wed, 18 Nov 2020 20:23:08 +0300 Subject: [PATCH 06/49] Update typescript eslint to v4.8 (#83520) * update deps * update rules use type-aware @typescript-eslint/no-shadow instead of no-shadow. do not use no-undef, rely on TypeScript instead * fix or mute all lint errors * react-hooks eslint plugin fails on ? syntax * fix wrong typings in viz * remove React as a global type * fix eslint errors * update version to 4.8.1 * fix a new error --- .eslintrc.js | 6 +- .../public/todo/todo.tsx | 2 +- .../with_data_services/components/app.tsx | 1 + package.json | 6 +- .../typescript.js | 6 +- src/core/public/utils/crypto/sha256.ts | 2 +- .../server/http/router/validator/validator.ts | 4 +- .../forms/hook_form_lib/hooks/use_field.ts | 6 +- ...ate_state_container_react_helpers.test.tsx | 6 +- .../kibana_utils/demos/state_sync/url.ts | 4 +- .../public/state_sync/state_sync.test.ts | 2 +- .../controls/has_extended_bounds.tsx | 1 - src/plugins/visualizations/public/vis.ts | 4 +- tsconfig.base.json | 1 - .../AgentConfigurationCreateEdit/index.tsx | 1 - .../TransactionActionMenu.tsx | 1 - x-pack/plugins/apm/typings/common.d.ts | 6 +- .../components/__tests__/app.test.tsx | 2 +- .../settings/__tests__/settings.test.tsx | 2 +- .../package_policy_input_config.tsx | 1 + .../package_policy_input_stream.tsx | 1 + .../step_select_agent_policy.tsx | 2 + .../sections/agents/agent_list_page/index.tsx | 1 + .../agent_reassign_policy_flyout/index.tsx | 1 + .../epm/components/package_list_grid.tsx | 2 +- .../template_form/template_form.tsx | 1 + .../inventory/components/expression.tsx | 1 - .../alerting/inventory/components/metric.tsx | 16 +-- .../components/expression_editor/editor.tsx | 2 - .../components/expression.tsx | 5 - .../infra/public/components/header/header.tsx | 3 +- .../saved_views/view_list_modal.tsx | 2 +- .../logs/log_filter/log_filter_state.ts | 1 - .../containers/logs/log_source/log_source.ts | 1 - .../containers/source/use_source_via_http.ts | 1 - .../hooks/use_bulk_get_saved_object.tsx | 1 - .../public/hooks/use_create_saved_object.tsx | 1 - .../public/hooks/use_delete_saved_object.tsx | 1 - .../public/hooks/use_find_saved_object.tsx | 1 - .../public/hooks/use_get_saved_object.tsx | 1 - .../public/hooks/use_update_saved_object.tsx | 1 - .../page_results_content.tsx | 1 - .../source_configuration_settings.tsx | 1 - .../components/node_details/overlay.tsx | 1 + .../metric_detail/components/sub_section.tsx | 1 - .../lens/public/app_plugin/app.test.tsx | 2 +- .../editor_frame/suggestion_panel.tsx | 1 + .../public/application/index.tsx | 1 + .../matrix_histogram/index.ts | 2 +- .../draggable_wrapper_hover_content.tsx | 2 - .../common/components/header_page/types.ts | 2 +- .../components/matrix_histogram/types.ts | 2 +- .../common/components/toasters/utils.ts | 2 +- .../public/common/store/types.ts | 2 +- .../alerts_histogram_panel/index.tsx | 1 - .../timeline_actions/alert_context_menu.tsx | 5 + .../detection_engine/rules/use_rules.tsx | 4 +- .../detection_engine/rules/all/reducer.ts | 2 +- .../detection_engine/rules/details/index.tsx | 1 - .../pages/endpoint_hosts/view/index.tsx | 1 + .../public/overview/components/types.ts | 2 +- .../public/resolver/store/data/selectors.ts | 22 ++-- .../public/resolver/store/selectors.ts | 4 +- .../public/resolver/store/ui/selectors.ts | 5 +- .../public/resolver/types.ts | 2 +- .../components/open_timeline/types.ts | 6 +- .../components/timeline/body/helpers.tsx | 2 +- .../body/renderers/column_renderer.ts | 2 +- .../expressions/boundary_index_expression.tsx | 2 + .../expressions/entity_by_expression.tsx | 1 + .../connector_add_modal.tsx | 1 + .../sections/alert_form/alert_add.tsx | 1 + .../common/lib/saved_object_test_utils.ts | 2 +- yarn.lock | 111 +++++++++--------- 74 files changed, 152 insertions(+), 155 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index ad9de04251e4..5ac1a79d0327 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -863,7 +863,8 @@ module.exports = { 'no-shadow-restricted-names': 'error', 'no-sparse-arrays': 'error', 'no-this-before-super': 'error', - 'no-undef': 'error', + // rely on typescript + 'no-undef': 'off', 'no-unreachable': 'error', 'no-unsafe-finally': 'error', 'no-useless-call': 'error', @@ -998,7 +999,8 @@ module.exports = { 'no-shadow-restricted-names': 'error', 'no-sparse-arrays': 'error', 'no-this-before-super': 'error', - 'no-undef': 'error', + // rely on typescript + 'no-undef': 'off', 'no-unreachable': 'error', 'no-unsafe-finally': 'error', 'no-useless-call': 'error', diff --git a/examples/state_containers_examples/public/todo/todo.tsx b/examples/state_containers_examples/public/todo/todo.tsx index b6f4f6550026..fe597042d38c 100644 --- a/examples/state_containers_examples/public/todo/todo.tsx +++ b/examples/state_containers_examples/public/todo/todo.tsx @@ -313,7 +313,7 @@ export const TodoAppPage: React.FC<{ function withDefaultState( stateContainer: BaseStateContainer, - // eslint-disable-next-line no-shadow + // eslint-disable-next-line @typescript-eslint/no-shadow defaultState: State ): INullableBaseStateContainer { return { diff --git a/examples/state_containers_examples/public/with_data_services/components/app.tsx b/examples/state_containers_examples/public/with_data_services/components/app.tsx index d007cfd97edc..8f444b96524c 100644 --- a/examples/state_containers_examples/public/with_data_services/components/app.tsx +++ b/examples/state_containers_examples/public/with_data_services/components/app.tsx @@ -180,6 +180,7 @@ function useGlobalStateSyncing( }, [query, kbnUrlStateStorage]); } +// eslint-disable-next-line @typescript-eslint/no-shadow function useAppStateSyncing( appStateContainer: BaseStateContainer, query: DataPublicPluginStart['query'], diff --git a/package.json b/package.json index 2560be4f55d0..87e51abe49be 100644 --- a/package.json +++ b/package.json @@ -567,8 +567,8 @@ "@types/xml2js": "^0.4.5", "@types/yauzl": "^2.9.1", "@types/zen-observable": "^0.8.0", - "@typescript-eslint/eslint-plugin": "^3.10.0", - "@typescript-eslint/parser": "^3.10.0", + "@typescript-eslint/eslint-plugin": "^4.8.1", + "@typescript-eslint/parser": "^4.8.1", "@welldone-software/why-did-you-render": "^5.0.0", "@yarnpkg/lockfile": "^1.1.0", "abab": "^1.0.4", @@ -644,7 +644,7 @@ "eslint-plugin-prefer-object-spread": "^1.2.1", "eslint-plugin-prettier": "^3.1.4", "eslint-plugin-react": "^7.20.3", - "eslint-plugin-react-hooks": "^4.0.4", + "eslint-plugin-react-hooks": "^4.2.0", "eslint-plugin-react-perf": "^3.2.3", "expose-loader": "^0.7.5", "faker": "1.1.0", diff --git a/packages/elastic-eslint-config-kibana/typescript.js b/packages/elastic-eslint-config-kibana/typescript.js index d3e80b744815..b439f5297032 100644 --- a/packages/elastic-eslint-config-kibana/typescript.js +++ b/packages/elastic-eslint-config-kibana/typescript.js @@ -189,6 +189,11 @@ module.exports = { '@typescript-eslint/no-extra-non-null-assertion': 'error', '@typescript-eslint/no-misused-new': 'error', '@typescript-eslint/no-namespace': 'error', + '@typescript-eslint/no-shadow': 'error', + // rely on typescript + '@typescript-eslint/no-undef': 'off', + 'no-undef': 'off', + '@typescript-eslint/triple-slash-reference': ['error', { path: 'never', types: 'never', @@ -218,7 +223,6 @@ module.exports = { 'no-eval': 'error', 'no-new-wrappers': 'error', 'no-script-url': 'error', - 'no-shadow': 'error', 'no-throw-literal': 'error', 'no-undef-init': 'error', 'no-unsafe-finally': 'error', diff --git a/src/core/public/utils/crypto/sha256.ts b/src/core/public/utils/crypto/sha256.ts index eaa057d60468..13e0d405a706 100644 --- a/src/core/public/utils/crypto/sha256.ts +++ b/src/core/public/utils/crypto/sha256.ts @@ -130,7 +130,7 @@ type BufferEncoding = | 'binary' | 'hex'; -/* eslint-disable no-bitwise, no-shadow */ +/* eslint-disable no-bitwise, @typescript-eslint/no-shadow */ export class Sha256 { private _a: number; private _b: number; diff --git a/src/core/server/http/router/validator/validator.ts b/src/core/server/http/router/validator/validator.ts index babca87495a4..be7781fdacbe 100644 --- a/src/core/server/http/router/validator/validator.ts +++ b/src/core/server/http/router/validator/validator.ts @@ -143,8 +143,8 @@ export type RouteValidatorFullConfig = RouteValidatorConfig & * @internal */ export class RouteValidator

{ - public static from

( - opts: RouteValidator | RouteValidatorFullConfig + public static from<_P = {}, _Q = {}, _B = {}>( + opts: RouteValidator<_P, _Q, _B> | RouteValidatorFullConfig<_P, _Q, _B> ) { if (opts instanceof RouteValidator) { return opts; diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.ts index f4f13a698ee3..eb67842bff83 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.ts +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.ts @@ -118,16 +118,16 @@ export const useField = ( * updating the "value" state. */ const formatInputValue = useCallback( - (inputValue: unknown): T => { + (inputValue: unknown): U => { const isEmptyString = typeof inputValue === 'string' && inputValue.trim() === ''; if (isEmptyString || !formatters) { - return inputValue as T; + return inputValue as U; } const formData = __getFormData$().value; - return formatters.reduce((output, formatter) => formatter(output, formData), inputValue) as T; + return formatters.reduce((output, formatter) => formatter(output, formData), inputValue) as U; }, [formatters, __getFormData$] ); diff --git a/src/plugins/kibana_utils/common/state_containers/create_state_container_react_helpers.test.tsx b/src/plugins/kibana_utils/common/state_containers/create_state_container_react_helpers.test.tsx index 81101f318073..48e5ee3c87e3 100644 --- a/src/plugins/kibana_utils/common/state_containers/create_state_container_react_helpers.test.tsx +++ b/src/plugins/kibana_utils/common/state_containers/create_state_container_react_helpers.test.tsx @@ -97,11 +97,11 @@ test('context receives stateContainer', () => { const { Provider, context } = createStateContainerReactHelpers(); ReactDOM.render( - /* eslint-disable no-shadow */ + /* eslint-disable @typescript-eslint/no-shadow */ {(stateContainer) => stateContainer.get().foo} , - /* eslint-enable no-shadow */ + /* eslint-enable @typescript-eslint/no-shadow */ container ); @@ -116,7 +116,7 @@ describe('hooks', () => { const stateContainer = createStateContainer({ foo: 'bar' }); const { Provider, useContainer } = createStateContainerReactHelpers(); const Demo: React.FC<{}> = () => { - // eslint-disable-next-line no-shadow + // eslint-disable-next-line @typescript-eslint/no-shadow const stateContainer = useContainer(); return <>{stateContainer.get().foo}; }; diff --git a/src/plugins/kibana_utils/demos/state_sync/url.ts b/src/plugins/kibana_utils/demos/state_sync/url.ts index e8e63eefe866..f7a66e79b817 100644 --- a/src/plugins/kibana_utils/demos/state_sync/url.ts +++ b/src/plugins/kibana_utils/demos/state_sync/url.ts @@ -56,9 +56,9 @@ export const result = Promise.resolve() }); function withDefaultState( - // eslint-disable-next-line no-shadow + // eslint-disable-next-line @typescript-eslint/no-shadow stateContainer: BaseStateContainer, - // eslint-disable-next-line no-shadow + // eslint-disable-next-line @typescript-eslint/no-shadow defaultState: State ): INullableBaseStateContainer { return { diff --git a/src/plugins/kibana_utils/public/state_sync/state_sync.test.ts b/src/plugins/kibana_utils/public/state_sync/state_sync.test.ts index 4b2b2bd99911..f96c243e82f8 100644 --- a/src/plugins/kibana_utils/public/state_sync/state_sync.test.ts +++ b/src/plugins/kibana_utils/public/state_sync/state_sync.test.ts @@ -354,7 +354,7 @@ describe('state_sync', () => { function withDefaultState( stateContainer: BaseStateContainer, - // eslint-disable-next-line no-shadow + // eslint-disable-next-line @typescript-eslint/no-shadow defaultState: State ): INullableBaseStateContainer { return { diff --git a/src/plugins/vis_default_editor/public/components/controls/has_extended_bounds.tsx b/src/plugins/vis_default_editor/public/components/controls/has_extended_bounds.tsx index ae3da8e203a5..a316a087c8bc 100644 --- a/src/plugins/vis_default_editor/public/components/controls/has_extended_bounds.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/has_extended_bounds.tsx @@ -38,7 +38,6 @@ function HasExtendedBoundsParamEditor(props: AggParamEditorProps) { setValue(value && agg.params.min_doc_count); } - /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, [agg.params.min_doc_count, setValue, value]); return ( diff --git a/src/plugins/visualizations/public/vis.ts b/src/plugins/visualizations/public/vis.ts index cae9058071b6..75c889af3d5c 100644 --- a/src/plugins/visualizations/public/vis.ts +++ b/src/plugins/visualizations/public/vis.ts @@ -97,13 +97,13 @@ export class Vis { public readonly uiState: PersistedState; constructor(visType: string, visState: SerializedVis = {} as any) { - this.type = this.getType(visType); + this.type = this.getType(visType); this.params = this.getParams(visState.params); this.uiState = new PersistedState(visState.uiState); this.id = visState.id; } - private getType(visType: string) { + private getType(visType: string) { const type = getTypes().get(visType); if (!type) { const errorMessage = i18n.translate('visualizations.visualizationTypeInvalidMessage', { diff --git a/tsconfig.base.json b/tsconfig.base.json index 0aad8d6b9c12..111c9dbc949d 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -53,7 +53,6 @@ "types": [ "node", "jest", - "react", "flot", "jest-styled-components", "@testing-library/jest-dom" diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.tsx index 1c42f146b867..4f94f255a4e4 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.tsx @@ -81,7 +81,6 @@ export function AgentConfigurationCreateEdit({ ..._newConfig, settings: existingConfig?.settings || {}, })); - /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, [existingConfig]); // update newConfig when existingConfig has loaded diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx index 4a548b44cf36..3f72f07b2a7d 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx +++ b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx @@ -65,7 +65,6 @@ export function TransactionActionMenu({ transaction }: Props) { { key: 'transaction.name', value: transaction?.transaction.name }, { key: 'transaction.type', value: transaction?.transaction.type }, ].filter((filter): filter is Filter => typeof filter.value === 'string'), - /* eslint-disable-next-line react-hooks/exhaustive-deps */ [transaction] ); diff --git a/x-pack/plugins/apm/typings/common.d.ts b/x-pack/plugins/apm/typings/common.d.ts index 9133315c4c16..fd5b5c8ea087 100644 --- a/x-pack/plugins/apm/typings/common.d.ts +++ b/x-pack/plugins/apm/typings/common.d.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - +import type { UnwrapPromise } from '@kbn/utility-types'; import '../../../typings/rison_node'; import '../../infra/types/eui'; // EUIBasicTable @@ -21,8 +21,6 @@ type AllowUnknownObjectProperties = T extends object } : T; -export type PromiseValueType = Value extends Promise - ? Value - : Value; +export type PromiseValueType> = UnwrapPromise; export type Maybe = T | null | undefined; diff --git a/x-pack/plugins/canvas/shareable_runtime/components/__tests__/app.test.tsx b/x-pack/plugins/canvas/shareable_runtime/components/__tests__/app.test.tsx index eaf45db0a0b9..755f6907a4d5 100644 --- a/x-pack/plugins/canvas/shareable_runtime/components/__tests__/app.test.tsx +++ b/x-pack/plugins/canvas/shareable_runtime/components/__tests__/app.test.tsx @@ -39,7 +39,7 @@ jest.mock('../../supported_renderers'); jest.mock('@elastic/eui/lib/components/portal/portal', () => { // Local constants are not supported in Jest mocks-- they must be // imported within the mock. - // eslint-disable-next-line no-shadow + // eslint-disable-next-line @typescript-eslint/no-shadow const React = jest.requireActual('react'); return { EuiPortal: (props: any) =>

{props.children}
, diff --git a/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/__tests__/settings.test.tsx b/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/__tests__/settings.test.tsx index 28aa6ef90aed..a4f2aca9bd79 100644 --- a/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/__tests__/settings.test.tsx +++ b/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/__tests__/settings.test.tsx @@ -25,7 +25,7 @@ jest.mock('@elastic/eui/lib/services/accessibility', () => { }; }); jest.mock('@elastic/eui/lib/components/portal/portal', () => { - // eslint-disable-next-line no-shadow + // eslint-disable-next-line @typescript-eslint/no-shadow const React = jest.requireActual('react'); return { EuiPortal: (props: any) =>
{props.children}
, diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/package_policy_input_config.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/package_policy_input_config.tsx index 177354dad14d..75000ad7e1d3 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/package_policy_input_config.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/package_policy_input_config.tsx @@ -47,6 +47,7 @@ export const PackagePolicyInputConfig: React.FunctionComponent<{ const hasErrors = forceShowErrors && validationHasErrors(inputVarsValidationResults); const requiredVars: RegistryVarsEntry[] = []; + // eslint-disable-next-line react-hooks/exhaustive-deps const advancedVars: RegistryVarsEntry[] = []; if (packageInputVars) { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/package_policy_input_stream.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/package_policy_input_stream.tsx index 963d0da50ce7..11d11ed33d5d 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/package_policy_input_stream.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/package_policy_input_stream.tsx @@ -49,6 +49,7 @@ export const PackagePolicyInputStreamConfig: React.FunctionComponent<{ const hasErrors = forceShowErrors && validationHasErrors(inputStreamValidationResults); const requiredVars: RegistryVarsEntry[] = []; + // eslint-disable-next-line react-hooks/exhaustive-deps const advancedVars: RegistryVarsEntry[] = []; if (packageInputStream.vars && packageInputStream.vars.length) { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_select_agent_policy.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_select_agent_policy.tsx index 525a22414699..9c94bb939cdf 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_select_agent_policy.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_select_agent_policy.tsx @@ -91,6 +91,7 @@ export const StepSelectAgentPolicy: React.FunctionComponent<{ sortOrder: 'asc', full: true, }); + // eslint-disable-next-line react-hooks/exhaustive-deps const agentPolicies = agentPoliciesData?.items || []; const agentPoliciesById = agentPolicies.reduce( (acc: { [key: string]: GetAgentPoliciesResponseItem }, policy) => { @@ -131,6 +132,7 @@ export const StepSelectAgentPolicy: React.FunctionComponent<{ } }, [selectedPolicyId, agentPolicy, updateAgentPolicy, setIsLoadingSecondStep]); + // eslint-disable-next-line react-hooks/exhaustive-deps const agentPolicyOptions: Array> = packageInfoData ? agentPolicies.map((agentConf) => { const alreadyHasLimitedPackage = diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx index d46d2aa44274..1d08a1f79197 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx @@ -278,6 +278,7 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { perPage: 1000, }); + // eslint-disable-next-line react-hooks/exhaustive-deps const agentPolicies = agentPoliciesRequest.data ? agentPoliciesRequest.data.items : []; const agentPoliciesIndexedById = useMemo(() => { return agentPolicies.reduce((acc, agentPolicy) => { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_reassign_policy_flyout/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_reassign_policy_flyout/index.tsx index 20c1eb8ff9c5..46e291e73fa7 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_reassign_policy_flyout/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_reassign_policy_flyout/index.tsx @@ -49,6 +49,7 @@ export const AgentReassignAgentPolicyFlyout: React.FunctionComponent = ({ page: 1, perPage: 1000, }); + // eslint-disable-next-line react-hooks/exhaustive-deps const agentPolicies = agentPoliciesRequest.data ? agentPoliciesRequest.data.items : []; useEffect(() => { if (!selectedAgentPolicyId && agentPolicies[0]) { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/package_list_grid.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/package_list_grid.tsx index ef3b94081b1d..b96fda2c23af 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/package_list_grid.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/components/package_list_grid.tsx @@ -37,7 +37,7 @@ export function PackageListGrid({ isLoading, controls, title, list }: ListProps) const localSearchRef = useLocalSearch(list); const onQueryChange = ({ - // eslint-disable-next-line no-shadow + // eslint-disable-next-line @typescript-eslint/no-shadow query, queryText: userInput, error, diff --git a/x-pack/plugins/index_management/public/application/components/template_form/template_form.tsx b/x-pack/plugins/index_management/public/application/components/template_form/template_form.tsx index 8e84abb5ce49..2fc0a260103f 100644 --- a/x-pack/plugins/index_management/public/application/components/template_form/template_form.tsx +++ b/x-pack/plugins/index_management/public/application/components/template_form/template_form.tsx @@ -105,6 +105,7 @@ export const TemplateForm = ({ aliases: true, }); + // eslint-disable-next-line react-hooks/exhaustive-deps const indexTemplate = defaultValue ?? { name: '', indexPatterns: [], diff --git a/x-pack/plugins/infra/public/alerting/inventory/components/expression.tsx b/x-pack/plugins/infra/public/alerting/inventory/components/expression.tsx index 097e0f1f1690..e16b2aeaacac 100644 --- a/x-pack/plugins/infra/public/alerting/inventory/components/expression.tsx +++ b/x-pack/plugins/infra/public/alerting/inventory/components/expression.tsx @@ -147,7 +147,6 @@ export const Expressions: React.FC = (props) => { timeUnit: timeUnit ?? defaultExpression.timeUnit, }); setAlertParams('criteria', exp); - /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, [setAlertParams, alertParams.criteria, timeSize, timeUnit]); const removeExpression = useCallback( diff --git a/x-pack/plugins/infra/public/alerting/inventory/components/metric.tsx b/x-pack/plugins/infra/public/alerting/inventory/components/metric.tsx index 2dd2938dfd55..dac9f91c9bd2 100644 --- a/x-pack/plugins/infra/public/alerting/inventory/components/metric.tsx +++ b/x-pack/plugins/infra/public/alerting/inventory/components/metric.tsx @@ -91,6 +91,7 @@ export const MetricExpression = ({ const [selectedOption, setSelectedOption] = useState(metric?.value); const [fieldDisplayedCustomLabel, setFieldDisplayedCustomLabel] = useState(customMetric?.label); + // eslint-disable-next-line react-hooks/exhaustive-deps const firstFieldOption = { text: i18n.translate('xpack.infra.metrics.alertFlyout.expression.metric.selectFieldLabel', { defaultMessage: 'Select a metric', @@ -106,16 +107,11 @@ export const MetricExpression = ({ [fields, customMetric?.field] ); - const expressionDisplayValue = useMemo( - () => { - return customMetricTabOpen - ? customMetric?.field && getCustomMetricLabel(customMetric) - : metric?.text || firstFieldOption.text; - }, - // The ?s are confusing eslint here, so... - // eslint-disable-next-line react-hooks/exhaustive-deps - [customMetricTabOpen, metric, customMetric, firstFieldOption] - ); + const expressionDisplayValue = useMemo(() => { + return customMetricTabOpen + ? customMetric?.field && getCustomMetricLabel(customMetric) + : metric?.text || firstFieldOption.text; + }, [customMetricTabOpen, metric, customMetric, firstFieldOption]); const onChangeTab = useCallback( (id) => { diff --git a/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/editor.tsx b/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/editor.tsx index 48639f3095d3..662b7f68f8fe 100644 --- a/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/editor.tsx +++ b/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/editor.tsx @@ -157,7 +157,6 @@ export const Editor: React.FC = (props) => { } else { return []; } - /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, [sourceStatus]); const groupByFields = useMemo(() => { @@ -168,7 +167,6 @@ export const Editor: React.FC = (props) => { } else { return []; } - /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, [sourceStatus]); const updateThreshold = useCallback( diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx index 92c017270342..48e15e0026ff 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.tsx @@ -96,7 +96,6 @@ export const Expressions: React.FC = (props) => { aggregation: 'avg', }; } - /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, [alertsContext.metadata]); const updateParams = useCallback( @@ -116,7 +115,6 @@ export const Expressions: React.FC = (props) => { timeUnit: timeUnit ?? defaultExpression.timeUnit, }); setAlertParams('criteria', exp); - /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, [setAlertParams, alertParams.criteria, timeSize, timeUnit]); const removeExpression = useCallback( @@ -127,7 +125,6 @@ export const Expressions: React.FC = (props) => { setAlertParams('criteria', exp); } }, - /* eslint-disable-next-line react-hooks/exhaustive-deps */ [setAlertParams, alertParams.criteria] ); @@ -172,7 +169,6 @@ export const Expressions: React.FC = (props) => { setTimeSize(ts || undefined); setAlertParams('criteria', criteria); }, - /* eslint-disable-next-line react-hooks/exhaustive-deps */ [alertParams.criteria, setAlertParams] ); @@ -186,7 +182,6 @@ export const Expressions: React.FC = (props) => { setTimeUnit(tu as Unit); setAlertParams('criteria', criteria); }, - /* eslint-disable-next-line react-hooks/exhaustive-deps */ [alertParams.criteria, setAlertParams] ); diff --git a/x-pack/plugins/infra/public/components/header/header.tsx b/x-pack/plugins/infra/public/components/header/header.tsx index 47ee1857da59..32ee6658ff1a 100644 --- a/x-pack/plugins/infra/public/components/header/header.tsx +++ b/x-pack/plugins/infra/public/components/header/header.tsx @@ -17,6 +17,7 @@ interface HeaderProps { export const Header = ({ breadcrumbs = [], readOnlyBadge = false }: HeaderProps) => { const chrome = useKibana().services.chrome; + // eslint-disable-next-line react-hooks/exhaustive-deps const badge = readOnlyBadge ? { text: i18n.translate('xpack.infra.header.badge.readOnly.text', { @@ -31,12 +32,10 @@ export const Header = ({ breadcrumbs = [], readOnlyBadge = false }: HeaderProps) const setBreadcrumbs = useCallback(() => { return chrome?.setBreadcrumbs(breadcrumbs || []); - /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, [breadcrumbs, chrome]); const setBadge = useCallback(() => { return chrome?.setBadge(badge); - /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, [badge, chrome]); useEffect(() => { diff --git a/x-pack/plugins/infra/public/components/saved_views/view_list_modal.tsx b/x-pack/plugins/infra/public/components/saved_views/view_list_modal.tsx index 4015d64e1097..374ba23f690e 100644 --- a/x-pack/plugins/infra/public/components/saved_views/view_list_modal.tsx +++ b/x-pack/plugins/infra/public/components/saved_views/view_list_modal.tsx @@ -60,7 +60,7 @@ export function SavedViewListModal diff --git a/x-pack/plugins/infra/public/containers/logs/log_filter/log_filter_state.ts b/x-pack/plugins/infra/public/containers/logs/log_filter/log_filter_state.ts index d5a43c0d6cff..7c903f59002d 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_filter/log_filter_state.ts +++ b/x-pack/plugins/infra/public/containers/logs/log_filter/log_filter_state.ts @@ -82,7 +82,6 @@ export const useLogFilterState: (props: { } return true; - /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, [filterQueryDraft]); const serializedFilterQuery = useMemo(() => (filterQuery ? filterQuery.serializedQuery : null), [ diff --git a/x-pack/plugins/infra/public/containers/logs/log_source/log_source.ts b/x-pack/plugins/infra/public/containers/logs/log_source/log_source.ts index 75c328b82939..f430e6b5e4d9 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_source/log_source.ts +++ b/x-pack/plugins/infra/public/containers/logs/log_source/log_source.ts @@ -91,7 +91,6 @@ export const useLogSource = ({ sourceId, fetch }: { sourceId: string; fetch: Htt fields: sourceStatus?.logIndexFields ?? [], title: sourceConfiguration?.configuration.name ?? 'unknown', }), - /* eslint-disable-next-line react-hooks/exhaustive-deps */ [sourceConfiguration, sourceStatus] ); diff --git a/x-pack/plugins/infra/public/containers/source/use_source_via_http.ts b/x-pack/plugins/infra/public/containers/source/use_source_via_http.ts index 54d565d9ee22..94e2537a67a2 100644 --- a/x-pack/plugins/infra/public/containers/source/use_source_via_http.ts +++ b/x-pack/plugins/infra/public/containers/source/use_source_via_http.ts @@ -76,7 +76,6 @@ export const useSourceViaHttp = ({ title: pickIndexPattern(response?.source, indexType), }; }, - /* eslint-disable-next-line react-hooks/exhaustive-deps */ [response, type] ); diff --git a/x-pack/plugins/infra/public/hooks/use_bulk_get_saved_object.tsx b/x-pack/plugins/infra/public/hooks/use_bulk_get_saved_object.tsx index cfa9a711f774..2a70edc9b9a5 100644 --- a/x-pack/plugins/infra/public/hooks/use_bulk_get_saved_object.tsx +++ b/x-pack/plugins/infra/public/hooks/use_bulk_get_saved_object.tsx @@ -35,7 +35,6 @@ export const useBulkGetSavedObject = (type: string) => { }; fetchData(); }, - /* eslint-disable-next-line react-hooks/exhaustive-deps */ [type, kibana.services.savedObjects] ); diff --git a/x-pack/plugins/infra/public/hooks/use_create_saved_object.tsx b/x-pack/plugins/infra/public/hooks/use_create_saved_object.tsx index 0efb862ad2eb..8313d496a065 100644 --- a/x-pack/plugins/infra/public/hooks/use_create_saved_object.tsx +++ b/x-pack/plugins/infra/public/hooks/use_create_saved_object.tsx @@ -40,7 +40,6 @@ export const useCreateSavedObject = (type: string) => { }; save(); }, - /* eslint-disable-next-line react-hooks/exhaustive-deps */ [type, kibana.services.savedObjects] ); diff --git a/x-pack/plugins/infra/public/hooks/use_delete_saved_object.tsx b/x-pack/plugins/infra/public/hooks/use_delete_saved_object.tsx index e353a79b1907..3f2d15b3b86a 100644 --- a/x-pack/plugins/infra/public/hooks/use_delete_saved_object.tsx +++ b/x-pack/plugins/infra/public/hooks/use_delete_saved_object.tsx @@ -29,7 +29,6 @@ export const useDeleteSavedObject = (type: string) => { }; dobj(); }, - /* eslint-disable-next-line react-hooks/exhaustive-deps */ [type, kibana.services.savedObjects] ); diff --git a/x-pack/plugins/infra/public/hooks/use_find_saved_object.tsx b/x-pack/plugins/infra/public/hooks/use_find_saved_object.tsx index 8aead6adfd0a..7c179875442d 100644 --- a/x-pack/plugins/infra/public/hooks/use_find_saved_object.tsx +++ b/x-pack/plugins/infra/public/hooks/use_find_saved_object.tsx @@ -37,7 +37,6 @@ export const useFindSavedObject = }; fetchData(); }, - /* eslint-disable-next-line react-hooks/exhaustive-deps */ [type, kibana.services.savedObjects] ); diff --git a/x-pack/plugins/infra/public/hooks/use_update_saved_object.tsx b/x-pack/plugins/infra/public/hooks/use_update_saved_object.tsx index 4c1e9ef7a613..f5b51ee869fb 100644 --- a/x-pack/plugins/infra/public/hooks/use_update_saved_object.tsx +++ b/x-pack/plugins/infra/public/hooks/use_update_saved_object.tsx @@ -40,7 +40,6 @@ export const useUpdateSavedObject = (type: string) => { }; save(); }, - /* eslint-disable-next-line react-hooks/exhaustive-deps */ [type, kibana.services.savedObjects] ); diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_results_content.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_results_content.tsx index 740fc8b7bafc..98367335d9c2 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_results_content.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_results_content.tsx @@ -77,7 +77,6 @@ export const LogEntryCategoriesResultsContent: React.FunctionComponent { const availableFields = useMemo( () => sourceStatus?.logIndexFields.map((field) => field.name) ?? [], - /* eslint-disable-next-line react-hooks/exhaustive-deps */ [sourceStatus] ); diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/overlay.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/overlay.tsx index af712c061157..8b2140aa196b 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/overlay.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/overlay.tsx @@ -33,6 +33,7 @@ export const NodeContextPopover = ({ options, onClose, }: Props) => { + // eslint-disable-next-line react-hooks/exhaustive-deps const tabConfigs = [MetricsTab, LogsTab, ProcessesTab, PropertiesTab]; const tabs = useMemo(() => { diff --git a/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/sub_section.tsx b/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/sub_section.tsx index 88e7c0c08e44..4c7500361611 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/sub_section.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/sub_section.tsx @@ -23,7 +23,6 @@ export const SubSection: FunctionComponent = ({ isLiveStreaming, stopLiveStreaming, }) => { - /* eslint-disable-next-line react-hooks/exhaustive-deps */ const metric = useMemo(() => metrics?.find((m) => m.id === id), [id, metrics]); if (!children || !metric) { diff --git a/x-pack/plugins/lens/public/app_plugin/app.test.tsx b/x-pack/plugins/lens/public/app_plugin/app.test.tsx index 831dd58c373a..a211416472f4 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.test.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.test.tsx @@ -44,7 +44,7 @@ import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/p jest.mock('../editor_frame_service/editor_frame/expression_helpers'); jest.mock('src/core/public'); jest.mock('../../../../../src/plugins/saved_objects/public', () => { - // eslint-disable-next-line no-shadow + // eslint-disable-next-line @typescript-eslint/no-shadow const { SavedObjectSaveModal, SavedObjectSaveModalOrigin } = jest.requireActual( '../../../../../src/plugins/saved_objects/public' ); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx index 97165a851307..913b39662251 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx @@ -273,6 +273,7 @@ export function SuggestionPanel({ return (props: ReactExpressionRendererProps) => ( ); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [plugins.data.query.timefilter.timefilter, context]); const [lastSelectedSuggestion, setLastSelectedSuggestion] = useState(-1); diff --git a/x-pack/plugins/observability/public/application/index.tsx b/x-pack/plugins/observability/public/application/index.tsx index 57a7bba8502d..585a45cf5279 100644 --- a/x-pack/plugins/observability/public/application/index.tsx +++ b/x-pack/plugins/observability/public/application/index.tsx @@ -39,6 +39,7 @@ function App() { const Wrapper = () => { const { core } = usePluginContext(); + // eslint-disable-next-line react-hooks/exhaustive-deps const breadcrumb = [observabilityLabelBreadcrumb, ...route.breadcrumb]; useEffect(() => { core.chrome.setBreadcrumbs(breadcrumb); diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/matrix_histogram/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/matrix_histogram/index.ts index 0217c48668fb..84a5d868c34a 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/matrix_histogram/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/matrix_histogram/index.ts @@ -60,7 +60,7 @@ export interface MatrixHistogramSchema { buildDsl: (options: MatrixHistogramRequestOptions) => {}; aggName: string; parseKey: string; - parser?: (data: MatrixHistogramParseData, keyBucket: string) => MatrixHistogramData[]; + parser?: (data: MatrixHistogramParseData, keyBucket: string) => MatrixHistogramData[]; } export type MatrixHistogramParseData = T extends MatrixHistogramType.alerts diff --git a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.tsx b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.tsx index 8c68551ddd98..f0eae407eedc 100644 --- a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.tsx +++ b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.tsx @@ -99,7 +99,6 @@ const DraggableWrapperHoverContentComponent: React.FC = ({ onFilterAdded(); } } - // eslint-disable-next-line react-hooks/exhaustive-deps }, [closePopOver, field, value, filterManager, onFilterAdded]); const filterOutValue = useCallback(() => { @@ -117,7 +116,6 @@ const DraggableWrapperHoverContentComponent: React.FC = ({ onFilterAdded(); } } - // eslint-disable-next-line react-hooks/exhaustive-deps }, [closePopOver, field, value, filterManager, onFilterAdded]); const handleGoGetTimelineId = useCallback(() => { diff --git a/x-pack/plugins/security_solution/public/common/components/header_page/types.ts b/x-pack/plugins/security_solution/public/common/components/header_page/types.ts index 3c16af83585e..3c45886e3c70 100644 --- a/x-pack/plugins/security_solution/public/common/components/header_page/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/header_page/types.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - +import type React from 'react'; export type TitleProp = string | React.ReactNode; export interface DraggableArguments { diff --git a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/types.ts b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/types.ts index 828cadd90bb1..327c2fa64997 100644 --- a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/types.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - +import type React from 'react'; import { EuiTitleSize } from '@elastic/eui'; import { ScaleType, Position, TickFormatter } from '@elastic/charts'; import { ActionCreator } from 'redux'; diff --git a/x-pack/plugins/security_solution/public/common/components/toasters/utils.ts b/x-pack/plugins/security_solution/public/common/components/toasters/utils.ts index 47c5588a1283..78509669443a 100644 --- a/x-pack/plugins/security_solution/public/common/components/toasters/utils.ts +++ b/x-pack/plugins/security_solution/public/common/components/toasters/utils.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - +import type React from 'react'; import uuid from 'uuid'; import { isError } from 'lodash/fp'; diff --git a/x-pack/plugins/security_solution/public/common/store/types.ts b/x-pack/plugins/security_solution/public/common/store/types.ts index 6903567c752b..189aa05b91f4 100644 --- a/x-pack/plugins/security_solution/public/common/store/types.ts +++ b/x-pack/plugins/security_solution/public/common/store/types.ts @@ -158,7 +158,7 @@ export type CreateStructuredSelector = < >( selectorMap: SelectorMap ) => ( - state: SelectorMap[keyof SelectorMap] extends (state: infer State) => unknown ? State : never + state: SelectorMap[keyof SelectorMap] extends (state: infer S) => unknown ? S : never ) => { [Key in keyof SelectorMap]: ReturnType; }; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_histogram_panel/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_histogram_panel/index.tsx index c96ef570c7e0..8900aa118d1c 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_histogram_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_histogram_panel/index.tsx @@ -171,7 +171,6 @@ export const AlertsHistogramPanel = memo( value: bucket.key, })) : NO_LEGEND_DATA, - // eslint-disable-next-line react-hooks/exhaustive-deps [alertsData, selectedStackByOption.value, timelineId] ); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx index 0315d513ee26..fcef88b3f189 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx @@ -203,6 +203,7 @@ const AlertContextMenuComponent: React.FC = ({ setEventsLoading, ]); + // eslint-disable-next-line react-hooks/exhaustive-deps const openAlertActionComponent = ( = ({ setEventsLoading, ]); + // eslint-disable-next-line react-hooks/exhaustive-deps const closeAlertActionComponent = ( = ({ setEventsLoading, ]); + // eslint-disable-next-line react-hooks/exhaustive-deps const inProgressAlertActionComponent = ( = ({ setOpenAddExceptionModal('endpoint'); }, [closePopover]); + // eslint-disable-next-line react-hooks/exhaustive-deps const addEndpointExceptionComponent = ( = ({ return !isMlRule(ruleType) && !isThresholdRule(ruleType); }, [ecsRowData]); + // eslint-disable-next-line react-hooks/exhaustive-deps const addExceptionComponent = ( { let isSubscribed = true; const abortCtrl = new AbortController(); @@ -96,8 +97,7 @@ export const useRules = ({ filterOptions.filter, filterOptions.sortField, filterOptions.sortOrder, - // eslint-disable-next-line react-hooks/exhaustive-deps - filterOptions.tags?.sort().join(), + filterTags, filterOptions.showCustomRules, filterOptions.showElasticRules, refetchPrePackagedRulesStatus, diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/reducer.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/reducer.ts index d603e5791f5c..89fa34856a3f 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/reducer.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/reducer.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - +import type React from 'react'; import { EuiBasicTable } from '@elastic/eui'; import { FilterOptions, diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx index 54aae5c41bd5..d7cc38950746 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx @@ -281,7 +281,6 @@ export const RuleDetailsPageComponent: FC = ({ date={rule?.last_failure_at} /> ) : null, - // eslint-disable-next-line react-hooks/exhaustive-deps [rule, ruleDetailTab] ); diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx index 25b012ed6862..a37f256e359b 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx @@ -492,6 +492,7 @@ export const EndpointList = () => { ], }, ]; + // eslint-disable-next-line react-hooks/exhaustive-deps }, [formatUrl, queryParams, search, agentPolicies, services?.application?.getUrlForApp]); const renderTableOrEmptyState = useMemo(() => { diff --git a/x-pack/plugins/security_solution/public/overview/components/types.ts b/x-pack/plugins/security_solution/public/overview/components/types.ts index e260f2843692..6aabf78788df 100644 --- a/x-pack/plugins/security_solution/public/overview/components/types.ts +++ b/x-pack/plugins/security_solution/public/overview/components/types.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - +import type React from 'react'; export type OverviewStatId = | 'auditbeatAuditd' | 'auditbeatFIM' diff --git a/x-pack/plugins/security_solution/public/resolver/store/data/selectors.ts b/x-pack/plugins/security_solution/public/resolver/store/data/selectors.ts index 505e6cfc3ee7..a79ffda0bcce 100644 --- a/x-pack/plugins/security_solution/public/resolver/store/data/selectors.ts +++ b/x-pack/plugins/security_solution/public/resolver/store/data/selectors.ts @@ -103,9 +103,8 @@ export const terminatedProcesses = createSelector(resolverTreeResponse, function * A function that given an entity id returns a boolean indicating if the id is in the set of terminated processes. */ export const isProcessTerminated = createSelector(terminatedProcesses, function ( - /* eslint-disable no-shadow */ + // eslint-disable-next-line @typescript-eslint/no-shadow terminatedProcesses - /* eslint-enable no-shadow */ ) { return (entityID: string) => { return terminatedProcesses.has(entityID); @@ -137,9 +136,8 @@ export const graphableProcesses = createSelector(resolverTreeResponse, function * The 'indexed process tree' contains the tree data, indexed in helpful ways. Used for O(1) access to stuff during graph layout. */ export const tree = createSelector(graphableProcesses, function indexedTree( - /* eslint-disable no-shadow */ + // eslint-disable-next-line @typescript-eslint/no-shadow graphableProcesses - /* eslint-enable no-shadow */ ) { return indexedProcessTreeModel.factory(graphableProcesses); }); @@ -248,9 +246,8 @@ export const relatedEventsByCategory: ( ) => (node: string, eventCategory: string) => SafeResolverEvent[] = createSelector( relatedEventsByEntityId, function ( - /* eslint-disable no-shadow */ + // eslint-disable-next-line @typescript-eslint/no-shadow relatedEventsByEntityId - /* eslint-enable no-shadow */ ) { // A map of nodeID -> event category -> SafeResolverEvent[] const nodeMap: Map> = new Map(); @@ -351,10 +348,9 @@ export const layout: (state: DataState) => IsometricTaxiLayout = createSelector( tree, originID, function processNodePositionsAndEdgeLineSegments( - /* eslint-disable no-shadow */ indexedProcessTree, + // eslint-disable-next-line @typescript-eslint/no-shadow originID - /* eslint-enable no-shadow */ ) { // use the isometric taxi layout as a base const taxiLayout = isometricTaxiLayoutModel.isometricTaxiLayoutFactory(indexedProcessTree); @@ -650,7 +646,7 @@ export const relatedEventCountOfTypeForNode: ( export const panelViewAndParameters = createSelector( (state: DataState) => state.locationSearch, resolverComponentInstanceID, - /* eslint-disable-next-line no-shadow */ + // eslint-disable-next-line @typescript-eslint/no-shadow (locationSearch, resolverComponentInstanceID) => { return panelViewAndParametersFromLocationSearchAndResolverComponentInstanceID({ locationSearch, @@ -670,7 +666,7 @@ export const nodeEventsInCategory = (state: DataState) => { export const lastRelatedEventResponseContainsCursor = createSelector( (state: DataState) => state.nodeEventsInCategory, panelViewAndParameters, - /* eslint-disable-next-line no-shadow */ + // eslint-disable-next-line @typescript-eslint/no-shadow function (nodeEventsInCategory, panelViewAndParameters) { if ( nodeEventsInCategory !== undefined && @@ -689,7 +685,7 @@ export const lastRelatedEventResponseContainsCursor = createSelector( export const hadErrorLoadingNodeEventsInCategory = createSelector( (state: DataState) => state.nodeEventsInCategory, panelViewAndParameters, - /* eslint-disable-next-line no-shadow */ + // eslint-disable-next-line @typescript-eslint/no-shadow function (nodeEventsInCategory, panelViewAndParameters) { if ( nodeEventsInCategory !== undefined && @@ -708,7 +704,7 @@ export const hadErrorLoadingNodeEventsInCategory = createSelector( export const isLoadingNodeEventsInCategory = createSelector( (state: DataState) => state.nodeEventsInCategory, panelViewAndParameters, - /* eslint-disable-next-line no-shadow */ + // eslint-disable-next-line @typescript-eslint/no-shadow function (nodeEventsInCategory, panelViewAndParameters) { const { panelView } = panelViewAndParameters; return panelView === 'nodeEventsInCategory' && nodeEventsInCategory === undefined; @@ -718,7 +714,7 @@ export const isLoadingNodeEventsInCategory = createSelector( export const isLoadingMoreNodeEventsInCategory = createSelector( (state: DataState) => state.nodeEventsInCategory, panelViewAndParameters, - /* eslint-disable-next-line no-shadow */ + // eslint-disable-next-line @typescript-eslint/no-shadow function (nodeEventsInCategory, panelViewAndParameters) { if ( nodeEventsInCategory !== undefined && diff --git a/x-pack/plugins/security_solution/public/resolver/store/selectors.ts b/x-pack/plugins/security_solution/public/resolver/store/selectors.ts index e805c16ed9c2..9a2ab53458a9 100644 --- a/x-pack/plugins/security_solution/public/resolver/store/selectors.ts +++ b/x-pack/plugins/security_solution/public/resolver/store/selectors.ts @@ -257,10 +257,10 @@ export const relatedEventTotalForProcess = composeSelectors( * animated. So in order to get the currently visible entities, we need to pass in time. */ export const visibleNodesAndEdgeLines = createSelector(nodesAndEdgelines, boundingBox, function ( - /* eslint-disable no-shadow */ + /* eslint-disable @typescript-eslint/no-shadow */ nodesAndEdgelines, boundingBox - /* eslint-enable no-shadow */ + /* eslint-enable @typescript-eslint/no-shadow */ ) { // `boundingBox` and `nodesAndEdgelines` are each memoized. return (time: number) => nodesAndEdgelines(boundingBox(time)); diff --git a/x-pack/plugins/security_solution/public/resolver/store/ui/selectors.ts b/x-pack/plugins/security_solution/public/resolver/store/ui/selectors.ts index 6f185db4bd8b..c60f92e4ba11 100644 --- a/x-pack/plugins/security_solution/public/resolver/store/ui/selectors.ts +++ b/x-pack/plugins/security_solution/public/resolver/store/ui/selectors.ts @@ -16,7 +16,7 @@ import { parameterName } from '../parameter_name'; */ export const ariaActiveDescendant = createSelector( (uiState: ResolverUIState) => uiState, - /* eslint-disable no-shadow */ + // eslint-disable-next-line @typescript-eslint/no-shadow ({ ariaActiveDescendant }) => { return ariaActiveDescendant; } @@ -27,7 +27,7 @@ export const ariaActiveDescendant = createSelector( */ export const selectedNode = createSelector( (uiState: ResolverUIState) => uiState, - /* eslint-disable no-shadow */ + // eslint-disable-next-line @typescript-eslint/no-shadow ({ selectedNode }: ResolverUIState) => { return selectedNode; } @@ -83,6 +83,7 @@ export const relatedEventsRelativeHrefs: ( ) => ( categories: Record | undefined, nodeID: string + // eslint-disable-next-line @typescript-eslint/no-shadow ) => Map = createSelector(relativeHref, (relativeHref) => { return (categories: Record | undefined, nodeID: string) => { const hrefsByCategory = new Map(); diff --git a/x-pack/plugins/security_solution/public/resolver/types.ts b/x-pack/plugins/security_solution/public/resolver/types.ts index 7129e3a47120..6cb25861a7b5 100644 --- a/x-pack/plugins/security_solution/public/resolver/types.ts +++ b/x-pack/plugins/security_solution/public/resolver/types.ts @@ -5,7 +5,7 @@ */ /* eslint-disable no-duplicate-imports */ - +import type React from 'react'; import { Store } from 'redux'; import { Middleware, Dispatch } from 'redux'; import { BBox } from 'rbush'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/types.ts b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/types.ts index 769a0a1658a4..4e7e99a5d3e4 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/types.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/types.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SetStateAction, Dispatch } from 'react'; +import type React from 'react'; import { AllTimelinesVariables } from '../../containers/all'; import { TimelineModel } from '../../store/timeline/model'; import { NoteResult } from '../../../graphql/types'; @@ -93,7 +93,9 @@ export type OnOpenTimeline = ({ }) => void; export type OnOpenDeleteTimelineModal = (selectedItem: OpenTimelineResult) => void; -export type SetActionTimeline = Dispatch>; +export type SetActionTimeline = React.Dispatch< + React.SetStateAction +>; export type EnableExportTimelineDownloader = (selectedItem: OpenTimelineResult) => void; /** Invoked when the user presses enters to submit the text in the search input */ export type OnQueryChange = (query: EuiSearchBarQuery) => void; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.tsx index d4d77d6fd40a..3ea7b8d471a4 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.tsx @@ -31,7 +31,7 @@ export const eventHasNotes = (noteIds: string[]): boolean => !isEmpty(noteIds); export const getPinTooltip = ({ isPinned, - // eslint-disable-next-line no-shadow + // eslint-disable-next-line @typescript-eslint/no-shadow eventHasNotes, timelineType, }: { diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/column_renderer.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/column_renderer.ts index c462841f7ea3..7efae14d58a9 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/column_renderer.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/column_renderer.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - +import type React from 'react'; import { TimelineNonEcsData } from '../../../../../../common/search_strategy/timeline'; import { ColumnHeaderOptions } from '../../../../../timelines/store/timeline/model'; diff --git a/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/query_builder/expressions/boundary_index_expression.tsx b/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/query_builder/expressions/boundary_index_expression.tsx index 55dfc82bdbdb..6433845370ff 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/query_builder/expressions/boundary_index_expression.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/query_builder/expressions/boundary_index_expression.tsx @@ -36,10 +36,12 @@ export const BoundaryIndexExpression: FunctionComponent = ({ setBoundaryGeoField, setBoundaryNameField, }) => { + // eslint-disable-next-line react-hooks/exhaustive-deps const BOUNDARY_NAME_ENTITY_TYPES = ['string', 'number', 'ip']; const { dataUi, dataIndexPatterns, http } = alertsContext; const IndexPatternSelect = (dataUi && dataUi.IndexPatternSelect) || null; const { boundaryGeoField } = alertParams; + // eslint-disable-next-line react-hooks/exhaustive-deps const nothingSelected: IFieldType = { name: '', type: 'string', diff --git a/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/query_builder/expressions/entity_by_expression.tsx b/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/query_builder/expressions/entity_by_expression.tsx index f519ad882802..0cff207e674e 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/query_builder/expressions/entity_by_expression.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/query_builder/expressions/entity_by_expression.tsx @@ -28,6 +28,7 @@ export const EntityByExpression: FunctionComponent = ({ indexFields, isInvalid, }) => { + // eslint-disable-next-line react-hooks/exhaustive-deps const ENTITY_TYPES = ['string', 'number', 'ip']; const usePrevious = (value: T): T | undefined => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.tsx index de27256bf566..a2a2d1234dbc 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.tsx @@ -57,6 +57,7 @@ export const ConnectorAddModal = ({ consumer, }: ConnectorAddModalProps) => { let hasErrors = false; + // eslint-disable-next-line react-hooks/exhaustive-deps const initialConnector = { actionTypeId: actionType.id, config: {}, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.tsx index 89deb4b26f01..741cbadb0707 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.tsx @@ -36,6 +36,7 @@ export const AlertAdd = ({ alertTypeId, initialValues, }: AlertAddProps) => { + // eslint-disable-next-line react-hooks/exhaustive-deps const initialAlert = ({ params: {}, consumer, diff --git a/x-pack/test/saved_object_api_integration/common/lib/saved_object_test_utils.ts b/x-pack/test/saved_object_api_integration/common/lib/saved_object_test_utils.ts index 511d183145a3..c9d84d9819c6 100644 --- a/x-pack/test/saved_object_api_integration/common/lib/saved_object_test_utils.ts +++ b/x-pack/test/saved_object_api_integration/common/lib/saved_object_test_utils.ts @@ -332,7 +332,7 @@ export const getTestScenarios = (modifiers?: T[]) => { }, ]; if (modifiers) { - const addModifier = (list: T[]) => + const addModifier = (list: U[]) => list.map((x) => modifiers.map((modifier) => ({ ...x, modifier }))).flat(); spaces = addModifier(spaces); security = addModifier(security); diff --git a/yarn.lock b/yarn.lock index 3bfa72cc50ae..337d7600bdb3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4587,11 +4587,6 @@ "@types/cheerio" "*" "@types/react" "*" -"@types/eslint-visitor-keys@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" - integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag== - "@types/eslint@^6.1.3": version "6.1.3" resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-6.1.3.tgz#ec2a66e445a48efaa234020eb3b6e8f06afc9c61" @@ -5965,26 +5960,28 @@ resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.0.tgz#8b63ab7f1aa5321248aad5ac890a485656dcea4d" integrity sha512-te5lMAWii1uEJ4FwLjzdlbw3+n0FZNOvFXHxQDKeT0dilh7HOzdMzV2TrJVUzq8ep7J4Na8OUYPRLSQkJHAlrg== -"@typescript-eslint/eslint-plugin@^3.10.0": - version "3.10.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.10.0.tgz#40fd53e81639c0d1a515b44e5fdf4c03dfd3cd39" - integrity sha512-Bbeg9JAnSzZ85Y0gpInZscSpifA6SbEgRryaKdP5ZlUjhTKsvZS4GUIE6xAZCjhNTrf4zXXsySo83ZdHL7it0w== +"@typescript-eslint/eslint-plugin@^4.8.1": + version "4.8.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.8.1.tgz#b362abe0ee478a6c6d06c14552a6497f0b480769" + integrity sha512-d7LeQ7dbUrIv5YVFNzGgaW3IQKMmnmKFneRWagRlGYOSfLJVaRbj/FrBNOBC1a3tVO+TgNq1GbHvRtg1kwL0FQ== dependencies: - "@typescript-eslint/experimental-utils" "3.10.0" + "@typescript-eslint/experimental-utils" "4.8.1" + "@typescript-eslint/scope-manager" "4.8.1" debug "^4.1.1" functional-red-black-tree "^1.0.1" regexpp "^3.0.0" semver "^7.3.2" tsutils "^3.17.1" -"@typescript-eslint/experimental-utils@3.10.0": - version "3.10.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-3.10.0.tgz#f97a669a84a78319ab324cd51169d0c52853a360" - integrity sha512-e5ZLSTuXgqC/Gq3QzK2orjlhTZVXzwxDujQmTBOM1NIVBZgW3wiIZjaXuVutk9R4UltFlwC9UD2+bdxsA7yyNg== +"@typescript-eslint/experimental-utils@4.8.1": + version "4.8.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.8.1.tgz#27275c20fa4336df99ebcf6195f7d7aa7aa9f22d" + integrity sha512-WigyLn144R3+lGATXW4nNcDJ9JlTkG8YdBWHkDlN0lC3gUGtDi7Pe3h5GPvFKMcRz8KbZpm9FJV9NTW8CpRHpg== dependencies: "@types/json-schema" "^7.0.3" - "@typescript-eslint/types" "3.10.0" - "@typescript-eslint/typescript-estree" "3.10.0" + "@typescript-eslint/scope-manager" "4.8.1" + "@typescript-eslint/types" "4.8.1" + "@typescript-eslint/typescript-estree" "4.8.1" eslint-scope "^5.0.0" eslint-utils "^2.0.0" @@ -6000,16 +5997,15 @@ eslint-scope "^5.0.0" eslint-utils "^2.0.0" -"@typescript-eslint/parser@^3.10.0": - version "3.10.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-3.10.0.tgz#820322d990a82265a78f4c1fc9aae03ce95b76ac" - integrity sha512-iJyf3f2HVwscvJR7ySGMXw2DJgIAPKEz8TeU17XVKzgJRV4/VgCeDFcqLzueRe7iFI2gv+Tln4AV88ZOnsCNXg== +"@typescript-eslint/parser@^4.8.1": + version "4.8.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.8.1.tgz#4fe2fbdbb67485bafc4320b3ae91e34efe1219d1" + integrity sha512-QND8XSVetATHK9y2Ltc/XBl5Ro7Y62YuZKnPEwnNPB8E379fDsvzJ1dMJ46fg/VOmk0hXhatc+GXs5MaXuL5Uw== dependencies: - "@types/eslint-visitor-keys" "^1.0.0" - "@typescript-eslint/experimental-utils" "3.10.0" - "@typescript-eslint/types" "3.10.0" - "@typescript-eslint/typescript-estree" "3.10.0" - eslint-visitor-keys "^1.1.0" + "@typescript-eslint/scope-manager" "4.8.1" + "@typescript-eslint/types" "4.8.1" + "@typescript-eslint/typescript-estree" "4.8.1" + debug "^4.1.1" "@typescript-eslint/scope-manager@4.3.0": version "4.3.0" @@ -6019,29 +6015,23 @@ "@typescript-eslint/types" "4.3.0" "@typescript-eslint/visitor-keys" "4.3.0" -"@typescript-eslint/types@3.10.0": - version "3.10.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-3.10.0.tgz#b81906674eca94a884345ba0bc1aaf6cd4da912a" - integrity sha512-ktUWSa75heQNwH85GRM7qP/UUrXqx9d6yIdw0iLO9/uE1LILW+i+3+B64dUodUS2WFWLzKTlwfi9giqrODibWg== +"@typescript-eslint/scope-manager@4.8.1": + version "4.8.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.8.1.tgz#e343c475f8f1d15801b546cb17d7f309b768fdce" + integrity sha512-r0iUOc41KFFbZdPAdCS4K1mXivnSZqXS5D9oW+iykQsRlTbQRfuFRSW20xKDdYiaCoH+SkSLeIF484g3kWzwOQ== + dependencies: + "@typescript-eslint/types" "4.8.1" + "@typescript-eslint/visitor-keys" "4.8.1" "@typescript-eslint/types@4.3.0": version "4.3.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.3.0.tgz#1f0b2d5e140543e2614f06d48fb3ae95193c6ddf" integrity sha512-Cx9TpRvlRjOppGsU6Y6KcJnUDOelja2NNCX6AZwtVHRzaJkdytJWMuYiqi8mS35MRNA3cJSwDzXePfmhU6TANw== -"@typescript-eslint/typescript-estree@3.10.0": - version "3.10.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-3.10.0.tgz#65df13579a5e53c12afb4f1c5309589e3855a5de" - integrity sha512-yjuY6rmVHRhcUKgXaSPNVloRueGWpFNhxR5EQLzxXfiFSl1U/+FBqHhbaGwtPPEgCSt61QNhZgiFjWT27bgAyw== - dependencies: - "@typescript-eslint/types" "3.10.0" - "@typescript-eslint/visitor-keys" "3.10.0" - debug "^4.1.1" - glob "^7.1.6" - is-glob "^4.0.1" - lodash "^4.17.15" - semver "^7.3.2" - tsutils "^3.17.1" +"@typescript-eslint/types@4.8.1": + version "4.8.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.8.1.tgz#23829c73c5fc6f4fcd5346a7780b274f72fee222" + integrity sha512-ave2a18x2Y25q5K05K/U3JQIe2Av4+TNi/2YuzyaXLAsDx6UZkz1boZ7nR/N6Wwae2PpudTZmHFXqu7faXfHmA== "@typescript-eslint/typescript-estree@4.3.0": version "4.3.0" @@ -6057,6 +6047,20 @@ semver "^7.3.2" tsutils "^3.17.1" +"@typescript-eslint/typescript-estree@4.8.1": + version "4.8.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.8.1.tgz#7307e3f2c9e95df7daa8dc0a34b8c43b7ec0dd32" + integrity sha512-bJ6Fn/6tW2g7WIkCWh3QRlaSU7CdUUK52shx36/J7T5oTQzANvi6raoTsbwGM11+7eBbeem8hCCKbyvAc0X3sQ== + dependencies: + "@typescript-eslint/types" "4.8.1" + "@typescript-eslint/visitor-keys" "4.8.1" + debug "^4.1.1" + globby "^11.0.1" + is-glob "^4.0.1" + lodash "^4.17.15" + semver "^7.3.2" + tsutils "^3.17.1" + "@typescript-eslint/typescript-estree@^1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-1.9.0.tgz#5d6d49be936e96fb0f859673480f89b070a5dd9b" @@ -6065,13 +6069,6 @@ lodash.unescape "4.0.1" semver "5.5.0" -"@typescript-eslint/visitor-keys@3.10.0": - version "3.10.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-3.10.0.tgz#6c0cac867e705a42e2c71b359bf6a10a88a28985" - integrity sha512-g4qftk8lWb/rHZe9uEp8oZSvsJhUvR2cfp7F7qE6DyUD2SsovEs8JDQTRP1xHzsD+pERsEpYNqkDgQXW6+ob5A== - dependencies: - eslint-visitor-keys "^1.1.0" - "@typescript-eslint/visitor-keys@4.3.0": version "4.3.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.3.0.tgz#0e5ab0a09552903edeae205982e8521e17635ae0" @@ -6080,6 +6077,14 @@ "@typescript-eslint/types" "4.3.0" eslint-visitor-keys "^2.0.0" +"@typescript-eslint/visitor-keys@4.8.1": + version "4.8.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.8.1.tgz#794f68ee292d1b2e3aa9690ebedfcb3a8c90e3c3" + integrity sha512-3nrwXFdEYALQh/zW8rFwP4QltqsanCDz4CwWMPiIZmwlk9GlvBeueEIbq05SEq4ganqM0g9nh02xXgv5XI3PeQ== + dependencies: + "@typescript-eslint/types" "4.8.1" + eslint-visitor-keys "^2.0.0" + "@webassemblyjs/ast@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964" @@ -12800,10 +12805,10 @@ eslint-plugin-prettier@^3.1.4: dependencies: prettier-linter-helpers "^1.0.0" -eslint-plugin-react-hooks@^4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.0.4.tgz#aed33b4254a41b045818cacb047b81e6df27fa58" - integrity sha512-equAdEIsUETLFNCmmCkiCGq6rkSK5MoJhXFPFYeUebcjKgBmWWcgVOqZyQC8Bv1BwVCnTq9tBxgJFgAJTWoJtA== +eslint-plugin-react-hooks@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.2.0.tgz#8c229c268d468956334c943bb45fc860280f5556" + integrity sha512-623WEiZJqxR7VdxFCKLI6d6LLpwJkGPYKODnkH3D7WpOG5KM8yWueBd8TLsNAetEJNF5iJmolaAKO3F8yzyVBQ== eslint-plugin-react-perf@^3.2.3: version "3.2.3" From 37636f3e35b40a2be5e25f8586f78e81b7202283 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?= Date: Wed, 18 Nov 2020 19:16:38 +0100 Subject: [PATCH 07/49] [Telemetry] Move Monitoring collection strategy to a collector (#82638) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../server/plugin.ts | 2 +- x-pack/plugins/monitoring/kibana.json | 1 - x-pack/plugins/monitoring/server/plugin.ts | 35 ++---- .../get_all_stats.test.ts | 41 +------ .../telemetry_collection/get_all_stats.ts | 20 ++-- .../get_cluster_uuids.test.ts | 26 +---- .../telemetry_collection/get_cluster_uuids.ts | 31 +++-- .../telemetry_collection/get_licenses.test.ts | 18 +-- .../telemetry_collection/get_licenses.ts | 23 ++-- .../server/telemetry_collection/index.ts | 2 +- .../register_monitoring_collection.ts | 41 ------- ...egister_monitoring_telemetry_collection.ts | 59 ++++++++++ x-pack/plugins/monitoring/server/types.ts | 2 - .../get_stats_with_xpack.test.ts.snap | 86 ++++++++++++++ .../get_stats_with_xpack.test.ts | 108 +++++++++++------- .../get_stats_with_xpack.ts | 27 +++-- .../apis/telemetry/telemetry.js | 104 +++++++++++++---- 17 files changed, 356 insertions(+), 270 deletions(-) delete mode 100644 x-pack/plugins/monitoring/server/telemetry_collection/register_monitoring_collection.ts create mode 100644 x-pack/plugins/monitoring/server/telemetry_collection/register_monitoring_telemetry_collection.ts diff --git a/src/plugins/telemetry_collection_manager/server/plugin.ts b/src/plugins/telemetry_collection_manager/server/plugin.ts index 2cd06f13a885..c9e2f22fa19a 100644 --- a/src/plugins/telemetry_collection_manager/server/plugin.ts +++ b/src/plugins/telemetry_collection_manager/server/plugin.ts @@ -289,9 +289,9 @@ export class TelemetryCollectionManagerPlugin return stats.map((stat) => { const license = licenses[stat.cluster_uuid]; return { + collectionSource: collection.title, ...(license ? { license } : {}), ...stat, - collectionSource: collection.title, }; }); } diff --git a/x-pack/plugins/monitoring/kibana.json b/x-pack/plugins/monitoring/kibana.json index a1e28985a352..a3d886b14cdf 100644 --- a/x-pack/plugins/monitoring/kibana.json +++ b/x-pack/plugins/monitoring/kibana.json @@ -13,7 +13,6 @@ ], "optionalPlugins": [ "infra", - "telemetryCollectionManager", "usageCollection", "home", "cloud", diff --git a/x-pack/plugins/monitoring/server/plugin.ts b/x-pack/plugins/monitoring/server/plugin.ts index 41b501d88af9..8a8e6a867c2e 100644 --- a/x-pack/plugins/monitoring/server/plugin.ts +++ b/x-pack/plugins/monitoring/server/plugin.ts @@ -20,8 +20,6 @@ import { CoreStart, CustomHttpResponseOptions, ResponseError, - IClusterClient, - SavedObjectsServiceStart, } from 'kibana/server'; import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/server'; import { @@ -41,7 +39,7 @@ import { initInfraSource } from './lib/logs/init_infra_source'; import { mbSafeQuery } from './lib/mb_safe_query'; import { instantiateClient } from './es_client/instantiate_client'; import { registerCollectors } from './kibana_monitoring/collectors'; -import { registerMonitoringCollection } from './telemetry_collection'; +import { registerMonitoringTelemetryCollection } from './telemetry_collection'; import { LicenseService } from './license_service'; import { AlertsFactory } from './alerts'; import { @@ -76,8 +74,6 @@ export class Plugin { private monitoringCore = {} as MonitoringCore; private legacyShimDependencies = {} as LegacyShimDependencies; private bulkUploader: IBulkUploader = {} as IBulkUploader; - private telemetryElasticsearchClient: IClusterClient | undefined; - private telemetrySavedObjectsService: SavedObjectsServiceStart | undefined; constructor(initializerContext: PluginInitializerContext) { this.initializerContext = initializerContext; @@ -145,19 +141,6 @@ export class Plugin { plugins.alerts?.registerType(alert.getAlertType()); } - // Initialize telemetry - if (plugins.telemetryCollectionManager) { - registerMonitoringCollection({ - telemetryCollectionManager: plugins.telemetryCollectionManager, - esCluster: this.cluster, - esClientGetter: () => this.telemetryElasticsearchClient, - soServiceGetter: () => this.telemetrySavedObjectsService, - customContext: { - maxBucketSize: config.ui.max_bucket_size, - }, - }); - } - // Register collector objects for stats to show up in the APIs if (plugins.usageCollection) { core.savedObjects.registerType({ @@ -174,6 +157,11 @@ export class Plugin { }); registerCollectors(plugins.usageCollection, config, cluster); + registerMonitoringTelemetryCollection( + plugins.usageCollection, + cluster, + config.ui.max_bucket_size + ); } // Always create the bulk uploader @@ -253,16 +241,7 @@ export class Plugin { }; } - start({ elasticsearch, savedObjects }: CoreStart) { - // TODO: For the telemetry plugin to work, we need to provide the new ES client. - // The new client should be inititalized with a similar config to `this.cluster` but, since we're not using - // the new client in Monitoring Telemetry collection yet, setting the local client allows progress for now. - // The usage collector `fetch` method has been refactored to accept a `collectorFetchContext` object, - // exposing both es clients and the saved objects client. - // We will update the client in a follow up PR. - this.telemetryElasticsearchClient = elasticsearch.client; - this.telemetrySavedObjectsService = savedObjects; - } + start() {} stop() { if (this.cluster) { diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_all_stats.test.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_all_stats.test.ts index a119686afe66..aa2033b64973 100644 --- a/x-pack/plugins/monitoring/server/telemetry_collection/get_all_stats.test.ts +++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_all_stats.test.ts @@ -9,13 +9,10 @@ import { getStackStats, getAllStats, handleAllStats } from './get_all_stats'; import { ESClusterStats } from './get_es_stats'; import { KibanaStats } from './get_kibana_stats'; import { ClustersHighLevelStats } from './get_high_level_stats'; -import { coreMock } from 'src/core/server/mocks'; describe('get_all_stats', () => { const timestamp = Date.now(); const callCluster = sinon.stub(); - const esClient = sinon.stub(); - const soClient = sinon.stub(); const esClusters = [ { cluster_uuid: 'a' }, @@ -172,24 +169,7 @@ describe('get_all_stats', () => { .onCall(4) .returns(Promise.resolve({})); // Beats state - expect( - await getAllStats( - [{ clusterUuid: 'a' }], - { - callCluster: callCluster as any, - esClient: esClient as any, - soClient: soClient as any, - usageCollection: {} as any, - kibanaRequest: undefined, - timestamp, - }, - { - logger: coreMock.createPluginInitializerContext().logger.get('test'), - version: 'version', - maxBucketSize: 1, - } - ) - ).toStrictEqual(allClusters); + expect(await getAllStats(['a'], callCluster, timestamp, 1)).toStrictEqual(allClusters); }); it('returns empty clusters', async () => { @@ -199,24 +179,7 @@ describe('get_all_stats', () => { callCluster.withArgs('search').returns(Promise.resolve(clusterUuidsResponse)); - expect( - await getAllStats( - [], - { - callCluster: callCluster as any, - esClient: esClient as any, - soClient: soClient as any, - usageCollection: {} as any, - kibanaRequest: undefined, - timestamp, - }, - { - logger: coreMock.createPluginInitializerContext().logger.get('test'), - version: 'version', - maxBucketSize: 1, - } - ) - ).toStrictEqual([]); + expect(await getAllStats([], callCluster, timestamp, 1)).toStrictEqual([]); }); }); diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_all_stats.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_all_stats.ts index b6b2023b2af1..1f194b75e200 100644 --- a/x-pack/plugins/monitoring/server/telemetry_collection/get_all_stats.ts +++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_all_stats.ts @@ -7,8 +7,8 @@ import { set } from '@elastic/safer-lodash-set'; import { get, merge } from 'lodash'; -import { StatsGetter } from 'src/plugins/telemetry_collection_manager/server'; import moment from 'moment'; +import { LegacyAPICaller } from 'kibana/server'; import { LOGSTASH_SYSTEM_ID, KIBANA_SYSTEM_ID, @@ -20,24 +20,20 @@ import { getKibanaStats, KibanaStats } from './get_kibana_stats'; import { getBeatsStats, BeatsStatsByClusterUuid } from './get_beats_stats'; import { getHighLevelStats, ClustersHighLevelStats } from './get_high_level_stats'; -export interface CustomContext { - maxBucketSize: number; -} /** * Get statistics for all products joined by Elasticsearch cluster. * Returns the array of clusters joined with the Kibana and Logstash instances. * */ -export const getAllStats: StatsGetter = async ( - clustersDetails, - { callCluster, timestamp }, - { maxBucketSize } -) => { +export async function getAllStats( + clusterUuids: string[], + callCluster: LegacyAPICaller, // TODO: To be changed to the new ES client when the plugin migrates + timestamp: number, + maxBucketSize: number +) { const start = moment(timestamp).subtract(USAGE_FETCH_INTERVAL, 'ms').toISOString(); const end = moment(timestamp).toISOString(); - const clusterUuids = clustersDetails.map((clusterDetails) => clusterDetails.clusterUuid); - const [esClusters, kibana, logstash, beats] = await Promise.all([ getElasticsearchStats(callCluster, clusterUuids, maxBucketSize), // cluster_stats, stack_stats.xpack, cluster_name/uuid, license, version getKibanaStats(callCluster, clusterUuids, start, end, maxBucketSize), // stack_stats.kibana @@ -46,7 +42,7 @@ export const getAllStats: StatsGetter = async ( ]); return handleAllStats(esClusters, { kibana, logstash, beats }); -}; +} /** * Combine the statistics from the stack to create "cluster" stats that associate all products together based on the cluster diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_cluster_uuids.test.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_cluster_uuids.test.ts index b296ff090aed..18a87296f786 100644 --- a/x-pack/plugins/monitoring/server/telemetry_collection/get_cluster_uuids.test.ts +++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_cluster_uuids.test.ts @@ -5,7 +5,6 @@ */ import sinon from 'sinon'; -import { elasticsearchServiceMock, savedObjectsRepositoryMock } from 'src/core/server/mocks'; import { getClusterUuids, fetchClusterUuids, @@ -13,10 +12,7 @@ import { } from './get_cluster_uuids'; describe('get_cluster_uuids', () => { - const kibanaRequest = undefined; const callCluster = sinon.stub(); - const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; - const soClient = savedObjectsRepositoryMock.create(); const response = { aggregations: { cluster_uuids: { @@ -24,36 +20,20 @@ describe('get_cluster_uuids', () => { }, }, }; - const expectedUuids = response.aggregations.cluster_uuids.buckets - .map((bucket) => bucket.key) - .map((expectedUuid) => ({ clusterUuid: expectedUuid })); + const expectedUuids = response.aggregations.cluster_uuids.buckets.map((bucket) => bucket.key); const timestamp = Date.now(); describe('getClusterUuids', () => { it('returns cluster UUIDs', async () => { callCluster.withArgs('search').returns(Promise.resolve(response)); - expect( - await getClusterUuids( - { callCluster, esClient, soClient, timestamp, kibanaRequest, usageCollection: {} as any }, - { - maxBucketSize: 1, - } as any - ) - ).toStrictEqual(expectedUuids); + expect(await getClusterUuids(callCluster, timestamp, 1)).toStrictEqual(expectedUuids); }); }); describe('fetchClusterUuids', () => { it('searches for clusters', async () => { callCluster.returns(Promise.resolve(response)); - expect( - await fetchClusterUuids( - { callCluster, esClient, soClient, timestamp, kibanaRequest, usageCollection: {} as any }, - { - maxBucketSize: 1, - } as any - ) - ).toStrictEqual(response); + expect(await fetchClusterUuids(callCluster, timestamp, 1)).toStrictEqual(response); }); }); diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_cluster_uuids.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_cluster_uuids.ts index 5f471851b662..32cda4ebdac9 100644 --- a/x-pack/plugins/monitoring/server/telemetry_collection/get_cluster_uuids.ts +++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_cluster_uuids.ts @@ -6,33 +6,31 @@ import { get } from 'lodash'; import moment from 'moment'; -import { - ClusterDetailsGetter, - StatsCollectionConfig, - ClusterDetails, -} from 'src/plugins/telemetry_collection_manager/server'; +import { LegacyAPICaller } from 'kibana/server'; import { createQuery } from './create_query'; import { INDEX_PATTERN_ELASTICSEARCH, CLUSTER_DETAILS_FETCH_INTERVAL, } from '../../common/constants'; -import { CustomContext } from './get_all_stats'; + /** * Get a list of Cluster UUIDs that exist within the specified timespan. */ -export const getClusterUuids: ClusterDetailsGetter = async ( - config, - { maxBucketSize } -) => { - const response = await fetchClusterUuids(config, maxBucketSize); +export async function getClusterUuids( + callCluster: LegacyAPICaller, // TODO: To be changed to the new ES client when the plugin migrates + timestamp: number, + maxBucketSize: number +) { + const response = await fetchClusterUuids(callCluster, timestamp, maxBucketSize); return handleClusterUuidsResponse(response); -}; +} /** * Fetch the aggregated Cluster UUIDs from the monitoring cluster. */ export async function fetchClusterUuids( - { callCluster, timestamp }: StatsCollectionConfig, + callCluster: LegacyAPICaller, + timestamp: number, maxBucketSize: number ) { const start = moment(timestamp).subtract(CLUSTER_DETAILS_FETCH_INTERVAL, 'ms').toISOString(); @@ -66,10 +64,7 @@ export async function fetchClusterUuids( * @param {Object} response The aggregation response * @return {Array} Strings; each representing a Cluster's UUID. */ -export function handleClusterUuidsResponse(response: any): ClusterDetails[] { +export function handleClusterUuidsResponse(response: any): string[] { const uuidBuckets: any[] = get(response, 'aggregations.cluster_uuids.buckets', []); - - return uuidBuckets.map((uuidBucket) => ({ - clusterUuid: uuidBucket.key as string, - })); + return uuidBuckets.map((uuidBucket) => uuidBucket.key); } diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_licenses.test.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_licenses.test.ts index 4812d9522d7a..8db563cebac0 100644 --- a/x-pack/plugins/monitoring/server/telemetry_collection/get_licenses.test.ts +++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_licenses.test.ts @@ -19,7 +19,7 @@ describe('get_licenses', () => { }, }; const expectedClusters = response.hits.hits.map((hit) => hit._source); - const clusterUuids = expectedClusters.map((cluster) => ({ clusterUuid: cluster.cluster_uuid })); + const clusterUuids = expectedClusters.map((cluster) => cluster.cluster_uuid); const expectedLicenses = { abc: { type: 'basic' }, xyz: { type: 'basic' }, @@ -30,13 +30,7 @@ describe('get_licenses', () => { it('returns clusters', async () => { callWith.withArgs('search').returns(Promise.resolve(response)); - expect( - await getLicenses( - clusterUuids, - { callCluster: callWith } as any, - { maxBucketSize: 1 } as any - ) - ).toStrictEqual(expectedLicenses); + expect(await getLicenses(clusterUuids, callWith, 1)).toStrictEqual(expectedLicenses); }); }); @@ -44,13 +38,7 @@ describe('get_licenses', () => { it('searches for clusters', async () => { callWith.returns(response); - expect( - await fetchLicenses( - callWith, - clusterUuids.map(({ clusterUuid }) => clusterUuid), - { maxBucketSize: 1 } as any - ) - ).toStrictEqual(response); + expect(await fetchLicenses(callWith, clusterUuids, 1)).toStrictEqual(response); }); }); diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_licenses.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_licenses.ts index a8b68929e84b..7b1b877c5127 100644 --- a/x-pack/plugins/monitoring/server/telemetry_collection/get_licenses.ts +++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_licenses.ts @@ -5,26 +5,21 @@ */ import { SearchResponse } from 'elasticsearch'; -import { - ESLicense, - LicenseGetter, - StatsCollectionConfig, -} from 'src/plugins/telemetry_collection_manager/server'; +import { ESLicense } from 'src/plugins/telemetry_collection_manager/server'; +import { LegacyAPICaller } from 'kibana/server'; import { INDEX_PATTERN_ELASTICSEARCH } from '../../common/constants'; -import { CustomContext } from './get_all_stats'; /** * Get statistics for all selected Elasticsearch clusters. */ -export const getLicenses: LicenseGetter = async ( - clustersDetails, - { callCluster }, - { maxBucketSize } -) => { - const clusterUuids = clustersDetails.map(({ clusterUuid }) => clusterUuid); +export async function getLicenses( + clusterUuids: string[], + callCluster: LegacyAPICaller, // TODO: To be changed to the new ES client when the plugin migrates + maxBucketSize: number +): Promise<{ [clusterUuid: string]: ESLicense | undefined }> { const response = await fetchLicenses(callCluster, clusterUuids, maxBucketSize); return handleLicenses(response); -}; +} /** * Fetch the Elasticsearch stats. @@ -36,7 +31,7 @@ export const getLicenses: LicenseGetter = async ( * Returns the response for the aggregations to fetch details for the product. */ export function fetchLicenses( - callCluster: StatsCollectionConfig['callCluster'], + callCluster: LegacyAPICaller, clusterUuids: string[], maxBucketSize: number ) { diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/index.ts b/x-pack/plugins/monitoring/server/telemetry_collection/index.ts index 764e080e390c..8627c741c974 100644 --- a/x-pack/plugins/monitoring/server/telemetry_collection/index.ts +++ b/x-pack/plugins/monitoring/server/telemetry_collection/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { registerMonitoringCollection } from './register_monitoring_collection'; +export { registerMonitoringTelemetryCollection } from './register_monitoring_telemetry_collection'; diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/register_monitoring_collection.ts b/x-pack/plugins/monitoring/server/telemetry_collection/register_monitoring_collection.ts deleted file mode 100644 index 109fefd2eb8d..000000000000 --- a/x-pack/plugins/monitoring/server/telemetry_collection/register_monitoring_collection.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { - ILegacyCustomClusterClient, - IClusterClient, - SavedObjectsServiceStart, -} from 'kibana/server'; -import { TelemetryCollectionManagerPluginSetup } from 'src/plugins/telemetry_collection_manager/server'; -import { getAllStats, CustomContext } from './get_all_stats'; -import { getClusterUuids } from './get_cluster_uuids'; -import { getLicenses } from './get_licenses'; - -export function registerMonitoringCollection({ - telemetryCollectionManager, - esCluster, - esClientGetter, - soServiceGetter, - customContext, -}: { - telemetryCollectionManager: TelemetryCollectionManagerPluginSetup; - esCluster: ILegacyCustomClusterClient; - esClientGetter: () => IClusterClient | undefined; - soServiceGetter: () => SavedObjectsServiceStart | undefined; - customContext: CustomContext; -}) { - telemetryCollectionManager.setCollection({ - esCluster, - esClientGetter, - soServiceGetter, - title: 'monitoring', - priority: 2, - statsGetter: getAllStats, - clusterDetailsGetter: getClusterUuids, - licenseGetter: getLicenses, - customContext, - }); -} diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/register_monitoring_telemetry_collection.ts b/x-pack/plugins/monitoring/server/telemetry_collection/register_monitoring_telemetry_collection.ts new file mode 100644 index 000000000000..91d6c2374acb --- /dev/null +++ b/x-pack/plugins/monitoring/server/telemetry_collection/register_monitoring_telemetry_collection.ts @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ILegacyClusterClient } from 'kibana/server'; +import { UsageStatsPayload } from 'src/plugins/telemetry_collection_manager/server'; +import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; +import { getAllStats } from './get_all_stats'; +import { getClusterUuids } from './get_cluster_uuids'; +import { getLicenses } from './get_licenses'; + +// TODO: To be removed in https://github.com/elastic/kibana/pull/83546 +interface MonitoringCollectorOptions { + ignoreForInternalUploader: boolean; // Allow the additional property required by bulk_uploader to be filtered out +} + +export function registerMonitoringTelemetryCollection( + usageCollection: UsageCollectionSetup, + legacyEsClient: ILegacyClusterClient, + maxBucketSize: number +) { + const monitoringStatsCollector = usageCollection.makeStatsCollector< + UsageStatsPayload[], + unknown, + true, + MonitoringCollectorOptions + >({ + type: 'monitoringTelemetry', + isReady: () => true, + ignoreForInternalUploader: true, // Used only by monitoring's bulk_uploader to filter out unwanted collectors + extendFetchContext: { kibanaRequest: true }, + fetch: async ({ kibanaRequest }) => { + const timestamp = Date.now(); // Collect the telemetry from the monitoring indices for this moment. + // NOTE: Usually, the monitoring indices index stats for each product every 10s (by default). + // However, some data may be delayed up-to 24h because monitoring only collects extended Kibana stats in that interval + // to avoid overloading of the system when retrieving data from the collectors (that delay is dealt with in the Kibana Stats getter inside the `getAllStats` method). + // By 8.x, we expect to stop collecting the Kibana extended stats and keep only the monitoring-related metrics. + const callCluster = kibanaRequest + ? legacyEsClient.asScoped(kibanaRequest).callAsCurrentUser + : legacyEsClient.callAsInternalUser; + const clusterDetails = await getClusterUuids(callCluster, timestamp, maxBucketSize); + const [licenses, stats] = await Promise.all([ + getLicenses(clusterDetails, callCluster, maxBucketSize), + getAllStats(clusterDetails, callCluster, timestamp, maxBucketSize), + ]); + return stats.map((stat) => { + const license = licenses[stat.cluster_uuid]; + return { + ...(license ? { license } : {}), + ...stat, + collectionSource: 'monitoring', + }; + }); + }, + }); + usageCollection.registerCollector(monitoringStatsCollector); +} diff --git a/x-pack/plugins/monitoring/server/types.ts b/x-pack/plugins/monitoring/server/types.ts index 543a12fb4135..b25daced50b7 100644 --- a/x-pack/plugins/monitoring/server/types.ts +++ b/x-pack/plugins/monitoring/server/types.ts @@ -6,7 +6,6 @@ import { Observable } from 'rxjs'; import { IRouter, ILegacyClusterClient, Logger } from 'kibana/server'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; -import { TelemetryCollectionManagerPluginSetup } from 'src/plugins/telemetry_collection_manager/server'; import { LicenseFeature, ILicense } from '../../licensing/server'; import { PluginStartContract as ActionsPluginsStartContact } from '../../actions/server'; import { @@ -35,7 +34,6 @@ export interface MonitoringElasticsearchConfig { export interface PluginsSetup { encryptedSavedObjects?: EncryptedSavedObjectsPluginSetup; - telemetryCollectionManager?: TelemetryCollectionManagerPluginSetup; usageCollection?: UsageCollectionSetup; licensing: LicensingPluginSetup; features: FeaturesPluginSetupContract; diff --git a/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/__snapshots__/get_stats_with_xpack.test.ts.snap b/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/__snapshots__/get_stats_with_xpack.test.ts.snap index b9bb206b8056..b68186c0c343 100644 --- a/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/__snapshots__/get_stats_with_xpack.test.ts.snap +++ b/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/__snapshots__/get_stats_with_xpack.test.ts.snap @@ -156,3 +156,89 @@ Object { "version": "8.0.0", } `; + +exports[`Telemetry Collection: Get Aggregated Stats X-Pack telemetry with appended Monitoring data 1`] = ` +Object { + "cluster_name": "test", + "cluster_stats": Object { + "nodes": Object { + "usage": Object { + "nodes": Array [ + Object { + "aggregations": Object { + "terms": Object { + "bytes": 2, + }, + }, + "node_id": "some_node_id", + "rest_actions": Object { + "nodes_usage_action": 1, + }, + "since": 1588616945163, + "timestamp": 1588617023177, + }, + ], + }, + }, + }, + "cluster_uuid": "test", + "collection": "local", + "stack_stats": Object { + "data": Array [], + "kibana": Object { + "count": 1, + "great": "googlymoogly", + "indices": 1, + "os": Object { + "platformReleases": Array [ + Object { + "count": 1, + "platformRelease": "iv", + }, + ], + "platforms": Array [ + Object { + "count": 1, + "platform": "rocky", + }, + ], + }, + "plugins": Object { + "clouds": Object { + "chances": 95, + }, + "localization": Object { + "integrities": Object {}, + "labelsCount": 0, + "locale": "en", + }, + "rain": Object { + "chances": 2, + }, + "snow": Object { + "chances": 0, + }, + "sun": Object { + "chances": 5, + }, + }, + "versions": Array [ + Object { + "count": 1, + "version": "8675309", + }, + ], + }, + "xpack": Object {}, + }, + "timestamp": Any, + "version": "8.0.0", +} +`; + +exports[`Telemetry Collection: Get Aggregated Stats X-Pack telemetry with appended Monitoring data 2`] = ` +Object { + "collectionSource": "monitoring", + "timestamp": Any, +} +`; diff --git a/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_stats_with_xpack.test.ts b/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_stats_with_xpack.test.ts index a4806cefeef3..5b3f73f206c6 100644 --- a/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_stats_with_xpack.test.ts +++ b/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_stats_with_xpack.test.ts @@ -48,34 +48,54 @@ const getContext = () => ({ logger: coreMock.createPluginInitializerContext().logger.get('test'), }); -const mockUsageCollection = (kibanaUsage = kibana) => ({ +const mockUsageCollection = (kibanaUsage: Record = kibana) => ({ bulkFetch: () => kibanaUsage, toObject: (data: any) => data, }); +/** + * Instantiate the esClient mock with the common requests + */ +function mockEsClient() { + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + // mock for license should return a basic license + esClient.license.get.mockResolvedValue( + // @ts-ignore we only care about the response body + { body: { license: { type: 'basic' } } } + ); + // mock for xpack usage should return an empty object + esClient.xpack.usage.mockResolvedValue( + // @ts-ignore we only care about the response body + { body: {} } + ); + // mock for nodes usage should resolve for this test + esClient.nodes.usage.mockResolvedValue( + // @ts-ignore we only care about the response body + { body: { cluster_name: 'test cluster', nodes: nodesUsage } } + ); + // mock for info should resolve for this test + esClient.info.mockResolvedValue( + // @ts-ignore we only care about the response body + { + body: { + cluster_uuid: 'test', + cluster_name: 'test', + version: { number: '8.0.0' }, + }, + } + ); + + return esClient; +} + describe('Telemetry Collection: Get Aggregated Stats', () => { test('OSS-like telemetry (no license nor X-Pack telemetry)', async () => { - const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + const esClient = mockEsClient(); // mock for xpack.usage should throw a 404 for this test esClient.xpack.usage.mockRejectedValue(new Error('Not Found')); // mock for license should throw a 404 for this test esClient.license.get.mockRejectedValue(new Error('Not Found')); - // mock for nodes usage should resolve for this test - esClient.nodes.usage.mockResolvedValue( - // @ts-ignore we only care about the response body - { body: { cluster_name: 'test cluster', nodes: nodesUsage } } - ); - // mock for info should resolve for this test - esClient.info.mockResolvedValue( - // @ts-ignore we only care about the response body - { - body: { - cluster_uuid: 'test', - cluster_name: 'test', - version: { number: '8.0.0' }, - }, - } - ); + const usageCollection = mockUsageCollection(); const context = getContext(); @@ -95,32 +115,7 @@ describe('Telemetry Collection: Get Aggregated Stats', () => { }); test('X-Pack telemetry (license + X-Pack)', async () => { - const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; - // mock for license should return a basic license - esClient.license.get.mockResolvedValue( - // @ts-ignore we only care about the response body - { body: { license: { type: 'basic' } } } - ); - // mock for xpack usage should return an empty object - esClient.xpack.usage.mockResolvedValue( - // @ts-ignore we only care about the response body - { body: {} } - ); - // mock for nodes usage should return the cluster name and nodes usage - esClient.nodes.usage.mockResolvedValue( - // @ts-ignore we only care about the response body - { body: { cluster_name: 'test cluster', nodes: nodesUsage } } - ); - esClient.info.mockResolvedValue( - // @ts-ignore we only care about the response body - { - body: { - cluster_uuid: 'test', - cluster_name: 'test', - version: { number: '8.0.0' }, - }, - } - ); + const esClient = mockEsClient(); const usageCollection = mockUsageCollection(); const context = getContext(); @@ -138,4 +133,29 @@ describe('Telemetry Collection: Get Aggregated Stats', () => { }); }); }); + + test('X-Pack telemetry with appended Monitoring data', async () => { + const esClient = mockEsClient(); + const usageCollection = mockUsageCollection({ + ...kibana, + monitoringTelemetry: [ + { collectionSource: 'monitoring', timestamp: new Date().toISOString() }, + ], + }); + const context = getContext(); + + const stats = await getStatsWithXpack( + [{ clusterUuid: '1234' }], + { + esClient, + usageCollection, + } as any, + context + ); + stats.forEach((entry, index) => { + expect(entry).toMatchSnapshot({ + timestamp: expect.any(String), + }); + }); + }); }); diff --git a/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_stats_with_xpack.ts b/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_stats_with_xpack.ts index 87e3d0a9613d..c0e55274b08d 100644 --- a/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_stats_with_xpack.ts +++ b/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_stats_with_xpack.ts @@ -21,14 +21,23 @@ export const getStatsWithXpack: StatsGetter<{}, TelemetryAggregatedStats> = asyn const clustersLocalStats = await getLocalStats(clustersDetails, config, context); const xpack = await getXPackUsage(esClient).catch(() => undefined); // We want to still report something (and do not lose the license) even when this method fails. - return clustersLocalStats.map((localStats) => { - if (xpack) { - return { - ...localStats, - stack_stats: { ...localStats.stack_stats, xpack }, - }; - } + return clustersLocalStats + .map((localStats) => { + if (xpack) { + return { + ...localStats, + stack_stats: { ...localStats.stack_stats, xpack }, + }; + } - return localStats; - }); + return localStats; + }) + .reduce((acc, stats) => { + // Concatenate the telemetry reported via monitoring as additional payloads instead of reporting it inside of stack_stats.kibana.plugins.monitoringTelemetry + const monitoringTelemetry = stats.stack_stats.kibana?.plugins?.monitoringTelemetry; + if (monitoringTelemetry) { + delete stats.stack_stats.kibana!.plugins.monitoringTelemetry; + } + return [...acc, stats, ...(monitoringTelemetry || [])]; + }, [] as TelemetryAggregatedStats[]); }; diff --git a/x-pack/test/api_integration/apis/telemetry/telemetry.js b/x-pack/test/api_integration/apis/telemetry/telemetry.js index b21ca27167bd..d0b7b2bbbb7d 100644 --- a/x-pack/test/api_integration/apis/telemetry/telemetry.js +++ b/x-pack/test/api_integration/apis/telemetry/telemetry.js @@ -5,48 +5,108 @@ */ import expect from '@kbn/expect'; +import moment from 'moment'; import multiClusterFixture from './fixtures/multicluster'; import basicClusterFixture from './fixtures/basiccluster'; +/** + * Update the .monitoring-* documents loaded via the archiver to the recent `timestamp` + * @param esSupertest The client to send requests to ES + * @param fromTimestamp The lower timestamp limit to query the documents from + * @param toTimestamp The upper timestamp limit to query the documents from + * @param timestamp The new timestamp to be set + */ +function updateMonitoringDates(esSupertest, fromTimestamp, toTimestamp, timestamp) { + return Promise.all([ + esSupertest + .post('/.monitoring-es-*/_update_by_query?refresh=true') + .send({ + query: { + range: { + timestamp: { + format: 'epoch_millis', + gte: moment(fromTimestamp).valueOf(), + lte: moment(toTimestamp).valueOf(), + }, + }, + }, + script: { + source: `ctx._source.timestamp='${timestamp}'`, + lang: 'painless', + }, + }) + .expect(200), + esSupertest + .post('/.monitoring-kibana-*/_update_by_query?refresh=true') + .send({ + query: { + range: { + timestamp: { + format: 'epoch_millis', + gte: moment(fromTimestamp).valueOf(), + lte: moment(toTimestamp).valueOf(), + }, + }, + }, + script: { + source: `ctx._source.timestamp='${timestamp}'`, + lang: 'painless', + }, + }) + .expect(200), + ]); +} + export default function ({ getService }) { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); + const esSupertest = getService('esSupertest'); describe('/api/telemetry/v2/clusters/_stats', () => { - it('should load multiple trial-license clusters', async () => { + const timestamp = new Date().toISOString(); + describe('monitoring/multicluster', () => { const archive = 'monitoring/multicluster'; - const timestamp = '2017-08-16T00:00:00Z'; - - await esArchiver.load(archive); - - const { body } = await supertest - .post('/api/telemetry/v2/clusters/_stats') - .set('kbn-xsrf', 'xxx') - .send({ timestamp, unencrypted: true }) - .expect(200); - - expect(body).length(3); - expect(body).to.eql(multiClusterFixture); + const fromTimestamp = '2017-08-15T21:00:00.000Z'; + const toTimestamp = '2017-08-16T00:00:00.000Z'; + before(async () => { + await esArchiver.load(archive); + await updateMonitoringDates(esSupertest, fromTimestamp, toTimestamp, timestamp); + }); + after(() => esArchiver.unload(archive)); + it('should load multiple trial-license clusters', async () => { + const { body } = await supertest + .post('/api/telemetry/v2/clusters/_stats') + .set('kbn-xsrf', 'xxx') + .send({ timestamp, unencrypted: true }) + .expect(200); - await esArchiver.unload(archive); + expect(body).length(4); + const [localXPack, ...monitoring] = body; + expect(localXPack.collectionSource).to.eql('local_xpack'); + expect(monitoring).to.eql(multiClusterFixture.map((item) => ({ ...item, timestamp }))); + }); }); describe('with basic cluster and reporting and canvas usage info', () => { - it('should load non-expiring basic cluster', async () => { - const archive = 'monitoring/basic_6.3.x'; - const timestamp = '2018-07-23T22:13:00Z'; - + const archive = 'monitoring/basic_6.3.x'; + const fromTimestamp = '2018-07-23T22:54:59.087Z'; + const toTimestamp = '2018-07-23T22:55:05.933Z'; + before(async () => { await esArchiver.load(archive); - + await updateMonitoringDates(esSupertest, fromTimestamp, toTimestamp, timestamp); + }); + after(() => esArchiver.unload(archive)); + it('should load non-expiring basic cluster', async () => { const { body } = await supertest .post('/api/telemetry/v2/clusters/_stats') .set('kbn-xsrf', 'xxx') .send({ timestamp, unencrypted: true }) .expect(200); - expect(body).to.eql(basicClusterFixture); - - await esArchiver.unload(archive); + expect(body).length(2); + const [localXPack, ...monitoring] = body; + expect(localXPack.collectionSource).to.eql('local_xpack'); + expect(monitoring).to.eql(basicClusterFixture.map((item) => ({ ...item, timestamp }))); }); }); }); From 4786b70cd147274147aa2444ada2de22c999fc56 Mon Sep 17 00:00:00 2001 From: Thomas Watson Date: Wed, 18 Nov 2020 19:45:38 +0100 Subject: [PATCH 08/49] Bump is-my-json-valid to v2.20.5 (#83642) --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 337d7600bdb3..d43c450e0c58 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16809,9 +16809,9 @@ is-my-ip-valid@^1.0.0: integrity sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ== is-my-json-valid@^2.10.0: - version "2.19.0" - resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.19.0.tgz#8fd6e40363cd06b963fa877d444bfb5eddc62175" - integrity sha512-mG0f/unGX1HZ5ep4uhRaPOS8EkAY8/j6mDRMJrutq4CqhoJWYp7qAlonIPy3TV7p3ju4TK9fo/PbnoksWmsp5Q== + version "2.20.5" + resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.20.5.tgz#5eca6a8232a687f68869b7361be1612e7512e5df" + integrity sha512-VTPuvvGQtxvCeghwspQu1rBgjYUT6FGxPlvFKbYuFtgc4ADsX3U5ihZOYN0qyU6u+d4X9xXb0IT5O6QpXKt87A== dependencies: generate-function "^2.0.0" generate-object-property "^1.1.0" From 02b59f25d2366699880c473a0ede385dd5932259 Mon Sep 17 00:00:00 2001 From: Thomas Watson Date: Wed, 18 Nov 2020 19:46:32 +0100 Subject: [PATCH 09/49] Bump jsonpointer to v4.1.0 (#83641) --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index d43c450e0c58..a88cad99fca0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18312,9 +18312,9 @@ jsonparse@^1.2.0: integrity sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA= jsonpointer@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9" - integrity sha1-T9kss04OnbPInIYi7PUfm5eMbLk= + version "4.1.0" + resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.1.0.tgz#501fb89986a2389765ba09e6053299ceb4f2c2cc" + integrity sha512-CXcRvMyTlnR53xMcKnuMzfCA5i/nfblTnnr74CZb6C4vG39eu6w51t7nKmU5MfLfbTgGItliNyjO/ciNPDqClg== jsonwebtoken@^8.3.0, jsonwebtoken@^8.5.1: version "8.5.1" From 19ed71968afa416bc7ed7ddefbbbeff7fad7fe83 Mon Sep 17 00:00:00 2001 From: Thomas Watson Date: Wed, 18 Nov 2020 19:47:14 +0100 Subject: [PATCH 10/49] Bump y18n@5 to v5.0.5 (#83644) --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index a88cad99fca0..15ce27eeca44 100644 --- a/yarn.lock +++ b/yarn.lock @@ -29797,9 +29797,9 @@ y18n@^4.0.0: integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== y18n@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.1.tgz#1ad2a7eddfa8bce7caa2e1f6b5da96c39d99d571" - integrity sha512-/jJ831jEs4vGDbYPQp4yGKDYPSCCEQ45uZWJHE1AoYBzqdZi8+LDWas0z4HrmJXmKdpFsTiowSHXdxyFhpmdMg== + version "5.0.5" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.5.tgz#8769ec08d03b1ea2df2500acef561743bbb9ab18" + integrity sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg== yallist@^2.1.2: version "2.1.2" From 02dfc47be60edf549cc7780dee14a1374639dde1 Mon Sep 17 00:00:00 2001 From: Thomas Watson Date: Wed, 18 Nov 2020 19:47:53 +0100 Subject: [PATCH 11/49] Bump flat to v4.1.1 (#83647) --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 15ce27eeca44..2a82e7024a89 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13805,9 +13805,9 @@ flat-cache@^2.0.1: write "1.0.3" flat@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/flat/-/flat-4.1.0.tgz#090bec8b05e39cba309747f1d588f04dbaf98db2" - integrity sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw== + version "4.1.1" + resolved "https://registry.yarnpkg.com/flat/-/flat-4.1.1.tgz#a392059cc382881ff98642f5da4dde0a959f309b" + integrity sha512-FmTtBsHskrU6FJ2VxCnsDb84wu9zhmO3cUX2kGFb5tuwhfXxGciiT0oRY+cck35QmG+NmGh5eLz6lLCpWTqwpA== dependencies: is-buffer "~2.0.3" From 2a365ff6329544465227e61141ded6fba8bb2c80 Mon Sep 17 00:00:00 2001 From: Dario Gieselaar Date: Wed, 18 Nov 2020 19:49:58 +0100 Subject: [PATCH 12/49] [APM] Improve router types (#83620) * [APM] Improve router types * Pass processorEvent param to useDynamicIndexPattern --- .../common/runtime_types/merge/index.test.ts | 71 +++++++ .../apm/common/runtime_types/merge/index.ts | 68 ++++++ .../strict_keys_rt/index.test.ts | 106 ++++++++++ .../runtime_types/strict_keys_rt/index.ts | 195 ++++++++++++++++++ .../anomaly_detection_setup_link.tsx | 5 +- .../app/ErrorGroupDetails/index.tsx | 4 +- .../app/ErrorGroupOverview/index.tsx | 4 +- .../route_handlers/agent_configuration.tsx | 2 +- .../app/RumDashboard/ClientMetrics/index.tsx | 2 +- .../ImpactfulMetrics/JSErrors.tsx | 2 +- .../PageLoadDistribution/index.tsx | 2 +- .../PageLoadDistribution/use_breakdowns.ts | 2 +- .../app/RumDashboard/PageViewsTrend/index.tsx | 2 +- .../app/RumDashboard/Panels/MainFilters.tsx | 2 +- .../URLFilter/URLSearch/index.tsx | 2 +- .../RumDashboard/UXMetrics/KeyUXMetrics.tsx | 2 +- .../app/RumDashboard/UXMetrics/index.tsx | 2 +- .../RumDashboard/VisitorBreakdown/index.tsx | 2 +- .../app/RumDashboard/ux_overview_fetchers.ts | 4 +- .../Popover/ServiceStatsFetcher.tsx | 2 +- .../components/app/ServiceMap/index.tsx | 2 +- .../app/ServiceNodeMetrics/index.tsx | 4 +- .../app/ServiceNodeOverview/index.tsx | 2 +- .../ServicePage/ServicePage.tsx | 6 +- .../SettingsPage/saveConfig.ts | 3 +- .../List/ConfirmDeleteModal.tsx | 3 +- .../Settings/AgentConfigurations/index.tsx | 2 +- .../app/Settings/ApmIndices/index.tsx | 5 +- .../CustomLinkFlyout/DeleteButton.tsx | 3 +- .../CustomLinkFlyout/LinkPreview.tsx | 2 +- .../CustomLinkFlyout/link_preview.test.tsx | 4 +- .../CustomLinkFlyout/saveCustomLink.ts | 6 +- .../CustomizeUI/CustomLink/index.test.tsx | 2 +- .../Settings/CustomizeUI/CustomLink/index.tsx | 5 +- .../anomaly_detection/add_environments.tsx | 2 +- .../Settings/anomaly_detection/create_jobs.ts | 3 +- .../app/Settings/anomaly_detection/index.tsx | 7 +- .../public/components/app/TraceLink/index.tsx | 2 +- .../components/app/TraceOverview/index.tsx | 4 +- .../app/service_inventory/index.tsx | 2 +- .../service_overview_errors_table/index.tsx | 2 +- .../TransactionActionMenu.tsx | 2 +- .../__test__/TransactionActionMenu.test.tsx | 2 +- .../transaction_error_rate_chart/index.tsx | 4 +- .../public/context/charts_sync_context.tsx | 2 +- .../plugins/apm/public/hooks/useAgentName.ts | 2 +- .../public/hooks/useAnomalyDetectionJobs.ts | 2 +- .../public/hooks/useDynamicIndexPattern.ts | 2 +- .../apm/public/hooks/useEnvironments.tsx | 2 +- .../public/hooks/useServiceMetricCharts.ts | 2 +- .../hooks/useServiceTransactionTypes.tsx | 2 +- .../public/hooks/useTransactionBreakdown.ts | 4 +- .../apm/public/hooks/useTransactionCharts.ts | 3 +- .../hooks/useTransactionDistribution.ts | 4 +- .../apm/public/hooks/useTransactionList.ts | 4 +- .../plugins/apm/public/hooks/useWaterfall.ts | 2 +- .../apm/public/hooks/use_annotations.ts | 2 +- .../services/__test__/callApmApi.test.ts | 7 +- .../apm_observability_overview_fetchers.ts | 4 +- .../public/services/rest/createCallApmApi.ts | 19 +- .../apm/public/services/rest/index_pattern.ts | 5 +- .../apm/server/lib/helpers/setup_request.ts | 2 - .../plugins/apm/server/routes/correlations.ts | 20 +- .../server/routes/create_api/index.test.ts | 82 +++++--- .../apm/server/routes/create_api/index.ts | 135 ++++++------ .../apm/server/routes/create_apm_api.ts | 3 +- .../plugins/apm/server/routes/create_route.ts | 27 ++- x-pack/plugins/apm/server/routes/errors.ts | 30 +-- .../apm/server/routes/index_pattern.ts | 26 ++- x-pack/plugins/apm/server/routes/metrics.ts | 10 +- .../server/routes/observability_overview.ts | 16 +- .../plugins/apm/server/routes/rum_client.ts | 110 +++++----- .../plugins/apm/server/routes/service_map.ts | 20 +- .../apm/server/routes/service_nodes.ts | 10 +- x-pack/plugins/apm/server/routes/services.ts | 72 +++---- .../routes/settings/agent_configuration.ts | 80 ++++--- .../routes/settings/anomaly_detection.ts | 25 +-- .../apm/server/routes/settings/apm_indices.ts | 25 +-- .../apm/server/routes/settings/custom_link.ts | 53 +++-- x-pack/plugins/apm/server/routes/traces.ts | 20 +- .../plugins/apm/server/routes/transaction.ts | 10 +- .../apm/server/routes/transaction_groups.ts | 61 +++--- x-pack/plugins/apm/server/routes/typings.ts | 160 ++++++-------- .../plugins/apm/server/routes/ui_filters.ts | 42 ++-- 84 files changed, 1044 insertions(+), 623 deletions(-) create mode 100644 x-pack/plugins/apm/common/runtime_types/merge/index.test.ts create mode 100644 x-pack/plugins/apm/common/runtime_types/merge/index.ts create mode 100644 x-pack/plugins/apm/common/runtime_types/strict_keys_rt/index.test.ts create mode 100644 x-pack/plugins/apm/common/runtime_types/strict_keys_rt/index.ts diff --git a/x-pack/plugins/apm/common/runtime_types/merge/index.test.ts b/x-pack/plugins/apm/common/runtime_types/merge/index.test.ts new file mode 100644 index 000000000000..0e0cb4a349c8 --- /dev/null +++ b/x-pack/plugins/apm/common/runtime_types/merge/index.test.ts @@ -0,0 +1,71 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import * as t from 'io-ts'; +import { isLeft } from 'fp-ts/lib/Either'; +import { merge } from './'; +import { jsonRt } from '../json_rt'; + +describe('merge', () => { + it('fails on one or more errors', () => { + const type = merge([t.type({ foo: t.string }), t.type({ bar: t.number })]); + + const result = type.decode({ foo: '' }); + + expect(isLeft(result)).toBe(true); + }); + + it('merges left to right', () => { + const typeBoolean = merge([ + t.type({ foo: t.string }), + t.type({ foo: jsonRt.pipe(t.boolean) }), + ]); + + const resultBoolean = typeBoolean.decode({ + foo: 'true', + }); + + // @ts-expect-error + expect(resultBoolean.right).toEqual({ + foo: true, + }); + + const typeString = merge([ + t.type({ foo: jsonRt.pipe(t.boolean) }), + t.type({ foo: t.string }), + ]); + + const resultString = typeString.decode({ + foo: 'true', + }); + + // @ts-expect-error + expect(resultString.right).toEqual({ + foo: 'true', + }); + }); + + it('deeply merges values', () => { + const type = merge([ + t.type({ foo: t.type({ baz: t.string }) }), + t.type({ foo: t.type({ bar: t.string }) }), + ]); + + const result = type.decode({ + foo: { + bar: '', + baz: '', + }, + }); + + // @ts-expect-error + expect(result.right).toEqual({ + foo: { + bar: '', + baz: '', + }, + }); + }); +}); diff --git a/x-pack/plugins/apm/common/runtime_types/merge/index.ts b/x-pack/plugins/apm/common/runtime_types/merge/index.ts new file mode 100644 index 000000000000..76a1092436dc --- /dev/null +++ b/x-pack/plugins/apm/common/runtime_types/merge/index.ts @@ -0,0 +1,68 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import * as t from 'io-ts'; +import { merge as lodashMerge } from 'lodash'; +import { isLeft } from 'fp-ts/lib/Either'; +import { ValuesType } from 'utility-types'; + +export type MergeType< + T extends t.Any[], + U extends ValuesType = ValuesType +> = t.Type & { + _tag: 'MergeType'; + types: T; +}; + +// this is similar to t.intersection, but does a deep merge +// instead of a shallow merge + +export function merge( + types: [A, B] +): MergeType<[A, B]>; + +export function merge(types: t.Any[]) { + const mergeType = new t.Type( + 'merge', + (u): u is unknown => { + return types.every((type) => type.is(u)); + }, + (input, context) => { + const errors: t.Errors = []; + + const successes: unknown[] = []; + + const results = types.map((type, index) => + type.validate( + input, + context.concat({ + key: String(index), + type, + actual: input, + }) + ) + ); + + results.forEach((result) => { + if (isLeft(result)) { + errors.push(...result.left); + } else { + successes.push(result.right); + } + }); + + const mergedValues = lodashMerge({}, ...successes); + + return errors.length > 0 ? t.failures(errors) : t.success(mergedValues); + }, + (a) => types.reduce((val, type) => type.encode(val), a) + ); + + return { + ...mergeType, + _tag: 'MergeType', + types, + }; +} diff --git a/x-pack/plugins/apm/common/runtime_types/strict_keys_rt/index.test.ts b/x-pack/plugins/apm/common/runtime_types/strict_keys_rt/index.test.ts new file mode 100644 index 000000000000..ac2f7d8e1679 --- /dev/null +++ b/x-pack/plugins/apm/common/runtime_types/strict_keys_rt/index.test.ts @@ -0,0 +1,106 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import * as t from 'io-ts'; +import { isRight, isLeft } from 'fp-ts/lib/Either'; +import { strictKeysRt } from './'; +import { jsonRt } from '../json_rt'; + +describe('strictKeysRt', () => { + it('correctly and deeply validates object keys', () => { + const checks: Array<{ type: t.Type; passes: any[]; fails: any[] }> = [ + { + type: t.intersection([ + t.type({ foo: t.string }), + t.partial({ bar: t.string }), + ]), + passes: [{ foo: '' }, { foo: '', bar: '' }], + fails: [ + { foo: '', unknownKey: '' }, + { foo: '', bar: '', unknownKey: '' }, + ], + }, + { + type: t.type({ + path: t.union([ + t.type({ serviceName: t.string }), + t.type({ transactionType: t.string }), + ]), + }), + passes: [ + { path: { serviceName: '' } }, + { path: { transactionType: '' } }, + ], + fails: [ + { path: { serviceName: '', unknownKey: '' } }, + { path: { transactionType: '', unknownKey: '' } }, + { path: { serviceName: '', transactionType: '' } }, + { path: { serviceName: '' }, unknownKey: '' }, + ], + }, + { + type: t.intersection([ + t.type({ query: t.type({ bar: t.string }) }), + t.partial({ query: t.partial({ _debug: t.boolean }) }), + ]), + passes: [{ query: { bar: '', _debug: true } }], + fails: [{ query: { _debug: true } }], + }, + ]; + + checks.forEach((check) => { + const { type, passes, fails } = check; + + const strictType = strictKeysRt(type); + + passes.forEach((value) => { + const result = strictType.decode(value); + + if (!isRight(result)) { + throw new Error( + `Expected ${JSON.stringify( + value + )} to be allowed, but validation failed with ${ + result.left[0].message + }` + ); + } + }); + + fails.forEach((value) => { + const result = strictType.decode(value); + + if (!isLeft(result)) { + throw new Error( + `Expected ${JSON.stringify( + value + )} to be disallowed, but validation succeeded` + ); + } + }); + }); + }); + + it('does not support piped types', () => { + const typeA = t.type({ + query: t.type({ filterNames: jsonRt.pipe(t.array(t.string)) }), + } as Record); + + const typeB = t.partial({ + query: t.partial({ _debug: jsonRt.pipe(t.boolean) }), + }); + + const value = { + query: { + _debug: 'true', + filterNames: JSON.stringify(['host', 'agentName']), + }, + }; + + const pipedType = strictKeysRt(typeA.pipe(typeB)); + + expect(isLeft(pipedType.decode(value))).toBe(true); + }); +}); diff --git a/x-pack/plugins/apm/common/runtime_types/strict_keys_rt/index.ts b/x-pack/plugins/apm/common/runtime_types/strict_keys_rt/index.ts new file mode 100644 index 000000000000..9ca37b4a0a26 --- /dev/null +++ b/x-pack/plugins/apm/common/runtime_types/strict_keys_rt/index.ts @@ -0,0 +1,195 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import * as t from 'io-ts'; +import { either, isRight } from 'fp-ts/lib/Either'; +import { mapValues, difference, isPlainObject, forEach } from 'lodash'; +import { MergeType, merge } from '../merge'; + +/* + Type that tracks validated keys, and fails when the input value + has keys that have not been validated. +*/ + +type ParsableType = + | t.IntersectionType + | t.UnionType + | t.PartialType + | t.ExactType + | t.InterfaceType + | MergeType; + +function getKeysInObject>( + object: T, + prefix: string = '' +): string[] { + const keys: string[] = []; + forEach(object, (value, key) => { + const ownPrefix = prefix ? `${prefix}.${key}` : key; + keys.push(ownPrefix); + if (isPlainObject(object[key])) { + keys.push( + ...getKeysInObject(object[key] as Record, ownPrefix) + ); + } + }); + return keys; +} + +function addToContextWhenValidated< + T extends t.InterfaceType | t.PartialType +>(type: T, prefix: string): T { + const validate = (input: unknown, context: t.Context) => { + const result = type.validate(input, context); + const keysType = context[0].type as StrictKeysType; + if (!('trackedKeys' in keysType)) { + throw new Error('Expected a top-level StrictKeysType'); + } + if (isRight(result)) { + keysType.trackedKeys.push( + ...Object.keys(type.props).map((propKey) => `${prefix}${propKey}`) + ); + } + return result; + }; + + if (type._tag === 'InterfaceType') { + return new t.InterfaceType( + type.name, + type.is, + validate, + type.encode, + type.props + ) as T; + } + + return new t.PartialType( + type.name, + type.is, + validate, + type.encode, + type.props + ) as T; +} + +function trackKeysOfValidatedTypes( + type: ParsableType | t.Any, + prefix: string = '' +): t.Any { + if (!('_tag' in type)) { + return type; + } + const taggedType = type as ParsableType; + + switch (taggedType._tag) { + case 'IntersectionType': { + const collectionType = type as t.IntersectionType; + return t.intersection( + collectionType.types.map((rt) => + trackKeysOfValidatedTypes(rt, prefix) + ) as [t.Any, t.Any] + ); + } + + case 'UnionType': { + const collectionType = type as t.UnionType; + return t.union( + collectionType.types.map((rt) => + trackKeysOfValidatedTypes(rt, prefix) + ) as [t.Any, t.Any] + ); + } + + case 'MergeType': { + const collectionType = type as MergeType; + return merge( + collectionType.types.map((rt) => + trackKeysOfValidatedTypes(rt, prefix) + ) as [t.Any, t.Any] + ); + } + + case 'PartialType': { + const propsType = type as t.PartialType; + + return addToContextWhenValidated( + t.partial( + mapValues(propsType.props, (val, key) => + trackKeysOfValidatedTypes(val, `${prefix}${key}.`) + ) + ), + prefix + ); + } + + case 'InterfaceType': { + const propsType = type as t.InterfaceType; + + return addToContextWhenValidated( + t.type( + mapValues(propsType.props, (val, key) => + trackKeysOfValidatedTypes(val, `${prefix}${key}.`) + ) + ), + prefix + ); + } + + case 'ExactType': { + const exactType = type as t.ExactType; + + return t.exact( + trackKeysOfValidatedTypes(exactType.type, prefix) as t.HasProps + ); + } + + default: + return type; + } +} + +class StrictKeysType< + A = any, + O = A, + I = any, + T extends t.Type = t.Type +> extends t.Type { + trackedKeys: string[]; + + constructor(type: T) { + const trackedType = trackKeysOfValidatedTypes(type); + + super( + 'strict_keys', + trackedType.is, + (input, context) => { + this.trackedKeys.length = 0; + return either.chain(trackedType.validate(input, context), (i) => { + const originalKeys = getKeysInObject( + input as Record + ); + const excessKeys = difference(originalKeys, this.trackedKeys); + + if (excessKeys.length) { + return t.failure( + i, + context, + `Excess keys are not allowed: \n${excessKeys.join('\n')}` + ); + } + + return t.success(i); + }); + }, + trackedType.encode + ); + + this.trackedKeys = []; + } +} + +export function strictKeysRt(type: T): T { + return (new StrictKeysType(type) as unknown) as T; +} diff --git a/x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.tsx b/x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.tsx index d75446cb0dd4..e08bd01a1842 100644 --- a/x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.tsx +++ b/x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.tsx @@ -24,8 +24,7 @@ import { APIReturnType } from '../../services/rest/createCallApmApi'; import { units } from '../../style/variables'; export type AnomalyDetectionApiResponse = APIReturnType< - '/api/apm/settings/anomaly-detection', - 'GET' + 'GET /api/apm/settings/anomaly-detection' >; const DEFAULT_DATA = { jobs: [], hasLegacyJobs: false }; @@ -60,7 +59,7 @@ export function AnomalyDetectionSetupLink() { export function MissingJobsAlert({ environment }: { environment?: string }) { const { data = DEFAULT_DATA, status } = useFetcher( (callApmApi) => - callApmApi({ pathname: `/api/apm/settings/anomaly-detection` }), + callApmApi({ endpoint: `GET /api/apm/settings/anomaly-detection` }), [], { preservePreviousData: false, showToastOnError: false } ); diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/index.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/index.tsx index f47674ba5891..dc97642dec35 100644 --- a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/index.tsx @@ -72,7 +72,7 @@ export function ErrorGroupDetails({ location, match }: ErrorGroupDetailsProps) { const { data: errorGroupData } = useFetcher(() => { if (start && end) { return callApmApi({ - pathname: '/api/apm/services/{serviceName}/errors/{groupId}', + endpoint: 'GET /api/apm/services/{serviceName}/errors/{groupId}', params: { path: { serviceName, @@ -91,7 +91,7 @@ export function ErrorGroupDetails({ location, match }: ErrorGroupDetailsProps) { const { data: errorDistributionData } = useFetcher(() => { if (start && end) { return callApmApi({ - pathname: '/api/apm/services/{serviceName}/errors/distribution', + endpoint: 'GET /api/apm/services/{serviceName}/errors/distribution', params: { path: { serviceName, diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/index.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/index.tsx index 52fb4b33cbc5..e2a02a2f3e7a 100644 --- a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/index.tsx @@ -36,7 +36,7 @@ function ErrorGroupOverview({ serviceName }: ErrorGroupOverviewProps) { const { data: errorDistributionData } = useFetcher(() => { if (start && end) { return callApmApi({ - pathname: '/api/apm/services/{serviceName}/errors/distribution', + endpoint: 'GET /api/apm/services/{serviceName}/errors/distribution', params: { path: { serviceName, @@ -56,7 +56,7 @@ function ErrorGroupOverview({ serviceName }: ErrorGroupOverviewProps) { if (start && end) { return callApmApi({ - pathname: '/api/apm/services/{serviceName}/errors', + endpoint: 'GET /api/apm/services/{serviceName}/errors', params: { path: { serviceName, diff --git a/x-pack/plugins/apm/public/components/app/Main/route_config/route_handlers/agent_configuration.tsx b/x-pack/plugins/apm/public/components/app/Main/route_config/route_handlers/agent_configuration.tsx index f2ae0c2ff99e..ac1668a54ab9 100644 --- a/x-pack/plugins/apm/public/components/app/Main/route_config/route_handlers/agent_configuration.tsx +++ b/x-pack/plugins/apm/public/components/app/Main/route_config/route_handlers/agent_configuration.tsx @@ -26,7 +26,7 @@ export function EditAgentConfigurationRouteHandler( const res = useFetcher( (callApmApi) => { return callApmApi({ - pathname: '/api/apm/settings/agent-configuration/view', + endpoint: 'GET /api/apm/settings/agent-configuration/view', params: { query: { name, environment } }, }); }, diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx index b6924b955269..237d33a6a89a 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx @@ -53,7 +53,7 @@ export function ClientMetrics() { (callApmApi) => { if (uxQuery) { return callApmApi({ - pathname: '/api/apm/rum/client-metrics', + endpoint: 'GET /api/apm/rum/client-metrics', params: { query: { ...uxQuery, diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/ImpactfulMetrics/JSErrors.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/ImpactfulMetrics/JSErrors.tsx index 58f00604b8fd..4c4f7110cafb 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/ImpactfulMetrics/JSErrors.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/ImpactfulMetrics/JSErrors.tsx @@ -39,7 +39,7 @@ export function JSErrors() { (callApmApi) => { if (start && end && serviceName) { return callApmApi({ - pathname: '/api/apm/rum-client/js-errors', + endpoint: 'GET /api/apm/rum-client/js-errors', params: { query: { start, diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx index 88d14a0213a9..4b94b98704da 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx @@ -37,7 +37,7 @@ export function PageLoadDistribution() { if (start && end && serviceName) { return callApmApi({ - pathname: '/api/apm/rum-client/page-load-distribution', + endpoint: 'GET /api/apm/rum-client/page-load-distribution', params: { query: { start, diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/use_breakdowns.ts b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/use_breakdowns.ts index d6a544333531..c3f4ab44179f 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/use_breakdowns.ts +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/use_breakdowns.ts @@ -25,7 +25,7 @@ export const useBreakdowns = ({ percentileRange, field, value }: Props) => { (callApmApi) => { if (start && end && field && value) { return callApmApi({ - pathname: '/api/apm/rum-client/page-load-distribution/breakdown', + endpoint: 'GET /api/apm/rum-client/page-load-distribution/breakdown', params: { query: { start, diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx index 621098b6028c..84668f4b06d7 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx @@ -26,7 +26,7 @@ export function PageViewsTrend() { if (start && end && serviceName) { return callApmApi({ - pathname: '/api/apm/rum-client/page-view-trends', + endpoint: 'GET /api/apm/rum-client/page-view-trends', params: { query: { start, diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/Panels/MainFilters.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/Panels/MainFilters.tsx index 7c2107988533..6c7e2e22a989 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/Panels/MainFilters.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/Panels/MainFilters.tsx @@ -22,7 +22,7 @@ export function MainFilters() { (callApmApi) => { if (start && end) { return callApmApi({ - pathname: '/api/apm/rum-client/services', + endpoint: 'GET /api/apm/rum-client/services', params: { query: { start, diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/index.tsx index f9aeb484cbdf..67692a9a8554 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/index.tsx @@ -71,7 +71,7 @@ export function URLSearch({ onChange: onFilterChange }: Props) { const { transactionUrl, ...restFilters } = uiFilters; return callApmApi({ - pathname: '/api/apm/rum-client/url-search', + endpoint: 'GET /api/apm/rum-client/url-search', params: { query: { ...uxQuery, diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.tsx index c7fe8e885020..2ded35deb58f 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.tsx @@ -55,7 +55,7 @@ export function KeyUXMetrics({ data, loading }: Props) { (callApmApi) => { if (uxQuery) { return callApmApi({ - pathname: '/api/apm/rum-client/long-task-metrics', + endpoint: 'GET /api/apm/rum-client/long-task-metrics', params: { query: { ...uxQuery, diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/index.tsx index 983e3be1c21a..95a42ce3018f 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/index.tsx @@ -33,7 +33,7 @@ export function UXMetrics() { (callApmApi) => { if (uxQuery) { return callApmApi({ - pathname: '/api/apm/rum-client/web-core-vitals', + endpoint: 'GET /api/apm/rum-client/web-core-vitals', params: { query: uxQuery, }, diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdown/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdown/index.tsx index 67127f9c2fd8..ce9485690b93 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdown/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdown/index.tsx @@ -22,7 +22,7 @@ export function VisitorBreakdown() { if (start && end && serviceName) { return callApmApi({ - pathname: '/api/apm/rum-client/visitor-breakdown', + endpoint: 'GET /api/apm/rum-client/visitor-breakdown', params: { query: { start, diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/ux_overview_fetchers.ts b/x-pack/plugins/apm/public/components/app/RumDashboard/ux_overview_fetchers.ts index a9f2486a3c28..4610205cee7e 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/ux_overview_fetchers.ts +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/ux_overview_fetchers.ts @@ -19,7 +19,7 @@ export const fetchUxOverviewDate = async ({ serviceName, }: FetchDataParams): Promise => { const data = await callApmApi({ - pathname: '/api/apm/rum-client/web-core-vitals', + endpoint: 'GET /api/apm/rum-client/web-core-vitals', params: { query: { start: new Date(absoluteTime.start).toISOString(), @@ -37,7 +37,7 @@ export const fetchUxOverviewDate = async ({ export async function hasRumData({ absoluteTime }: HasDataParams) { return await callApmApi({ - pathname: '/api/apm/observability_overview/has_rum_data', + endpoint: 'GET /api/apm/observability_overview/has_rum_data', params: { query: { start: new Date(absoluteTime.start).toISOString(), diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/ServiceStatsFetcher.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/ServiceStatsFetcher.tsx index 9e8f1f7a0171..be8c5cf8cd43 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/ServiceStatsFetcher.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/ServiceStatsFetcher.tsx @@ -42,7 +42,7 @@ export function ServiceStatsFetcher({ (callApmApi) => { if (serviceName && start && end) { return callApmApi({ - pathname: '/api/apm/service-map/service/{serviceName}', + endpoint: 'GET /api/apm/service-map/service/{serviceName}', params: { path: { serviceName }, query: { start, end, uiFilters: JSON.stringify(uiFilters) }, diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx index 15adf8a70d35..1731d3f9430d 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx @@ -83,7 +83,7 @@ export function ServiceMap({ if (start && end) { return callApmApi({ isCachable: false, - pathname: '/api/apm/service-map', + endpoint: 'GET /api/apm/service-map', params: { query: { start, diff --git a/x-pack/plugins/apm/public/components/app/ServiceNodeMetrics/index.tsx b/x-pack/plugins/apm/public/components/app/ServiceNodeMetrics/index.tsx index 7c6b63f75382..efa6110fea10 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceNodeMetrics/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceNodeMetrics/index.tsx @@ -58,8 +58,8 @@ export function ServiceNodeMetrics({ match }: ServiceNodeMetricsProps) { (callApmApi) => { if (start && end) { return callApmApi({ - pathname: - '/api/apm/services/{serviceName}/node/{serviceNodeName}/metadata', + endpoint: + 'GET /api/apm/services/{serviceName}/node/{serviceNodeName}/metadata', params: { path: { serviceName, serviceNodeName }, query: { diff --git a/x-pack/plugins/apm/public/components/app/ServiceNodeOverview/index.tsx b/x-pack/plugins/apm/public/components/app/ServiceNodeOverview/index.tsx index b05785db1462..5c9677e3c7af 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceNodeOverview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceNodeOverview/index.tsx @@ -62,7 +62,7 @@ function ServiceNodeOverview({ serviceName }: ServiceNodeOverviewProps) { return undefined; } return callApmApi({ - pathname: '/api/apm/services/{serviceName}/serviceNodes', + endpoint: 'GET /api/apm/services/{serviceName}/serviceNodes', params: { path: { serviceName, diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/ServicePage/ServicePage.tsx b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/ServicePage/ServicePage.tsx index 869762e36088..7c0869afe0cd 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/ServicePage/ServicePage.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/ServicePage/ServicePage.tsx @@ -35,7 +35,7 @@ export function ServicePage({ newConfig, setNewConfig, onClickNext }: Props) { const { data: serviceNames = [], status: serviceNamesStatus } = useFetcher( (callApmApi) => { return callApmApi({ - pathname: '/api/apm/settings/agent-configuration/services', + endpoint: 'GET /api/apm/settings/agent-configuration/services', isCachable: true, }); }, @@ -47,7 +47,7 @@ export function ServicePage({ newConfig, setNewConfig, onClickNext }: Props) { (callApmApi) => { if (newConfig.service.name) { return callApmApi({ - pathname: '/api/apm/settings/agent-configuration/environments', + endpoint: 'GET /api/apm/settings/agent-configuration/environments', params: { query: { serviceName: omitAllOption(newConfig.service.name) }, }, @@ -67,7 +67,7 @@ export function ServicePage({ newConfig, setNewConfig, onClickNext }: Props) { } const { agentName } = await callApmApi({ - pathname: '/api/apm/settings/agent-configuration/agent_name', + endpoint: 'GET /api/apm/settings/agent-configuration/agent_name', params: { query: { serviceName } }, }); diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/SettingsPage/saveConfig.ts b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/SettingsPage/saveConfig.ts index 4e75b24e6af9..e15a57ff7539 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/SettingsPage/saveConfig.ts +++ b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/SettingsPage/saveConfig.ts @@ -25,8 +25,7 @@ export async function saveConfig({ }) { try { await callApmApi({ - pathname: '/api/apm/settings/agent-configuration', - method: 'PUT', + endpoint: 'PUT /api/apm/settings/agent-configuration', params: { query: { overwrite: isEditMode }, body: { diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/ConfirmDeleteModal.tsx b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/ConfirmDeleteModal.tsx index aca04a3e46ad..3483ad082280 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/ConfirmDeleteModal.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/ConfirmDeleteModal.tsx @@ -71,8 +71,7 @@ async function deleteConfig( ) { try { await callApmApi({ - pathname: '/api/apm/settings/agent-configuration', - method: 'DELETE', + endpoint: 'DELETE /api/apm/settings/agent-configuration', params: { body: { service: { diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/index.tsx index dfc78028c359..12c63f8702f2 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/index.tsx @@ -24,7 +24,7 @@ import { AgentConfigurationList } from './List'; export function AgentConfigurations() { const { refetch, data = [], status } = useFetcher( (callApmApi) => - callApmApi({ pathname: '/api/apm/settings/agent-configuration' }), + callApmApi({ endpoint: 'GET /api/apm/settings/agent-configuration' }), [], { preservePreviousData: false, showToastOnError: false } ); diff --git a/x-pack/plugins/apm/public/components/app/Settings/ApmIndices/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/ApmIndices/index.tsx index fac947b3ec68..a1ef9ddd8727 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/ApmIndices/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/ApmIndices/index.tsx @@ -72,8 +72,7 @@ async function saveApmIndices({ apmIndices: Record; }) { await callApmApi({ - method: 'POST', - pathname: '/api/apm/settings/apm-indices/save', + endpoint: 'POST /api/apm/settings/apm-indices/save', params: { body: apmIndices, }, @@ -94,7 +93,7 @@ export function ApmIndices() { const { data = INITIAL_STATE, status, refetch } = useFetcher( (_callApmApi) => _callApmApi({ - pathname: `/api/apm/settings/apm-index-settings`, + endpoint: `GET /api/apm/settings/apm-index-settings`, }), [] ); diff --git a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/DeleteButton.tsx b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/DeleteButton.tsx index 686970c0493e..5014584c3928 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/DeleteButton.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/DeleteButton.tsx @@ -47,8 +47,7 @@ async function deleteConfig( ) { try { await callApmApi({ - pathname: '/api/apm/settings/custom_links/{id}', - method: 'DELETE', + endpoint: 'DELETE /api/apm/settings/custom_links/{id}', params: { path: { id: customLinkId }, }, diff --git a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/LinkPreview.tsx b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/LinkPreview.tsx index b7250bda3096..25fd8f7ad3ca 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/LinkPreview.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/LinkPreview.tsx @@ -31,7 +31,7 @@ interface Props { const fetchTransaction = debounce( async (filters: Filter[], callback: (transaction: Transaction) => void) => { const transaction = await callApmApi({ - pathname: '/api/apm/settings/custom_links/transaction', + endpoint: 'GET /api/apm/settings/custom_links/transaction', params: { query: convertFiltersToQuery(filters) }, }); callback(transaction); diff --git a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/link_preview.test.tsx b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/link_preview.test.tsx index a2fd755b234f..3a2aa01ba3bc 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/link_preview.test.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/link_preview.test.tsx @@ -18,9 +18,9 @@ export const removeExternalLinkText = (str: string) => str.replace(/\(opens in a new tab or window\)/g, ''); describe('LinkPreview', () => { - let callApmApiSpy: jest.SpyInstance; + let callApmApiSpy: jest.SpyInstance; beforeAll(() => { - callApmApiSpy = jest.spyOn(apmApi, 'callApmApi').mockReturnValue({ + callApmApiSpy = jest.spyOn(apmApi, 'callApmApi').mockResolvedValue({ transaction: { id: 'foo' }, }); }); diff --git a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/saveCustomLink.ts b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/saveCustomLink.ts index 8ccd799b7cbc..cb1eaf6bca3f 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/saveCustomLink.ts +++ b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CustomLinkFlyout/saveCustomLink.ts @@ -34,8 +34,7 @@ export async function saveCustomLink({ if (id) { await callApmApi({ - pathname: '/api/apm/settings/custom_links/{id}', - method: 'PUT', + endpoint: 'PUT /api/apm/settings/custom_links/{id}', params: { path: { id }, body: customLink, @@ -43,8 +42,7 @@ export async function saveCustomLink({ }); } else { await callApmApi({ - pathname: '/api/apm/settings/custom_links', - method: 'POST', + endpoint: 'POST /api/apm/settings/custom_links', params: { body: customLink, }, diff --git a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.test.tsx b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.test.tsx index fea22e890dc1..a7feafad1111 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.test.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.test.tsx @@ -41,7 +41,7 @@ const data = [ describe('CustomLink', () => { beforeAll(() => { - jest.spyOn(apmApi, 'callApmApi').mockReturnValue({}); + jest.spyOn(apmApi, 'callApmApi').mockResolvedValue({}); }); afterAll(() => { jest.resetAllMocks(); diff --git a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.tsx index a7d7cf40ba84..d872f6d21ed9 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.tsx @@ -34,8 +34,9 @@ export function CustomLinkOverview() { CustomLink | undefined >(); - const { data: customLinks, status, refetch } = useFetcher( - (callApmApi) => callApmApi({ pathname: '/api/apm/settings/custom_links' }), + const { data: customLinks = [], status, refetch } = useFetcher( + (callApmApi) => + callApmApi({ endpoint: 'GET /api/apm/settings/custom_links' }), [] ); diff --git a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/add_environments.tsx b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/add_environments.tsx index a594edb32b08..ccc1778e9fbd 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/add_environments.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/add_environments.tsx @@ -42,7 +42,7 @@ export function AddEnvironments({ const { data = [], status } = useFetcher( (callApmApi) => callApmApi({ - pathname: `/api/apm/settings/anomaly-detection/environments`, + endpoint: `GET /api/apm/settings/anomaly-detection/environments`, }), [], { preservePreviousData: false } diff --git a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/create_jobs.ts b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/create_jobs.ts index 2e2c2ccbad7c..7106a4c48ef7 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/create_jobs.ts +++ b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/create_jobs.ts @@ -27,8 +27,7 @@ export async function createJobs({ }) { try { await callApmApi({ - pathname: '/api/apm/settings/anomaly-detection/jobs', - method: 'POST', + endpoint: 'POST /api/apm/settings/anomaly-detection/jobs', params: { body: { environments }, }, diff --git a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/index.tsx index acc1a1ba1614..debf3fa85d93 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/index.tsx @@ -18,8 +18,7 @@ import { useLicense } from '../../../../hooks/useLicense'; import { APIReturnType } from '../../../../services/rest/createCallApmApi'; export type AnomalyDetectionApiResponse = APIReturnType< - '/api/apm/settings/anomaly-detection', - 'GET' + 'GET /api/apm/settings/anomaly-detection' >; const DEFAULT_VALUE: AnomalyDetectionApiResponse = { @@ -38,7 +37,9 @@ export function AnomalyDetection() { const { refetch, data = DEFAULT_VALUE, status } = useFetcher( (callApmApi) => { if (canGetJobs) { - return callApmApi({ pathname: `/api/apm/settings/anomaly-detection` }); + return callApmApi({ + endpoint: `GET /api/apm/settings/anomaly-detection`, + }); } }, [canGetJobs], diff --git a/x-pack/plugins/apm/public/components/app/TraceLink/index.tsx b/x-pack/plugins/apm/public/components/app/TraceLink/index.tsx index ee3b0a33ebbc..1a41ffe1f606 100644 --- a/x-pack/plugins/apm/public/components/app/TraceLink/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TraceLink/index.tsx @@ -27,7 +27,7 @@ export function TraceLink({ match }: RouteComponentProps<{ traceId: string }>) { (callApmApi) => { if (traceId) { return callApmApi({ - pathname: '/api/apm/transaction/{traceId}', + endpoint: 'GET /api/apm/transaction/{traceId}', params: { path: { traceId, diff --git a/x-pack/plugins/apm/public/components/app/TraceOverview/index.tsx b/x-pack/plugins/apm/public/components/app/TraceOverview/index.tsx index a87bbdb926a2..cbab2c44132f 100644 --- a/x-pack/plugins/apm/public/components/app/TraceOverview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TraceOverview/index.tsx @@ -15,7 +15,7 @@ import { LocalUIFilters } from '../../shared/LocalUIFilters'; import { SearchBar } from '../../shared/search_bar'; import { TraceList } from './TraceList'; -type TracesAPIResponse = APIReturnType<'/api/apm/traces'>; +type TracesAPIResponse = APIReturnType<'GET /api/apm/traces'>; const DEFAULT_RESPONSE: TracesAPIResponse = { items: [], isAggregationAccurate: true, @@ -29,7 +29,7 @@ export function TraceOverview() { (callApmApi) => { if (start && end) { return callApmApi({ - pathname: '/api/apm/traces', + endpoint: 'GET /api/apm/traces', params: { query: { start, diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx index 7a5893314ddf..83f5f4deb89a 100644 --- a/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx @@ -47,7 +47,7 @@ export function ServiceInventory() { (callApmApi) => { if (start && end) { return callApmApi({ - pathname: '/api/apm/services', + endpoint: 'GET /api/apm/services', params: { query: { start, end, uiFilters: JSON.stringify(uiFilters) }, }, diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx index 34b934c41cca..82dbd6dd86aa 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx @@ -159,7 +159,7 @@ export function ServiceOverviewErrorsTable({ serviceName }: Props) { } return callApmApi({ - pathname: '/api/apm/services/{serviceName}/error_groups', + endpoint: 'GET /api/apm/services/{serviceName}/error_groups', params: { path: { serviceName }, query: { diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx index 3f72f07b2a7d..f5a57544209f 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx +++ b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx @@ -71,7 +71,7 @@ export function TransactionActionMenu({ transaction }: Props) { const { data: customLinks = [], status, refetch } = useFetcher( (callApmApi) => callApmApi({ - pathname: '/api/apm/settings/custom_links', + endpoint: 'GET /api/apm/settings/custom_links', params: { query: convertFiltersToQuery(filters) }, }), [filters] diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__test__/TransactionActionMenu.test.tsx b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__test__/TransactionActionMenu.test.tsx index ec0b473c3ade..9b5f00f76eeb 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__test__/TransactionActionMenu.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__test__/TransactionActionMenu.test.tsx @@ -245,7 +245,7 @@ describe('TransactionActionMenu component', () => { describe('Custom links', () => { beforeAll(() => { // Mocks callApmAPI because it's going to be used to fecth the transaction in the custom links flyout. - jest.spyOn(apmApi, 'callApmApi').mockReturnValue({}); + jest.spyOn(apmApi, 'callApmApi').mockResolvedValue({}); }); afterAll(() => { jest.resetAllMocks(); diff --git a/x-pack/plugins/apm/public/components/shared/charts/transaction_error_rate_chart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/transaction_error_rate_chart/index.tsx index 5b977b699161..dd9a1e2ec2ef 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/transaction_error_rate_chart/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/transaction_error_rate_chart/index.tsx @@ -44,8 +44,8 @@ export function TransactionErrorRateChart({ const { data, status } = useFetcher(() => { if (serviceName && start && end) { return callApmApi({ - pathname: - '/api/apm/services/{serviceName}/transaction_groups/error_rate', + endpoint: + 'GET /api/apm/services/{serviceName}/transaction_groups/error_rate', params: { path: { serviceName, diff --git a/x-pack/plugins/apm/public/context/charts_sync_context.tsx b/x-pack/plugins/apm/public/context/charts_sync_context.tsx index 6f69ae097828..282097fed246 100644 --- a/x-pack/plugins/apm/public/context/charts_sync_context.tsx +++ b/x-pack/plugins/apm/public/context/charts_sync_context.tsx @@ -34,7 +34,7 @@ export function LegacyChartsSyncContextProvider({ (callApmApi) => { if (start && end && serviceName) { return callApmApi({ - pathname: '/api/apm/services/{serviceName}/annotation/search', + endpoint: 'GET /api/apm/services/{serviceName}/annotation/search', params: { path: { serviceName, diff --git a/x-pack/plugins/apm/public/hooks/useAgentName.ts b/x-pack/plugins/apm/public/hooks/useAgentName.ts index 1f8a3b916ecd..b226971762fa 100644 --- a/x-pack/plugins/apm/public/hooks/useAgentName.ts +++ b/x-pack/plugins/apm/public/hooks/useAgentName.ts @@ -16,7 +16,7 @@ export function useAgentName() { (callApmApi) => { if (serviceName && start && end) { return callApmApi({ - pathname: '/api/apm/services/{serviceName}/agent_name', + endpoint: 'GET /api/apm/services/{serviceName}/agent_name', params: { path: { serviceName }, query: { start, end }, diff --git a/x-pack/plugins/apm/public/hooks/useAnomalyDetectionJobs.ts b/x-pack/plugins/apm/public/hooks/useAnomalyDetectionJobs.ts index 56c58bc82967..5bb36720e7b9 100644 --- a/x-pack/plugins/apm/public/hooks/useAnomalyDetectionJobs.ts +++ b/x-pack/plugins/apm/public/hooks/useAnomalyDetectionJobs.ts @@ -10,7 +10,7 @@ export function useAnomalyDetectionJobs() { return useFetcher( (callApmApi) => callApmApi({ - pathname: `/api/apm/settings/anomaly-detection`, + endpoint: `GET /api/apm/settings/anomaly-detection`, }), [], { showToastOnError: false } diff --git a/x-pack/plugins/apm/public/hooks/useDynamicIndexPattern.ts b/x-pack/plugins/apm/public/hooks/useDynamicIndexPattern.ts index 0b4978acdfcb..d0e12d853784 100644 --- a/x-pack/plugins/apm/public/hooks/useDynamicIndexPattern.ts +++ b/x-pack/plugins/apm/public/hooks/useDynamicIndexPattern.ts @@ -13,7 +13,7 @@ export function useDynamicIndexPattern( const { data, status } = useFetcher( (callApmApi) => { return callApmApi({ - pathname: '/api/apm/index_pattern/dynamic', + endpoint: 'GET /api/apm/index_pattern/dynamic', isCachable: true, params: { query: { diff --git a/x-pack/plugins/apm/public/hooks/useEnvironments.tsx b/x-pack/plugins/apm/public/hooks/useEnvironments.tsx index 9e01dde274ff..05ac780aefbd 100644 --- a/x-pack/plugins/apm/public/hooks/useEnvironments.tsx +++ b/x-pack/plugins/apm/public/hooks/useEnvironments.tsx @@ -35,7 +35,7 @@ export function useEnvironments({ const { data: environments = [], status = 'loading' } = useFetcher(() => { if (start && end) { return callApmApi({ - pathname: '/api/apm/ui_filters/environments', + endpoint: 'GET /api/apm/ui_filters/environments', params: { query: { start, diff --git a/x-pack/plugins/apm/public/hooks/useServiceMetricCharts.ts b/x-pack/plugins/apm/public/hooks/useServiceMetricCharts.ts index f4a981ff0975..d264ad6069db 100644 --- a/x-pack/plugins/apm/public/hooks/useServiceMetricCharts.ts +++ b/x-pack/plugins/apm/public/hooks/useServiceMetricCharts.ts @@ -26,7 +26,7 @@ export function useServiceMetricCharts( (callApmApi) => { if (serviceName && start && end && agentName) { return callApmApi({ - pathname: '/api/apm/services/{serviceName}/metrics/charts', + endpoint: 'GET /api/apm/services/{serviceName}/metrics/charts', params: { path: { serviceName }, query: { diff --git a/x-pack/plugins/apm/public/hooks/useServiceTransactionTypes.tsx b/x-pack/plugins/apm/public/hooks/useServiceTransactionTypes.tsx index 4e110ac2d438..5f778e3d8834 100644 --- a/x-pack/plugins/apm/public/hooks/useServiceTransactionTypes.tsx +++ b/x-pack/plugins/apm/public/hooks/useServiceTransactionTypes.tsx @@ -17,7 +17,7 @@ export function useServiceTransactionTypes(urlParams: IUrlParams) { (callApmApi) => { if (serviceName && start && end) { return callApmApi({ - pathname: '/api/apm/services/{serviceName}/transaction_types', + endpoint: 'GET /api/apm/services/{serviceName}/transaction_types', params: { path: { serviceName }, query: { start, end }, diff --git a/x-pack/plugins/apm/public/hooks/useTransactionBreakdown.ts b/x-pack/plugins/apm/public/hooks/useTransactionBreakdown.ts index 0705383ecb0c..148324768642 100644 --- a/x-pack/plugins/apm/public/hooks/useTransactionBreakdown.ts +++ b/x-pack/plugins/apm/public/hooks/useTransactionBreakdown.ts @@ -19,8 +19,8 @@ export function useTransactionBreakdown() { (callApmApi) => { if (serviceName && start && end && transactionType) { return callApmApi({ - pathname: - '/api/apm/services/{serviceName}/transaction_groups/breakdown', + endpoint: + 'GET /api/apm/services/{serviceName}/transaction_groups/breakdown', params: { path: { serviceName }, query: { diff --git a/x-pack/plugins/apm/public/hooks/useTransactionCharts.ts b/x-pack/plugins/apm/public/hooks/useTransactionCharts.ts index e66d70a53afa..78ea30f466cf 100644 --- a/x-pack/plugins/apm/public/hooks/useTransactionCharts.ts +++ b/x-pack/plugins/apm/public/hooks/useTransactionCharts.ts @@ -21,7 +21,8 @@ export function useTransactionCharts() { (callApmApi) => { if (serviceName && start && end) { return callApmApi({ - pathname: '/api/apm/services/{serviceName}/transaction_groups/charts', + endpoint: + 'GET /api/apm/services/{serviceName}/transaction_groups/charts', params: { path: { serviceName }, query: { diff --git a/x-pack/plugins/apm/public/hooks/useTransactionDistribution.ts b/x-pack/plugins/apm/public/hooks/useTransactionDistribution.ts index 8c76225d0348..36b5a7c00d4b 100644 --- a/x-pack/plugins/apm/public/hooks/useTransactionDistribution.ts +++ b/x-pack/plugins/apm/public/hooks/useTransactionDistribution.ts @@ -38,8 +38,8 @@ export function useTransactionDistribution(urlParams: IUrlParams) { async (callApmApi) => { if (serviceName && start && end && transactionType && transactionName) { const response = await callApmApi({ - pathname: - '/api/apm/services/{serviceName}/transaction_groups/distribution', + endpoint: + 'GET /api/apm/services/{serviceName}/transaction_groups/distribution', params: { path: { serviceName, diff --git a/x-pack/plugins/apm/public/hooks/useTransactionList.ts b/x-pack/plugins/apm/public/hooks/useTransactionList.ts index b2c2cc30f78e..e847309fd026 100644 --- a/x-pack/plugins/apm/public/hooks/useTransactionList.ts +++ b/x-pack/plugins/apm/public/hooks/useTransactionList.ts @@ -11,7 +11,7 @@ import { APIReturnType } from '../services/rest/createCallApmApi'; import { useFetcher } from './useFetcher'; type TransactionsAPIResponse = APIReturnType< - '/api/apm/services/{serviceName}/transaction_groups' + 'GET /api/apm/services/{serviceName}/transaction_groups' >; const DEFAULT_RESPONSE: Partial = { @@ -28,7 +28,7 @@ export function useTransactionList(urlParams: IUrlParams) { (callApmApi) => { if (serviceName && start && end && transactionType) { return callApmApi({ - pathname: '/api/apm/services/{serviceName}/transaction_groups', + endpoint: 'GET /api/apm/services/{serviceName}/transaction_groups', params: { path: { serviceName }, query: { diff --git a/x-pack/plugins/apm/public/hooks/useWaterfall.ts b/x-pack/plugins/apm/public/hooks/useWaterfall.ts index accc92da9ab0..6264ec45088a 100644 --- a/x-pack/plugins/apm/public/hooks/useWaterfall.ts +++ b/x-pack/plugins/apm/public/hooks/useWaterfall.ts @@ -21,7 +21,7 @@ export function useWaterfall(urlParams: IUrlParams) { (callApmApi) => { if (traceId && start && end) { return callApmApi({ - pathname: '/api/apm/traces/{traceId}', + endpoint: 'GET /api/apm/traces/{traceId}', params: { path: { traceId }, query: { diff --git a/x-pack/plugins/apm/public/hooks/use_annotations.ts b/x-pack/plugins/apm/public/hooks/use_annotations.ts index 2b1c2bec52b3..e8f6785706a9 100644 --- a/x-pack/plugins/apm/public/hooks/use_annotations.ts +++ b/x-pack/plugins/apm/public/hooks/use_annotations.ts @@ -19,7 +19,7 @@ export function useAnnotations() { const { data = INITIAL_STATE } = useFetcher(() => { if (start && end && serviceName) { return callApmApi({ - pathname: '/api/apm/services/{serviceName}/annotation/search', + endpoint: 'GET /api/apm/services/{serviceName}/annotation/search', params: { path: { serviceName, diff --git a/x-pack/plugins/apm/public/services/__test__/callApmApi.test.ts b/x-pack/plugins/apm/public/services/__test__/callApmApi.test.ts index 3fc673109026..2307ec9f06bb 100644 --- a/x-pack/plugins/apm/public/services/__test__/callApmApi.test.ts +++ b/x-pack/plugins/apm/public/services/__test__/callApmApi.test.ts @@ -23,7 +23,7 @@ describe('callApmApi', () => { it('should format the pathname with the given path params', async () => { await callApmApi({ - pathname: '/api/apm/{param1}/to/{param2}', + endpoint: 'GET /api/apm/{param1}/to/{param2}', params: { path: { param1: 'foo', @@ -42,7 +42,7 @@ describe('callApmApi', () => { it('should add the query parameters to the options object', async () => { await callApmApi({ - pathname: '/api/apm', + endpoint: 'GET /api/apm', params: { query: { foo: 'bar', @@ -65,8 +65,7 @@ describe('callApmApi', () => { it('should stringify the body and add it to the options object', async () => { await callApmApi({ - pathname: '/api/apm', - method: 'POST', + endpoint: 'POST /api/apm', params: { body: { foo: 'bar', diff --git a/x-pack/plugins/apm/public/services/rest/apm_observability_overview_fetchers.ts b/x-pack/plugins/apm/public/services/rest/apm_observability_overview_fetchers.ts index bc1db4eed1d9..a0ed51be685c 100644 --- a/x-pack/plugins/apm/public/services/rest/apm_observability_overview_fetchers.ts +++ b/x-pack/plugins/apm/public/services/rest/apm_observability_overview_fetchers.ts @@ -19,7 +19,7 @@ export const fetchObservabilityOverviewPageData = async ({ bucketSize, }: FetchDataParams): Promise => { const data = await callApmApi({ - pathname: '/api/apm/observability_overview', + endpoint: 'GET /api/apm/observability_overview', params: { query: { start: new Date(absoluteTime.start).toISOString(), @@ -58,6 +58,6 @@ export const fetchObservabilityOverviewPageData = async ({ export async function hasData() { return await callApmApi({ - pathname: '/api/apm/observability_overview/has_data', + endpoint: 'GET /api/apm/observability_overview/has_data', }); } diff --git a/x-pack/plugins/apm/public/services/rest/createCallApmApi.ts b/x-pack/plugins/apm/public/services/rest/createCallApmApi.ts index 08588bd03008..2760ed558865 100644 --- a/x-pack/plugins/apm/public/services/rest/createCallApmApi.ts +++ b/x-pack/plugins/apm/public/services/rest/createCallApmApi.ts @@ -9,10 +9,14 @@ import { callApi } from './callApi'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { APMAPI } from '../../../server/routes/create_apm_api'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { Client, HttpMethod } from '../../../server/routes/typings'; +import { Client } from '../../../server/routes/typings'; export type APMClient = Client; -export type APMClientOptions = Omit & { +export type APMClientOptions = Omit< + FetchOptions, + 'query' | 'body' | 'pathname' +> & { + endpoint: string; params?: { body?: any; query?: any; @@ -28,9 +32,10 @@ export let callApmApi: APMClient = () => { export function createCallApmApi(http: HttpSetup) { callApmApi = ((options: APMClientOptions) => { - const { pathname, params = {}, ...opts } = options; + const { endpoint, params = {}, ...opts } = options; const path = (params.path || {}) as Record; + const [method, pathname] = endpoint.split(' '); const formattedPathname = Object.keys(path).reduce((acc, paramName) => { return acc.replace(`{${paramName}}`, path[paramName]); @@ -38,6 +43,7 @@ export function createCallApmApi(http: HttpSetup) { return callApi(http, { ...opts, + method, pathname: formattedPathname, body: params.body, query: params.query, @@ -47,8 +53,7 @@ export function createCallApmApi(http: HttpSetup) { // infer return type from API export type APIReturnType< - TPath extends keyof APMAPI['_S'], - TMethod extends HttpMethod = 'GET' -> = APMAPI['_S'][TPath] extends { [key in TMethod]: { ret: any } } - ? APMAPI['_S'][TPath][TMethod]['ret'] + TPath extends keyof APMAPI['_S'] +> = APMAPI['_S'][TPath] extends { ret: any } + ? APMAPI['_S'][TPath]['ret'] : unknown; diff --git a/x-pack/plugins/apm/public/services/rest/index_pattern.ts b/x-pack/plugins/apm/public/services/rest/index_pattern.ts index 7c96b3773833..6ec542ab6baf 100644 --- a/x-pack/plugins/apm/public/services/rest/index_pattern.ts +++ b/x-pack/plugins/apm/public/services/rest/index_pattern.ts @@ -8,13 +8,12 @@ import { callApmApi } from './createCallApmApi'; export const createStaticIndexPattern = async () => { return await callApmApi({ - method: 'POST', - pathname: '/api/apm/index_pattern/static', + endpoint: 'POST /api/apm/index_pattern/static', }); }; export const getApmIndexPatternTitle = async () => { return await callApmApi({ - pathname: '/api/apm/index_pattern/title', + endpoint: 'GET /api/apm/index_pattern/title', }); }; diff --git a/x-pack/plugins/apm/server/lib/helpers/setup_request.ts b/x-pack/plugins/apm/server/lib/helpers/setup_request.ts index 65d36c8b36af..7e128493c873 100644 --- a/x-pack/plugins/apm/server/lib/helpers/setup_request.ts +++ b/x-pack/plugins/apm/server/lib/helpers/setup_request.ts @@ -10,7 +10,6 @@ import { APMConfig } from '../..'; import { KibanaRequest } from '../../../../../../src/core/server'; import { UI_SETTINGS } from '../../../../../../src/plugins/data/common'; import { ESFilter } from '../../../../../typings/elasticsearch'; -import { ProcessorEvent } from '../../../common/processor_event'; import { isActivePlatinumLicense } from '../../../common/service_map'; import { UIFilters } from '../../../typings/ui_filters'; import { APMRequestHandlerContext } from '../../routes/typings'; @@ -60,7 +59,6 @@ interface SetupRequestParams { */ end?: string; uiFilters?: string; - processorEvent?: ProcessorEvent; }; } diff --git a/x-pack/plugins/apm/server/routes/correlations.ts b/x-pack/plugins/apm/server/routes/correlations.ts index 5f8d2afd544f..19eb639a72bb 100644 --- a/x-pack/plugins/apm/server/routes/correlations.ts +++ b/x-pack/plugins/apm/server/routes/correlations.ts @@ -12,9 +12,9 @@ import { scoringRt } from '../lib/transaction_groups/correlations/scoring_rt'; import { createRoute } from './create_route'; import { setupRequest } from '../lib/helpers/setup_request'; -export const correlationsForSlowTransactionsRoute = createRoute(() => ({ - path: '/api/apm/correlations/slow_durations', - params: { +export const correlationsForSlowTransactionsRoute = createRoute({ + endpoint: 'GET /api/apm/correlations/slow_durations', + params: t.type({ query: t.intersection([ t.partial({ serviceName: t.string, @@ -29,7 +29,7 @@ export const correlationsForSlowTransactionsRoute = createRoute(() => ({ t.partial({ uiFilters: t.string }), rangeRt, ]), - }, + }), handler: async ({ context, request }) => { const setup = await setupRequest(context, request); const { @@ -51,11 +51,11 @@ export const correlationsForSlowTransactionsRoute = createRoute(() => ({ setup, }); }, -})); +}); -export const correlationsForRangesRoute = createRoute(() => ({ - path: '/api/apm/correlations/ranges', - params: { +export const correlationsForRangesRoute = createRoute({ + endpoint: 'GET /api/apm/correlations/ranges', + params: t.type({ query: t.intersection([ t.partial({ serviceName: t.string, @@ -70,7 +70,7 @@ export const correlationsForRangesRoute = createRoute(() => ({ t.partial({ uiFilters: t.string }), rangeRt, ]), - }, + }), handler: async ({ context, request }) => { const setup = await setupRequest(context, request); @@ -98,4 +98,4 @@ export const correlationsForRangesRoute = createRoute(() => ({ setup, }); }, -})); +}); diff --git a/x-pack/plugins/apm/server/routes/create_api/index.test.ts b/x-pack/plugins/apm/server/routes/create_api/index.test.ts index 3d3e26f680e0..32a5e5c5a5c8 100644 --- a/x-pack/plugins/apm/server/routes/create_api/index.test.ts +++ b/x-pack/plugins/apm/server/routes/create_api/index.test.ts @@ -6,9 +6,10 @@ import * as t from 'io-ts'; import { createApi } from './index'; import { CoreSetup, Logger } from 'src/core/server'; -import { Params } from '../typings'; +import { RouteParamsRT } from '../typings'; import { BehaviorSubject } from 'rxjs'; import { APMConfig } from '../..'; +import { jsonRt } from '../../../common/runtime_types/json_rt'; const getCoreMock = () => { const get = jest.fn(); @@ -51,30 +52,35 @@ describe('createApi', () => { createApi() .add(() => ({ - path: '/foo', + endpoint: 'GET /foo', handler: async () => null, })) .add(() => ({ - path: '/bar', - method: 'POST', - params: { + endpoint: 'POST /bar', + params: t.type({ body: t.string, - }, + }), handler: async () => null, })) .add(() => ({ - path: '/baz', - method: 'PUT', + endpoint: 'PUT /baz', options: { tags: ['access:apm', 'access:apm_write'], }, handler: async () => null, })) + .add({ + endpoint: 'GET /qux', + options: { + tags: ['access:apm', 'access:apm_write'], + }, + handler: async () => null, + }) .init(mock, context); expect(createRouter).toHaveBeenCalledTimes(1); - expect(get).toHaveBeenCalledTimes(1); + expect(get).toHaveBeenCalledTimes(2); expect(post).toHaveBeenCalledTimes(1); expect(put).toHaveBeenCalledTimes(1); @@ -86,6 +92,14 @@ describe('createApi', () => { validate: expect.anything(), }); + expect(get.mock.calls[1][0]).toEqual({ + options: { + tags: ['access:apm', 'access:apm_write'], + }, + path: '/qux', + validate: expect.anything(), + }); + expect(post.mock.calls[0][0]).toEqual({ options: { tags: ['access:apm'], @@ -104,18 +118,19 @@ describe('createApi', () => { }); describe('when validating', () => { - const initApi = (params: Params) => { + const initApi = (params?: RouteParamsRT) => { const { mock, context, createRouter, get, post } = getCoreMock(); const handlerMock = jest.fn(); createApi() .add(() => ({ - path: '/foo', + endpoint: 'GET /foo', params, handler: handlerMock, })) .init(mock, context); const routeHandler = get.mock.calls[0][1]; + const responseMock = { ok: jest.fn(), internalError: jest.fn(), @@ -142,16 +157,16 @@ describe('createApi', () => { }; it('adds a _debug query parameter by default', async () => { - const { simulate, handlerMock, responseMock } = initApi({}); + const { simulate, handlerMock, responseMock } = initApi(); await simulate({ query: { _debug: 'true' } }); + expect(responseMock.badRequest).not.toHaveBeenCalled(); + expect(handlerMock).toHaveBeenCalledTimes(1); expect(responseMock.ok).toHaveBeenCalled(); - expect(responseMock.badRequest).not.toHaveBeenCalled(); - const params = handlerMock.mock.calls[0][0].context.params; expect(params).toEqual({ @@ -170,7 +185,7 @@ describe('createApi', () => { }); it('throws if any parameters are used but no types are defined', async () => { - const { simulate, responseMock } = initApi({}); + const { simulate, responseMock } = initApi(); await simulate({ query: { @@ -197,11 +212,13 @@ describe('createApi', () => { }); it('validates path parameters', async () => { - const { simulate, handlerMock, responseMock } = initApi({ - path: t.type({ - foo: t.string, - }), - }); + const { simulate, handlerMock, responseMock } = initApi( + t.type({ + path: t.type({ + foo: t.string, + }), + }) + ); await simulate({ params: { @@ -252,17 +269,19 @@ describe('createApi', () => { }); it('validates body parameters', async () => { - const { simulate, handlerMock, responseMock } = initApi({ - body: t.string, - }); + const { simulate, handlerMock, responseMock } = initApi( + t.type({ + body: t.string, + }) + ); await simulate({ body: '', }); + expect(responseMock.badRequest).not.toHaveBeenCalled(); expect(handlerMock).toHaveBeenCalledTimes(1); expect(responseMock.ok).toHaveBeenCalledTimes(1); - expect(responseMock.badRequest).not.toHaveBeenCalled(); const params = handlerMock.mock.calls[0][0].context.params; @@ -281,20 +300,26 @@ describe('createApi', () => { }); it('validates query parameters', async () => { - const { simulate, handlerMock, responseMock } = initApi({ - query: t.type({ bar: t.string }), - }); + const { simulate, handlerMock, responseMock } = initApi( + t.type({ + query: t.type({ + bar: t.string, + filterNames: jsonRt.pipe(t.array(t.string)), + }), + }) + ); await simulate({ query: { bar: '', _debug: 'true', + filterNames: JSON.stringify(['hostName', 'agentName']), }, }); + expect(responseMock.badRequest).not.toHaveBeenCalled(); expect(handlerMock).toHaveBeenCalledTimes(1); expect(responseMock.ok).toHaveBeenCalledTimes(1); - expect(responseMock.badRequest).not.toHaveBeenCalled(); const params = handlerMock.mock.calls[0][0].context.params; @@ -302,6 +327,7 @@ describe('createApi', () => { query: { bar: '', _debug: true, + filterNames: ['hostName', 'agentName'], }, }); diff --git a/x-pack/plugins/apm/server/routes/create_api/index.ts b/x-pack/plugins/apm/server/routes/create_api/index.ts index cecb4f6ed336..25a074ea100e 100644 --- a/x-pack/plugins/apm/server/routes/create_api/index.ts +++ b/x-pack/plugins/apm/server/routes/create_api/index.ts @@ -3,31 +3,36 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { pick, difference } from 'lodash'; +import { merge as mergeLodash, pickBy, isEmpty, isPlainObject } from 'lodash'; import Boom from '@hapi/boom'; import { schema } from '@kbn/config-schema'; import * as t from 'io-ts'; import { PathReporter } from 'io-ts/lib/PathReporter'; import { isLeft } from 'fp-ts/lib/Either'; import { KibanaResponseFactory, RouteRegistrar } from 'src/core/server'; +import { merge } from '../../../common/runtime_types/merge'; +import { strictKeysRt } from '../../../common/runtime_types/strict_keys_rt'; import { APMConfig } from '../..'; -import { - ServerAPI, - RouteFactoryFn, - HttpMethod, - Route, - Params, -} from '../typings'; +import { ServerAPI } from '../typings'; import { jsonRt } from '../../../common/runtime_types/json_rt'; -const debugRt = t.partial({ _debug: jsonRt.pipe(t.boolean) }); +const debugRt = t.exact( + t.partial({ + query: t.exact(t.partial({ _debug: jsonRt.pipe(t.boolean) })), + }) +); + +type RouteOrRouteFactoryFn = Parameters['add']>[0]; + +const isNotEmpty = (val: any) => + val !== undefined && val !== null && !(isPlainObject(val) && isEmpty(val)); export function createApi() { - const factoryFns: Array> = []; + const routes: RouteOrRouteFactoryFn[] = []; const api: ServerAPI<{}> = { _S: {}, - add(fn) { - factoryFns.push(fn); + add(route) { + routes.push((route as unknown) as RouteOrRouteFactoryFn); return this as any; }, init(core, { config$, logger, plugins }) { @@ -39,41 +44,41 @@ export function createApi() { config = val; }); - factoryFns.forEach((fn) => { + routes.forEach((routeOrFactoryFn) => { + const route = + typeof routeOrFactoryFn === 'function' + ? routeOrFactoryFn(core) + : routeOrFactoryFn; + const { - params = {}, - path, + params, + endpoint, options = { tags: ['access:apm'] }, - method, handler, - } = fn(core) as Route; + } = route; - const routerMethod = (method || 'GET').toLowerCase() as + const [method, path] = endpoint.split(' '); + + const typedRouterMethod = method.trim().toLowerCase() as + | 'get' | 'post' | 'put' - | 'get' | 'delete'; + if (!['get', 'post', 'put', 'delete'].includes(typedRouterMethod)) { + throw new Error( + "Couldn't register route, as endpoint was not prefixed with a valid HTTP method" + ); + } + // For all runtime types with props, we create an exact // version that will strip all keys that are unvalidated. - const bodyRt = - params.body && 'props' in params.body - ? t.exact(params.body) - : params.body; - - const rts = { - // Add _debug query parameter to all routes - query: params.query - ? t.exact(t.intersection([params.query, debugRt])) - : t.exact(debugRt), - path: params.path ? t.exact(params.path) : t.strict({}), - body: bodyRt || t.null, - }; + const paramsRt = params ? merge([params, debugRt]) : debugRt; const anyObject = schema.object({}, { unknowns: 'allow' }); - (router[routerMethod] as RouteRegistrar)( + (router[typedRouterMethod] as RouteRegistrar)( { path, options, @@ -89,49 +94,23 @@ export function createApi() { }, async (context, request, response) => { try { - const paramMap = { - path: request.params, - body: request.body, - query: { - _debug: 'false', - ...request.query, + const paramMap = pickBy( + { + path: request.params, + body: request.body, + query: { + _debug: 'false', + ...request.query, + }, }, - }; - - const parsedParams = (Object.keys(rts) as Array< - keyof typeof rts - >).reduce((acc, key) => { - const codec = rts[key]; - const value = paramMap[key]; - - const result = codec.decode(value); - - if (isLeft(result)) { - throw Boom.badRequest(PathReporter.report(result)[0]); - } - - // `io-ts` has stripped unvalidated keys, so we can compare - // the output with the input to see if all object keys are - // known and validated. - const strippedKeys = difference( - Object.keys(value || {}), - Object.keys(result.right || {}) - ); - - if (strippedKeys.length) { - throw Boom.badRequest( - `Unknown keys specified: ${strippedKeys}` - ); - } - - const parsedValue = result.right; - - return { - ...acc, - [key]: parsedValue, - }; - }, {} as Record); + isNotEmpty + ); + const result = strictKeysRt(paramsRt).decode(paramMap); + + if (isLeft(result)) { + throw Boom.badRequest(PathReporter.report(result)[0]); + } const data = await handler({ request, context: { @@ -140,14 +119,16 @@ export function createApi() { // Only return values for parameters that have runtime types, // but always include query as _debug is always set even if // it's not defined in the route. - // @ts-expect-error - params: pick(parsedParams, ...Object.keys(params), 'query'), + params: mergeLodash( + { query: { _debug: false } }, + pickBy(result.right, isNotEmpty) + ), config, logger, }, }); - return response.ok({ body: data }); + return response.ok({ body: data as any }); } catch (error) { if (Boom.isBoom(error)) { return convertBoomToKibanaResponse(error, response); diff --git a/x-pack/plugins/apm/server/routes/create_apm_api.ts b/x-pack/plugins/apm/server/routes/create_apm_api.ts index 34551c35ee23..a272b448deaf 100644 --- a/x-pack/plugins/apm/server/routes/create_apm_api.ts +++ b/x-pack/plugins/apm/server/routes/create_apm_api.ts @@ -3,12 +3,12 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - import { staticIndexPatternRoute, dynamicIndexPatternRoute, apmIndexPatternTitleRoute, } from './index_pattern'; +import { createApi } from './create_api'; import { errorDistributionRoute, errorGroupsRoute, @@ -65,7 +65,6 @@ import { uiFiltersEnvironmentsRoute, rumOverviewLocalFiltersRoute, } from './ui_filters'; -import { createApi } from './create_api'; import { serviceMapRoute, serviceMapServiceNodeRoute } from './service_map'; import { createCustomLinkRoute, diff --git a/x-pack/plugins/apm/server/routes/create_route.ts b/x-pack/plugins/apm/server/routes/create_route.ts index 892f4ec40de7..0d222f9f3049 100644 --- a/x-pack/plugins/apm/server/routes/create_route.ts +++ b/x-pack/plugins/apm/server/routes/create_route.ts @@ -3,13 +3,26 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { RouteFactoryFn, HttpMethod, Params } from './typings'; + +import { CoreSetup } from 'src/core/server'; +import { Route, RouteParamsRT } from './typings'; + +export function createRoute< + TEndpoint extends string, + TRouteParamsRT extends RouteParamsRT | undefined = undefined, + TReturn = unknown +>( + route: Route +): Route; export function createRoute< - TName extends string, - TReturn, - TMethod extends HttpMethod = 'GET', - TParams extends Params = {} ->(fn: RouteFactoryFn) { - return fn; + TEndpoint extends string, + TRouteParamsRT extends RouteParamsRT | undefined = undefined, + TReturn = unknown +>( + route: (core: CoreSetup) => Route +): (core: CoreSetup) => Route; + +export function createRoute(routeOrFactoryFn: Function | object) { + return routeOrFactoryFn; } diff --git a/x-pack/plugins/apm/server/routes/errors.ts b/x-pack/plugins/apm/server/routes/errors.ts index 1615550027d3..189a18698b56 100644 --- a/x-pack/plugins/apm/server/routes/errors.ts +++ b/x-pack/plugins/apm/server/routes/errors.ts @@ -12,9 +12,9 @@ import { getErrorGroups } from '../lib/errors/get_error_groups'; import { setupRequest } from '../lib/helpers/setup_request'; import { uiFiltersRt, rangeRt } from './default_api_types'; -export const errorsRoute = createRoute(() => ({ - path: '/api/apm/services/{serviceName}/errors', - params: { +export const errorsRoute = createRoute({ + endpoint: 'GET /api/apm/services/{serviceName}/errors', + params: t.type({ path: t.type({ serviceName: t.string, }), @@ -26,7 +26,7 @@ export const errorsRoute = createRoute(() => ({ uiFiltersRt, rangeRt, ]), - }, + }), handler: async ({ context, request }) => { const setup = await setupRequest(context, request); const { params } = context; @@ -40,27 +40,27 @@ export const errorsRoute = createRoute(() => ({ setup, }); }, -})); +}); -export const errorGroupsRoute = createRoute(() => ({ - path: '/api/apm/services/{serviceName}/errors/{groupId}', - params: { +export const errorGroupsRoute = createRoute({ + endpoint: 'GET /api/apm/services/{serviceName}/errors/{groupId}', + params: t.type({ path: t.type({ serviceName: t.string, groupId: t.string, }), query: t.intersection([uiFiltersRt, rangeRt]), - }, + }), handler: async ({ context, request }) => { const setup = await setupRequest(context, request); const { serviceName, groupId } = context.params.path; return getErrorGroup({ serviceName, groupId, setup }); }, -})); +}); -export const errorDistributionRoute = createRoute(() => ({ - path: '/api/apm/services/{serviceName}/errors/distribution', - params: { +export const errorDistributionRoute = createRoute({ + endpoint: 'GET /api/apm/services/{serviceName}/errors/distribution', + params: t.type({ path: t.type({ serviceName: t.string, }), @@ -71,7 +71,7 @@ export const errorDistributionRoute = createRoute(() => ({ uiFiltersRt, rangeRt, ]), - }, + }), handler: async ({ context, request }) => { const setup = await setupRequest(context, request); const { params } = context; @@ -79,4 +79,4 @@ export const errorDistributionRoute = createRoute(() => ({ const { groupId } = params.query; return getErrorDistribution({ serviceName, groupId, setup }); }, -})); +}); diff --git a/x-pack/plugins/apm/server/routes/index_pattern.ts b/x-pack/plugins/apm/server/routes/index_pattern.ts index 18bc2986d406..5b9b211032bf 100644 --- a/x-pack/plugins/apm/server/routes/index_pattern.ts +++ b/x-pack/plugins/apm/server/routes/index_pattern.ts @@ -11,13 +11,14 @@ import { getInternalSavedObjectsClient } from '../lib/helpers/get_internal_saved import { getApmIndexPatternTitle } from '../lib/index_pattern/get_apm_index_pattern_title'; import { getDynamicIndexPattern } from '../lib/index_pattern/get_dynamic_index_pattern'; import { getApmIndices } from '../lib/settings/apm_indices/get_apm_indices'; +import { UIProcessorEvent } from '../../common/processor_event'; export const staticIndexPatternRoute = createRoute((core) => ({ - method: 'POST', - path: '/api/apm/index_pattern/static', + endpoint: 'POST /api/apm/index_pattern/static', handler: async ({ context, request }) => { const setup = await setupRequest(context, request); const savedObjectsClient = await getInternalSavedObjectsClient(core); + await createStaticIndexPattern(setup, context, savedObjectsClient); // send empty response regardless of outcome @@ -25,9 +26,9 @@ export const staticIndexPatternRoute = createRoute((core) => ({ }, })); -export const dynamicIndexPatternRoute = createRoute(() => ({ - path: '/api/apm/index_pattern/dynamic', - params: { +export const dynamicIndexPatternRoute = createRoute({ + endpoint: 'GET /api/apm/index_pattern/dynamic', + params: t.partial({ query: t.partial({ processorEvent: t.union([ t.literal('transaction'), @@ -35,25 +36,30 @@ export const dynamicIndexPatternRoute = createRoute(() => ({ t.literal('error'), ]), }), - }, + }), handler: async ({ context }) => { const indices = await getApmIndices({ config: context.config, savedObjectsClient: context.core.savedObjects.client, }); + const processorEvent = context.params.query.processorEvent as + | UIProcessorEvent + | undefined; + const dynamicIndexPattern = await getDynamicIndexPattern({ context, indices, + processorEvent, }); return { dynamicIndexPattern }; }, -})); +}); -export const apmIndexPatternTitleRoute = createRoute(() => ({ - path: '/api/apm/index_pattern/title', +export const apmIndexPatternTitleRoute = createRoute({ + endpoint: 'GET /api/apm/index_pattern/title', handler: async ({ context }) => { return getApmIndexPatternTitle(context); }, -})); +}); diff --git a/x-pack/plugins/apm/server/routes/metrics.ts b/x-pack/plugins/apm/server/routes/metrics.ts index fabd98c71956..82697a78b424 100644 --- a/x-pack/plugins/apm/server/routes/metrics.ts +++ b/x-pack/plugins/apm/server/routes/metrics.ts @@ -10,9 +10,9 @@ import { getMetricsChartDataByAgent } from '../lib/metrics/get_metrics_chart_dat import { createRoute } from './create_route'; import { uiFiltersRt, rangeRt } from './default_api_types'; -export const metricsChartsRoute = createRoute(() => ({ - path: `/api/apm/services/{serviceName}/metrics/charts`, - params: { +export const metricsChartsRoute = createRoute({ + endpoint: `GET /api/apm/services/{serviceName}/metrics/charts`, + params: t.type({ path: t.type({ serviceName: t.string, }), @@ -26,7 +26,7 @@ export const metricsChartsRoute = createRoute(() => ({ uiFiltersRt, rangeRt, ]), - }, + }), handler: async ({ context, request }) => { const setup = await setupRequest(context, request); const { params } = context; @@ -39,4 +39,4 @@ export const metricsChartsRoute = createRoute(() => ({ serviceNodeName, }); }, -})); +}); diff --git a/x-pack/plugins/apm/server/routes/observability_overview.ts b/x-pack/plugins/apm/server/routes/observability_overview.ts index 498e8b4792de..e6d6bc8157a3 100644 --- a/x-pack/plugins/apm/server/routes/observability_overview.ts +++ b/x-pack/plugins/apm/server/routes/observability_overview.ts @@ -12,19 +12,19 @@ import { createRoute } from './create_route'; import { rangeRt } from './default_api_types'; import { getSearchAggregatedTransactions } from '../lib/helpers/aggregated_transactions'; -export const observabilityOverviewHasDataRoute = createRoute(() => ({ - path: '/api/apm/observability_overview/has_data', +export const observabilityOverviewHasDataRoute = createRoute({ + endpoint: 'GET /api/apm/observability_overview/has_data', handler: async ({ context, request }) => { const setup = await setupRequest(context, request); return await hasData({ setup }); }, -})); +}); -export const observabilityOverviewRoute = createRoute(() => ({ - path: '/api/apm/observability_overview', - params: { +export const observabilityOverviewRoute = createRoute({ + endpoint: 'GET /api/apm/observability_overview', + params: t.type({ query: t.intersection([rangeRt, t.type({ bucketSize: t.string })]), - }, + }), handler: async ({ context, request }) => { const setup = await setupRequest(context, request); const { bucketSize } = context.params.query; @@ -48,4 +48,4 @@ export const observabilityOverviewRoute = createRoute(() => ({ ]); return { serviceCount, transactionCoordinates }; }, -})); +}); diff --git a/x-pack/plugins/apm/server/routes/rum_client.ts b/x-pack/plugins/apm/server/routes/rum_client.ts index cfa6eb289688..ead774c0c791 100644 --- a/x-pack/plugins/apm/server/routes/rum_client.ts +++ b/x-pack/plugins/apm/server/routes/rum_client.ts @@ -31,11 +31,11 @@ const uxQueryRt = t.intersection([ t.partial({ urlQuery: t.string, percentile: t.string }), ]); -export const rumClientMetricsRoute = createRoute(() => ({ - path: '/api/apm/rum/client-metrics', - params: { +export const rumClientMetricsRoute = createRoute({ + endpoint: 'GET /api/apm/rum/client-metrics', + params: t.type({ query: uxQueryRt, - }, + }), handler: async ({ context, request }) => { const setup = await setupRequest(context, request); @@ -49,13 +49,13 @@ export const rumClientMetricsRoute = createRoute(() => ({ percentile: percentile ? Number(percentile) : undefined, }); }, -})); +}); -export const rumPageLoadDistributionRoute = createRoute(() => ({ - path: '/api/apm/rum-client/page-load-distribution', - params: { +export const rumPageLoadDistributionRoute = createRoute({ + endpoint: 'GET /api/apm/rum-client/page-load-distribution', + params: t.type({ query: t.intersection([uxQueryRt, percentileRangeRt]), - }, + }), handler: async ({ context, request }) => { const setup = await setupRequest(context, request); @@ -70,17 +70,17 @@ export const rumPageLoadDistributionRoute = createRoute(() => ({ urlQuery, }); }, -})); +}); -export const rumPageLoadDistBreakdownRoute = createRoute(() => ({ - path: '/api/apm/rum-client/page-load-distribution/breakdown', - params: { +export const rumPageLoadDistBreakdownRoute = createRoute({ + endpoint: 'GET /api/apm/rum-client/page-load-distribution/breakdown', + params: t.type({ query: t.intersection([ uxQueryRt, percentileRangeRt, t.type({ breakdown: t.string }), ]), - }, + }), handler: async ({ context, request }) => { const setup = await setupRequest(context, request); @@ -96,13 +96,13 @@ export const rumPageLoadDistBreakdownRoute = createRoute(() => ({ urlQuery, }); }, -})); +}); -export const rumPageViewsTrendRoute = createRoute(() => ({ - path: '/api/apm/rum-client/page-view-trends', - params: { +export const rumPageViewsTrendRoute = createRoute({ + endpoint: 'GET /api/apm/rum-client/page-view-trends', + params: t.type({ query: t.intersection([uxQueryRt, t.partial({ breakdowns: t.string })]), - }, + }), handler: async ({ context, request }) => { const setup = await setupRequest(context, request); @@ -116,25 +116,25 @@ export const rumPageViewsTrendRoute = createRoute(() => ({ urlQuery, }); }, -})); +}); -export const rumServicesRoute = createRoute(() => ({ - path: '/api/apm/rum-client/services', - params: { +export const rumServicesRoute = createRoute({ + endpoint: 'GET /api/apm/rum-client/services', + params: t.type({ query: t.intersection([uiFiltersRt, rangeRt]), - }, + }), handler: async ({ context, request }) => { const setup = await setupRequest(context, request); return getRumServices({ setup }); }, -})); +}); -export const rumVisitorsBreakdownRoute = createRoute(() => ({ - path: '/api/apm/rum-client/visitor-breakdown', - params: { +export const rumVisitorsBreakdownRoute = createRoute({ + endpoint: 'GET /api/apm/rum-client/visitor-breakdown', + params: t.type({ query: uxQueryRt, - }, + }), handler: async ({ context, request }) => { const setup = await setupRequest(context, request); @@ -147,13 +147,13 @@ export const rumVisitorsBreakdownRoute = createRoute(() => ({ urlQuery, }); }, -})); +}); -export const rumWebCoreVitals = createRoute(() => ({ - path: '/api/apm/rum-client/web-core-vitals', - params: { +export const rumWebCoreVitals = createRoute({ + endpoint: 'GET /api/apm/rum-client/web-core-vitals', + params: t.type({ query: uxQueryRt, - }, + }), handler: async ({ context, request }) => { const setup = await setupRequest(context, request); @@ -167,13 +167,13 @@ export const rumWebCoreVitals = createRoute(() => ({ percentile: percentile ? Number(percentile) : undefined, }); }, -})); +}); -export const rumLongTaskMetrics = createRoute(() => ({ - path: '/api/apm/rum-client/long-task-metrics', - params: { +export const rumLongTaskMetrics = createRoute({ + endpoint: 'GET /api/apm/rum-client/long-task-metrics', + params: t.type({ query: uxQueryRt, - }, + }), handler: async ({ context, request }) => { const setup = await setupRequest(context, request); @@ -187,13 +187,13 @@ export const rumLongTaskMetrics = createRoute(() => ({ percentile: percentile ? Number(percentile) : undefined, }); }, -})); +}); -export const rumUrlSearch = createRoute(() => ({ - path: '/api/apm/rum-client/url-search', - params: { +export const rumUrlSearch = createRoute({ + endpoint: 'GET /api/apm/rum-client/url-search', + params: t.type({ query: uxQueryRt, - }, + }), handler: async ({ context, request }) => { const setup = await setupRequest(context, request); @@ -203,18 +203,18 @@ export const rumUrlSearch = createRoute(() => ({ return getUrlSearch({ setup, urlQuery, percentile: Number(percentile) }); }, -})); +}); -export const rumJSErrors = createRoute(() => ({ - path: '/api/apm/rum-client/js-errors', - params: { +export const rumJSErrors = createRoute({ + endpoint: 'GET /api/apm/rum-client/js-errors', + params: t.type({ query: t.intersection([ uiFiltersRt, rangeRt, t.type({ pageSize: t.string, pageIndex: t.string }), t.partial({ urlQuery: t.string }), ]), - }, + }), handler: async ({ context, request }) => { const setup = await setupRequest(context, request); @@ -229,15 +229,15 @@ export const rumJSErrors = createRoute(() => ({ pageIndex: Number(pageIndex), }); }, -})); +}); -export const rumHasDataRoute = createRoute(() => ({ - path: '/api/apm/observability_overview/has_rum_data', - params: { +export const rumHasDataRoute = createRoute({ + endpoint: 'GET /api/apm/observability_overview/has_rum_data', + params: t.type({ query: t.intersection([uiFiltersRt, rangeRt]), - }, + }), handler: async ({ context, request }) => { const setup = await setupRequest(context, request); return await hasRumData({ setup }); }, -})); +}); diff --git a/x-pack/plugins/apm/server/routes/service_map.ts b/x-pack/plugins/apm/server/routes/service_map.ts index ffc8cb84b690..2ad9d97130d1 100644 --- a/x-pack/plugins/apm/server/routes/service_map.ts +++ b/x-pack/plugins/apm/server/routes/service_map.ts @@ -18,9 +18,9 @@ import { rangeRt, uiFiltersRt } from './default_api_types'; import { notifyFeatureUsage } from '../feature'; import { getSearchAggregatedTransactions } from '../lib/helpers/aggregated_transactions'; -export const serviceMapRoute = createRoute(() => ({ - path: '/api/apm/service-map', - params: { +export const serviceMapRoute = createRoute({ + endpoint: 'GET /api/apm/service-map', + params: t.type({ query: t.intersection([ t.partial({ environment: t.string, @@ -28,7 +28,7 @@ export const serviceMapRoute = createRoute(() => ({ }), rangeRt, ]), - }, + }), handler: async ({ context, request }) => { if (!context.config['xpack.apm.serviceMapEnabled']) { throw Boom.notFound(); @@ -59,16 +59,16 @@ export const serviceMapRoute = createRoute(() => ({ logger, }); }, -})); +}); -export const serviceMapServiceNodeRoute = createRoute(() => ({ - path: `/api/apm/service-map/service/{serviceName}`, - params: { +export const serviceMapServiceNodeRoute = createRoute({ + endpoint: `GET /api/apm/service-map/service/{serviceName}`, + params: t.type({ path: t.type({ serviceName: t.string, }), query: t.intersection([rangeRt, uiFiltersRt]), - }, + }), handler: async ({ context, request }) => { if (!context.config['xpack.apm.serviceMapEnabled']) { throw Boom.notFound(); @@ -92,4 +92,4 @@ export const serviceMapServiceNodeRoute = createRoute(() => ({ searchAggregatedTransactions, }); }, -})); +}); diff --git a/x-pack/plugins/apm/server/routes/service_nodes.ts b/x-pack/plugins/apm/server/routes/service_nodes.ts index 872140767182..df01a034b06c 100644 --- a/x-pack/plugins/apm/server/routes/service_nodes.ts +++ b/x-pack/plugins/apm/server/routes/service_nodes.ts @@ -9,14 +9,14 @@ import { setupRequest } from '../lib/helpers/setup_request'; import { getServiceNodes } from '../lib/service_nodes'; import { rangeRt, uiFiltersRt } from './default_api_types'; -export const serviceNodesRoute = createRoute(() => ({ - path: '/api/apm/services/{serviceName}/serviceNodes', - params: { +export const serviceNodesRoute = createRoute({ + endpoint: 'GET /api/apm/services/{serviceName}/serviceNodes', + params: t.type({ path: t.type({ serviceName: t.string, }), query: t.intersection([rangeRt, uiFiltersRt]), - }, + }), handler: async ({ context, request }) => { const setup = await setupRequest(context, request); const { params } = context; @@ -27,4 +27,4 @@ export const serviceNodesRoute = createRoute(() => ({ serviceName, }); }, -})); +}); diff --git a/x-pack/plugins/apm/server/routes/services.ts b/x-pack/plugins/apm/server/routes/services.ts index ada1674d4555..10af35df4b0e 100644 --- a/x-pack/plugins/apm/server/routes/services.ts +++ b/x-pack/plugins/apm/server/routes/services.ts @@ -20,11 +20,11 @@ import { getSearchAggregatedTransactions } from '../lib/helpers/aggregated_trans import { getServiceErrorGroups } from '../lib/services/get_service_error_groups'; import { toNumberRt } from '../../common/runtime_types/to_number_rt'; -export const servicesRoute = createRoute(() => ({ - path: '/api/apm/services', - params: { +export const servicesRoute = createRoute({ + endpoint: 'GET /api/apm/services', + params: t.type({ query: t.intersection([uiFiltersRt, rangeRt]), - }, + }), handler: async ({ context, request }) => { const setup = await setupRequest(context, request); @@ -40,16 +40,16 @@ export const servicesRoute = createRoute(() => ({ return services; }, -})); +}); -export const serviceAgentNameRoute = createRoute(() => ({ - path: '/api/apm/services/{serviceName}/agent_name', - params: { +export const serviceAgentNameRoute = createRoute({ + endpoint: 'GET /api/apm/services/{serviceName}/agent_name', + params: t.type({ path: t.type({ serviceName: t.string, }), query: rangeRt, - }, + }), handler: async ({ context, request }) => { const setup = await setupRequest(context, request); const { serviceName } = context.params.path; @@ -63,16 +63,16 @@ export const serviceAgentNameRoute = createRoute(() => ({ searchAggregatedTransactions, }); }, -})); +}); -export const serviceTransactionTypesRoute = createRoute(() => ({ - path: '/api/apm/services/{serviceName}/transaction_types', - params: { +export const serviceTransactionTypesRoute = createRoute({ + endpoint: 'GET /api/apm/services/{serviceName}/transaction_types', + params: t.type({ path: t.type({ serviceName: t.string, }), query: rangeRt, - }, + }), handler: async ({ context, request }) => { const setup = await setupRequest(context, request); const { serviceName } = context.params.path; @@ -84,27 +84,28 @@ export const serviceTransactionTypesRoute = createRoute(() => ({ ), }); }, -})); +}); -export const serviceNodeMetadataRoute = createRoute(() => ({ - path: '/api/apm/services/{serviceName}/node/{serviceNodeName}/metadata', - params: { +export const serviceNodeMetadataRoute = createRoute({ + endpoint: + 'GET /api/apm/services/{serviceName}/node/{serviceNodeName}/metadata', + params: t.type({ path: t.type({ serviceName: t.string, serviceNodeName: t.string, }), query: t.intersection([uiFiltersRt, rangeRt]), - }, + }), handler: async ({ context, request }) => { const setup = await setupRequest(context, request); const { serviceName, serviceNodeName } = context.params.path; return getServiceNodeMetadata({ setup, serviceName, serviceNodeName }); }, -})); +}); -export const serviceAnnotationsRoute = createRoute(() => ({ - path: '/api/apm/services/{serviceName}/annotation/search', - params: { +export const serviceAnnotationsRoute = createRoute({ + endpoint: 'GET /api/apm/services/{serviceName}/annotation/search', + params: t.type({ path: t.type({ serviceName: t.string, }), @@ -114,7 +115,7 @@ export const serviceAnnotationsRoute = createRoute(() => ({ environment: t.string, }), ]), - }, + }), handler: async ({ context, request }) => { const setup = await setupRequest(context, request); const { serviceName } = context.params.path; @@ -141,15 +142,14 @@ export const serviceAnnotationsRoute = createRoute(() => ({ logger: context.logger, }); }, -})); +}); -export const serviceAnnotationsCreateRoute = createRoute(() => ({ - path: '/api/apm/services/{serviceName}/annotation', - method: 'POST', +export const serviceAnnotationsCreateRoute = createRoute({ + endpoint: 'POST /api/apm/services/{serviceName}/annotation', options: { tags: ['access:apm', 'access:apm_write'], }, - params: { + params: t.type({ path: t.type({ serviceName: t.string, }), @@ -170,7 +170,7 @@ export const serviceAnnotationsCreateRoute = createRoute(() => ({ tags: t.array(t.string), }), ]), - }, + }), handler: async ({ request, context }) => { const annotationsClient = await context.plugins.observability?.getScopedAnnotationsClient( context, @@ -196,11 +196,11 @@ export const serviceAnnotationsCreateRoute = createRoute(() => ({ tags: uniq(['apm'].concat(body.tags ?? [])), }); }, -})); +}); -export const serviceErrorGroupsRoute = createRoute(() => ({ - path: '/api/apm/services/{serviceName}/error_groups', - params: { +export const serviceErrorGroupsRoute = createRoute({ + endpoint: 'GET /api/apm/services/{serviceName}/error_groups', + params: t.type({ path: t.type({ serviceName: t.string, }), @@ -219,7 +219,7 @@ export const serviceErrorGroupsRoute = createRoute(() => ({ ]), }), ]), - }, + }), handler: async ({ context, request }) => { const setup = await setupRequest(context, request); @@ -238,4 +238,4 @@ export const serviceErrorGroupsRoute = createRoute(() => ({ sortField, }); }, -})); +}); diff --git a/x-pack/plugins/apm/server/routes/settings/agent_configuration.ts b/x-pack/plugins/apm/server/routes/settings/agent_configuration.ts index 7ed5ef442b6f..942fef5b559b 100644 --- a/x-pack/plugins/apm/server/routes/settings/agent_configuration.ts +++ b/x-pack/plugins/apm/server/routes/settings/agent_configuration.ts @@ -25,20 +25,20 @@ import { jsonRt } from '../../../common/runtime_types/json_rt'; import { getSearchAggregatedTransactions } from '../../lib/helpers/aggregated_transactions'; // get list of configurations -export const agentConfigurationRoute = createRoute(() => ({ - path: '/api/apm/settings/agent-configuration', +export const agentConfigurationRoute = createRoute({ + endpoint: 'GET /api/apm/settings/agent-configuration', handler: async ({ context, request }) => { const setup = await setupRequest(context, request); return await listConfigurations({ setup }); }, -})); +}); // get a single configuration -export const getSingleAgentConfigurationRoute = createRoute(() => ({ - path: '/api/apm/settings/agent-configuration/view', - params: { +export const getSingleAgentConfigurationRoute = createRoute({ + endpoint: 'GET /api/apm/settings/agent-configuration/view', + params: t.partial({ query: serviceRt, - }, + }), handler: async ({ context, request }) => { const setup = await setupRequest(context, request); const { name, environment } = context.params.query; @@ -56,20 +56,19 @@ export const getSingleAgentConfigurationRoute = createRoute(() => ({ return config._source; }, -})); +}); // delete configuration -export const deleteAgentConfigurationRoute = createRoute(() => ({ - method: 'DELETE', - path: '/api/apm/settings/agent-configuration', +export const deleteAgentConfigurationRoute = createRoute({ + endpoint: 'DELETE /api/apm/settings/agent-configuration', options: { tags: ['access:apm', 'access:apm_write'], }, - params: { + params: t.type({ body: t.type({ service: serviceRt, }), - }, + }), handler: async ({ context, request }) => { const setup = await setupRequest(context, request); const { service } = context.params.body; @@ -92,19 +91,18 @@ export const deleteAgentConfigurationRoute = createRoute(() => ({ setup, }); }, -})); +}); // create/update configuration -export const createOrUpdateAgentConfigurationRoute = createRoute(() => ({ - method: 'PUT', - path: '/api/apm/settings/agent-configuration', +export const createOrUpdateAgentConfigurationRoute = createRoute({ + endpoint: 'PUT /api/apm/settings/agent-configuration', options: { tags: ['access:apm', 'access:apm_write'], }, - params: { - query: t.partial({ overwrite: jsonRt.pipe(t.boolean) }), - body: agentConfigurationIntakeRt, - }, + params: t.intersection([ + t.partial({ query: t.partial({ overwrite: jsonRt.pipe(t.boolean) }) }), + t.type({ body: agentConfigurationIntakeRt }), + ]), handler: async ({ context, request }) => { const setup = await setupRequest(context, request); const { body, query } = context.params; @@ -135,7 +133,7 @@ export const createOrUpdateAgentConfigurationRoute = createRoute(() => ({ setup, }); }, -})); +}); const searchParamsRt = t.intersection([ t.type({ service: serviceRt }), @@ -145,12 +143,11 @@ const searchParamsRt = t.intersection([ export type AgentConfigSearchParams = t.TypeOf; // Lookup single configuration (used by APM Server) -export const agentConfigurationSearchRoute = createRoute(() => ({ - method: 'POST', - path: '/api/apm/settings/agent-configuration/search', - params: { +export const agentConfigurationSearchRoute = createRoute({ + endpoint: 'POST /api/apm/settings/agent-configuration/search', + params: t.type({ body: searchParamsRt, - }, + }), handler: async ({ context, request }) => { const { service, @@ -188,16 +185,15 @@ export const agentConfigurationSearchRoute = createRoute(() => ({ return config; }, -})); +}); /* * Utility endpoints (not documented as part of the public API) */ // get list of services -export const listAgentConfigurationServicesRoute = createRoute(() => ({ - method: 'GET', - path: '/api/apm/settings/agent-configuration/services', +export const listAgentConfigurationServicesRoute = createRoute({ + endpoint: 'GET /api/apm/settings/agent-configuration/services', handler: async ({ context, request }) => { const setup = await setupRequest(context, request); const searchAggregatedTransactions = await getSearchAggregatedTransactions( @@ -208,14 +204,14 @@ export const listAgentConfigurationServicesRoute = createRoute(() => ({ searchAggregatedTransactions, }); }, -})); +}); // get environments for service -export const listAgentConfigurationEnvironmentsRoute = createRoute(() => ({ - path: '/api/apm/settings/agent-configuration/environments', - params: { +export const listAgentConfigurationEnvironmentsRoute = createRoute({ + endpoint: 'GET /api/apm/settings/agent-configuration/environments', + params: t.partial({ query: t.partial({ serviceName: t.string }), - }, + }), handler: async ({ context, request }) => { const setup = await setupRequest(context, request); const { serviceName } = context.params.query; @@ -229,18 +225,18 @@ export const listAgentConfigurationEnvironmentsRoute = createRoute(() => ({ searchAggregatedTransactions, }); }, -})); +}); // get agentName for service -export const agentConfigurationAgentNameRoute = createRoute(() => ({ - path: '/api/apm/settings/agent-configuration/agent_name', - params: { +export const agentConfigurationAgentNameRoute = createRoute({ + endpoint: 'GET /api/apm/settings/agent-configuration/agent_name', + params: t.type({ query: t.type({ serviceName: t.string }), - }, + }), handler: async ({ context, request }) => { const setup = await setupRequest(context, request); const { serviceName } = context.params.query; const agentName = await getAgentNameByService({ serviceName, setup }); return { agentName }; }, -})); +}); diff --git a/x-pack/plugins/apm/server/routes/settings/anomaly_detection.ts b/x-pack/plugins/apm/server/routes/settings/anomaly_detection.ts index 3e5a9ee72599..633c284e91a4 100644 --- a/x-pack/plugins/apm/server/routes/settings/anomaly_detection.ts +++ b/x-pack/plugins/apm/server/routes/settings/anomaly_detection.ts @@ -18,9 +18,8 @@ import { getSearchAggregatedTransactions } from '../../lib/helpers/aggregated_tr import { notifyFeatureUsage } from '../../feature'; // get ML anomaly detection jobs for each environment -export const anomalyDetectionJobsRoute = createRoute(() => ({ - method: 'GET', - path: '/api/apm/settings/anomaly-detection', +export const anomalyDetectionJobsRoute = createRoute({ + endpoint: 'GET /api/apm/settings/anomaly-detection', options: { tags: ['access:apm', 'access:ml:canGetJobs'], }, @@ -40,20 +39,19 @@ export const anomalyDetectionJobsRoute = createRoute(() => ({ hasLegacyJobs: legacyJobs, }; }, -})); +}); // create new ML anomaly detection jobs for each given environment -export const createAnomalyDetectionJobsRoute = createRoute(() => ({ - method: 'POST', - path: '/api/apm/settings/anomaly-detection/jobs', +export const createAnomalyDetectionJobsRoute = createRoute({ + endpoint: 'POST /api/apm/settings/anomaly-detection/jobs', options: { tags: ['access:apm', 'access:apm_write', 'access:ml:canCreateJob'], }, - params: { + params: t.type({ body: t.type({ environments: t.array(t.string), }), - }, + }), handler: async ({ context, request }) => { const { environments } = context.params.body; const setup = await setupRequest(context, request); @@ -68,12 +66,11 @@ export const createAnomalyDetectionJobsRoute = createRoute(() => ({ featureName: 'ml', }); }, -})); +}); // get all available environments to create anomaly detection jobs for -export const anomalyDetectionEnvironmentsRoute = createRoute(() => ({ - method: 'GET', - path: '/api/apm/settings/anomaly-detection/environments', +export const anomalyDetectionEnvironmentsRoute = createRoute({ + endpoint: 'GET /api/apm/settings/anomaly-detection/environments', handler: async ({ context, request }) => { const setup = await setupRequest(context, request); @@ -87,4 +84,4 @@ export const anomalyDetectionEnvironmentsRoute = createRoute(() => ({ includeMissing: true, }); }, -})); +}); diff --git a/x-pack/plugins/apm/server/routes/settings/apm_indices.ts b/x-pack/plugins/apm/server/routes/settings/apm_indices.ts index 1946bd1111d4..760ee4225ede 100644 --- a/x-pack/plugins/apm/server/routes/settings/apm_indices.ts +++ b/x-pack/plugins/apm/server/routes/settings/apm_indices.ts @@ -13,34 +13,31 @@ import { import { saveApmIndices } from '../../lib/settings/apm_indices/save_apm_indices'; // get list of apm indices and values -export const apmIndexSettingsRoute = createRoute(() => ({ - method: 'GET', - path: '/api/apm/settings/apm-index-settings', +export const apmIndexSettingsRoute = createRoute({ + endpoint: 'GET /api/apm/settings/apm-index-settings', handler: async ({ context }) => { return await getApmIndexSettings({ context }); }, -})); +}); // get apm indices configuration object -export const apmIndicesRoute = createRoute(() => ({ - method: 'GET', - path: '/api/apm/settings/apm-indices', +export const apmIndicesRoute = createRoute({ + endpoint: 'GET /api/apm/settings/apm-indices', handler: async ({ context }) => { return await getApmIndices({ savedObjectsClient: context.core.savedObjects.client, config: context.config, }); }, -})); +}); // save ui indices -export const saveApmIndicesRoute = createRoute(() => ({ - method: 'POST', - path: '/api/apm/settings/apm-indices/save', +export const saveApmIndicesRoute = createRoute({ + endpoint: 'POST /api/apm/settings/apm-indices/save', options: { tags: ['access:apm', 'access:apm_write'], }, - params: { + params: t.type({ body: t.partial({ /* eslint-disable @typescript-eslint/naming-convention */ 'apm_oss.sourcemapIndices': t.string, @@ -51,10 +48,10 @@ export const saveApmIndicesRoute = createRoute(() => ({ 'apm_oss.metricsIndices': t.string, /* eslint-enable @typescript-eslint/naming-convention */ }), - }, + }), handler: async ({ context }) => { const { body } = context.params; const savedObjectsClient = context.core.savedObjects.client; return await saveApmIndices(savedObjectsClient, body); }, -})); +}); diff --git a/x-pack/plugins/apm/server/routes/settings/custom_link.ts b/x-pack/plugins/apm/server/routes/settings/custom_link.ts index 33769ac1d1c6..6f06ed4e970d 100644 --- a/x-pack/plugins/apm/server/routes/settings/custom_link.ts +++ b/x-pack/plugins/apm/server/routes/settings/custom_link.ts @@ -26,11 +26,11 @@ function isActiveGoldLicense(license: ILicense) { return license.isActive && license.hasAtLeast('gold'); } -export const customLinkTransactionRoute = createRoute(() => ({ - path: '/api/apm/settings/custom_links/transaction', - params: { +export const customLinkTransactionRoute = createRoute({ + endpoint: 'GET /api/apm/settings/custom_links/transaction', + params: t.partial({ query: filterOptionsRt, - }, + }), handler: async ({ context, request }) => { const setup = await setupRequest(context, request); const { query } = context.params; @@ -38,13 +38,13 @@ export const customLinkTransactionRoute = createRoute(() => ({ const filters = pick(query, FILTER_OPTIONS); return await getTransaction({ setup, filters }); }, -})); +}); -export const listCustomLinksRoute = createRoute(() => ({ - path: '/api/apm/settings/custom_links', - params: { +export const listCustomLinksRoute = createRoute({ + endpoint: 'GET /api/apm/settings/custom_links', + params: t.partial({ query: filterOptionsRt, - }, + }), handler: async ({ context, request }) => { if (!isActiveGoldLicense(context.licensing.license)) { throw Boom.forbidden(INVALID_LICENSE); @@ -55,14 +55,13 @@ export const listCustomLinksRoute = createRoute(() => ({ const filters = pick(query, FILTER_OPTIONS); return await listCustomLinks({ setup, filters }); }, -})); +}); -export const createCustomLinkRoute = createRoute(() => ({ - method: 'POST', - path: '/api/apm/settings/custom_links', - params: { +export const createCustomLinkRoute = createRoute({ + endpoint: 'POST /api/apm/settings/custom_links', + params: t.type({ body: payloadRt, - }, + }), options: { tags: ['access:apm', 'access:apm_write'], }, @@ -80,17 +79,16 @@ export const createCustomLinkRoute = createRoute(() => ({ }); return res; }, -})); +}); -export const updateCustomLinkRoute = createRoute(() => ({ - method: 'PUT', - path: '/api/apm/settings/custom_links/{id}', - params: { +export const updateCustomLinkRoute = createRoute({ + endpoint: 'PUT /api/apm/settings/custom_links/{id}', + params: t.type({ path: t.type({ id: t.string, }), body: payloadRt, - }, + }), options: { tags: ['access:apm', 'access:apm_write'], }, @@ -108,16 +106,15 @@ export const updateCustomLinkRoute = createRoute(() => ({ }); return res; }, -})); +}); -export const deleteCustomLinkRoute = createRoute(() => ({ - method: 'DELETE', - path: '/api/apm/settings/custom_links/{id}', - params: { +export const deleteCustomLinkRoute = createRoute({ + endpoint: 'DELETE /api/apm/settings/custom_links/{id}', + params: t.type({ path: t.type({ id: t.string, }), - }, + }), options: { tags: ['access:apm', 'access:apm_write'], }, @@ -133,4 +130,4 @@ export const deleteCustomLinkRoute = createRoute(() => ({ }); return res; }, -})); +}); diff --git a/x-pack/plugins/apm/server/routes/traces.ts b/x-pack/plugins/apm/server/routes/traces.ts index 45b334a7f06d..9bbf6f1cc906 100644 --- a/x-pack/plugins/apm/server/routes/traces.ts +++ b/x-pack/plugins/apm/server/routes/traces.ts @@ -12,11 +12,11 @@ import { createRoute } from './create_route'; import { rangeRt, uiFiltersRt } from './default_api_types'; import { getSearchAggregatedTransactions } from '../lib/helpers/aggregated_transactions'; -export const tracesRoute = createRoute(() => ({ - path: '/api/apm/traces', - params: { +export const tracesRoute = createRoute({ + endpoint: 'GET /api/apm/traces', + params: t.type({ query: t.intersection([rangeRt, uiFiltersRt]), - }, + }), handler: async ({ context, request }) => { const setup = await setupRequest(context, request); const searchAggregatedTransactions = await getSearchAggregatedTransactions( @@ -27,18 +27,18 @@ export const tracesRoute = createRoute(() => ({ setup ); }, -})); +}); -export const tracesByIdRoute = createRoute(() => ({ - path: '/api/apm/traces/{traceId}', - params: { +export const tracesByIdRoute = createRoute({ + endpoint: 'GET /api/apm/traces/{traceId}', + params: t.type({ path: t.type({ traceId: t.string, }), query: rangeRt, - }, + }), handler: async ({ context, request }) => { const setup = await setupRequest(context, request); return getTrace(context.params.path.traceId, setup); }, -})); +}); diff --git a/x-pack/plugins/apm/server/routes/transaction.ts b/x-pack/plugins/apm/server/routes/transaction.ts index b8cf0f4554d4..04f6c2e1ce24 100644 --- a/x-pack/plugins/apm/server/routes/transaction.ts +++ b/x-pack/plugins/apm/server/routes/transaction.ts @@ -9,16 +9,16 @@ import { setupRequest } from '../lib/helpers/setup_request'; import { getRootTransactionByTraceId } from '../lib/transactions/get_transaction_by_trace'; import { createRoute } from './create_route'; -export const transactionByTraceIdRoute = createRoute(() => ({ - path: '/api/apm/transaction/{traceId}', - params: { +export const transactionByTraceIdRoute = createRoute({ + endpoint: 'GET /api/apm/transaction/{traceId}', + params: t.type({ path: t.type({ traceId: t.string, }), - }, + }), handler: async ({ context, request }) => { const { traceId } = context.params.path; const setup = await setupRequest(context, request); return getRootTransactionByTraceId(traceId, setup); }, -})); +}); diff --git a/x-pack/plugins/apm/server/routes/transaction_groups.ts b/x-pack/plugins/apm/server/routes/transaction_groups.ts index a3a73222210b..423506afebe7 100644 --- a/x-pack/plugins/apm/server/routes/transaction_groups.ts +++ b/x-pack/plugins/apm/server/routes/transaction_groups.ts @@ -17,9 +17,9 @@ import { getTransactionSampleForGroup } from '../lib/transaction_groups/get_tran import { getSearchAggregatedTransactions } from '../lib/helpers/aggregated_transactions'; import { getErrorRate } from '../lib/transaction_groups/get_error_rate'; -export const transactionGroupsRoute = createRoute(() => ({ - path: '/api/apm/services/{serviceName}/transaction_groups', - params: { +export const transactionGroupsRoute = createRoute({ + endpoint: 'GET /api/apm/services/{serviceName}/transaction_groups', + params: t.type({ path: t.type({ serviceName: t.string, }), @@ -30,7 +30,7 @@ export const transactionGroupsRoute = createRoute(() => ({ uiFiltersRt, rangeRt, ]), - }, + }), handler: async ({ context, request }) => { const setup = await setupRequest(context, request); const { serviceName } = context.params.path; @@ -50,11 +50,11 @@ export const transactionGroupsRoute = createRoute(() => ({ setup ); }, -})); +}); -export const transactionGroupsChartsRoute = createRoute(() => ({ - path: '/api/apm/services/{serviceName}/transaction_groups/charts', - params: { +export const transactionGroupsChartsRoute = createRoute({ + endpoint: 'GET /api/apm/services/{serviceName}/transaction_groups/charts', + params: t.type({ path: t.type({ serviceName: t.string, }), @@ -66,7 +66,7 @@ export const transactionGroupsChartsRoute = createRoute(() => ({ uiFiltersRt, rangeRt, ]), - }, + }), handler: async ({ context, request }) => { const setup = await setupRequest(context, request); const logger = context.logger; @@ -94,11 +94,12 @@ export const transactionGroupsChartsRoute = createRoute(() => ({ return getTransactionCharts(options); }, -})); +}); -export const transactionGroupsDistributionRoute = createRoute(() => ({ - path: '/api/apm/services/{serviceName}/transaction_groups/distribution', - params: { +export const transactionGroupsDistributionRoute = createRoute({ + endpoint: + 'GET /api/apm/services/{serviceName}/transaction_groups/distribution', + params: t.type({ path: t.type({ serviceName: t.string, }), @@ -114,7 +115,7 @@ export const transactionGroupsDistributionRoute = createRoute(() => ({ uiFiltersRt, rangeRt, ]), - }, + }), handler: async ({ context, request }) => { const setup = await setupRequest(context, request); const { serviceName } = context.params.path; @@ -139,11 +140,11 @@ export const transactionGroupsDistributionRoute = createRoute(() => ({ searchAggregatedTransactions, }); }, -})); +}); -export const transactionGroupsBreakdownRoute = createRoute(() => ({ - path: '/api/apm/services/{serviceName}/transaction_groups/breakdown', - params: { +export const transactionGroupsBreakdownRoute = createRoute({ + endpoint: 'GET /api/apm/services/{serviceName}/transaction_groups/breakdown', + params: t.type({ path: t.type({ serviceName: t.string, }), @@ -157,7 +158,7 @@ export const transactionGroupsBreakdownRoute = createRoute(() => ({ uiFiltersRt, rangeRt, ]), - }, + }), handler: async ({ context, request }) => { const setup = await setupRequest(context, request); const { serviceName } = context.params.path; @@ -170,17 +171,17 @@ export const transactionGroupsBreakdownRoute = createRoute(() => ({ setup, }); }, -})); +}); -export const transactionSampleForGroupRoute = createRoute(() => ({ - path: `/api/apm/transaction_sample`, - params: { +export const transactionSampleForGroupRoute = createRoute({ + endpoint: `GET /api/apm/transaction_sample`, + params: t.type({ query: t.intersection([ uiFiltersRt, rangeRt, t.type({ serviceName: t.string, transactionName: t.string }), ]), - }, + }), handler: async ({ context, request }) => { const setup = await setupRequest(context, request); @@ -194,11 +195,11 @@ export const transactionSampleForGroupRoute = createRoute(() => ({ }), }; }, -})); +}); -export const transactionGroupsErrorRateRoute = createRoute(() => ({ - path: '/api/apm/services/{serviceName}/transaction_groups/error_rate', - params: { +export const transactionGroupsErrorRateRoute = createRoute({ + endpoint: 'GET /api/apm/services/{serviceName}/transaction_groups/error_rate', + params: t.type({ path: t.type({ serviceName: t.string, }), @@ -210,7 +211,7 @@ export const transactionGroupsErrorRateRoute = createRoute(() => ({ transactionName: t.string, }), ]), - }, + }), handler: async ({ context, request }) => { const setup = await setupRequest(context, request); const { params } = context; @@ -229,4 +230,4 @@ export const transactionGroupsErrorRateRoute = createRoute(() => ({ searchAggregatedTransactions, }); }, -})); +}); diff --git a/x-pack/plugins/apm/server/routes/typings.ts b/x-pack/plugins/apm/server/routes/typings.ts index 78c820fbf4ec..5f1b344ead5c 100644 --- a/x-pack/plugins/apm/server/routes/typings.ts +++ b/x-pack/plugins/apm/server/routes/typings.ts @@ -4,62 +4,70 @@ * you may not use this file except in compliance with the Elastic License. */ -import t from 'io-ts'; +import t, { Encode, Encoder } from 'io-ts'; import { CoreSetup, KibanaRequest, RequestHandlerContext, Logger, } from 'src/core/server'; -import { PickByValue, Optional } from 'utility-types'; import { Observable } from 'rxjs'; +import { RequiredKeys } from 'utility-types'; import { ObservabilityPluginSetup } from '../../../observability/server'; import { SecurityPluginSetup } from '../../../security/server'; import { MlPluginSetup } from '../../../ml/server'; import { FetchOptions } from '../../common/fetch_options'; import { APMConfig } from '..'; -export interface Params { - query?: t.HasProps; - path?: t.HasProps; - body?: t.Any | t.HasProps; +export interface RouteParams { + path?: Record; + query?: Record; + body?: any; } -type DecodeParams = { - [key in keyof TParams]: TParams[key] extends t.Any - ? t.TypeOf - : never; -}; +type WithoutIncompatibleMethods = Omit< + T, + 'encode' | 'asEncoder' +> & { encode: Encode; asEncoder: () => Encoder }; + +export type RouteParamsRT = WithoutIncompatibleMethods>; + +export type RouteHandler< + TParamsRT extends RouteParamsRT | undefined, + TReturn +> = (kibanaContext: { + context: APMRequestHandlerContext< + (TParamsRT extends RouteParamsRT ? t.TypeOf : {}) & { + query: { _debug: boolean }; + } + >; + request: KibanaRequest; +}) => Promise; -export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE'; +interface RouteOptions { + tags: Array< + | 'access:apm' + | 'access:apm_write' + | 'access:ml:canGetJobs' + | 'access:ml:canCreateJob' + >; +} export interface Route< - TPath extends string, - TMethod extends HttpMethod | undefined, - TParams extends Params | undefined, + TEndpoint extends string, + TRouteParamsRT extends RouteParamsRT | undefined, TReturn > { - path: TPath; - method?: TMethod; - params?: TParams; - options?: { - tags: Array< - | 'access:apm' - | 'access:apm_write' - | 'access:ml:canGetJobs' - | 'access:ml:canCreateJob' - >; - }; - handler: (kibanaContext: { - context: APMRequestHandlerContext>; - request: KibanaRequest; - }) => Promise; + endpoint: TEndpoint; + options?: RouteOptions; + params?: TRouteParamsRT; + handler: RouteHandler; } export type APMRequestHandlerContext< - TDecodedParams extends { [key in keyof Params]: any } = {} + TRouteParams = {} > = RequestHandlerContext & { - params: { query: { _debug: boolean } } & TDecodedParams; + params: TRouteParams & { query: { _debug: boolean } }; config: APMConfig; logger: Logger; plugins: { @@ -69,39 +77,29 @@ export type APMRequestHandlerContext< }; }; -export type RouteFactoryFn< - TPath extends string, - TMethod extends HttpMethod | undefined, - TParams extends Params, - TReturn -> = (core: CoreSetup) => Route; - export interface RouteState { - [key: string]: { - [key in HttpMethod]: { - params?: Params; - ret: any; - }; + [endpoint: string]: { + params?: RouteParams; + ret: any; }; } export interface ServerAPI { _S: TRouteState; add< - TPath extends string, - TReturn, - // default params allow them to be optional in the route configuration object - TMethod extends HttpMethod = 'GET', - TParams extends Params = {} + TEndpoint extends string, + TRouteParamsRT extends RouteParamsRT | undefined = undefined, + TReturn = unknown >( - factoryFn: RouteFactoryFn + route: + | Route + | ((core: CoreSetup) => Route) ): ServerAPI< TRouteState & { - [Key in TPath]: { - [key in TMethod]: { - ret: TReturn; - } & (TParams extends Params ? { params: TParams } : {}); + [key in TEndpoint]: { + params: TRouteParamsRT; + ret: TReturn; }; } >; @@ -119,49 +117,23 @@ export interface ServerAPI { ) => void; } -// without this, TS does not recognize possible existence of `params` in `options` below -interface NoParams { - params?: TParams; -} - -type GetOptionalParamKeys = keyof PickByValue< - { - [key in keyof TParams]: TParams[key] extends t.PartialType - ? false - : TParams[key] extends t.Any - ? true - : false; - }, - false ->; - -// this type makes the params object optional if no required props are found -type GetParams = Exclude< - keyof TParams, - GetOptionalParamKeys +type MaybeOptional }> = RequiredKeys< + T['params'] > extends never - ? NoParams>> - : { - params: Optional, GetOptionalParamKeys>; - }; + ? { params?: T['params'] } + : { params: T['params'] }; export type Client = < - TPath extends keyof TRouteState & string, - TMethod extends keyof TRouteState[TPath] & string, - TRouteDescription extends TRouteState[TPath][TMethod], - TParams extends TRouteDescription extends { params: Params } - ? TRouteDescription['params'] - : undefined, - TReturn extends TRouteDescription extends { ret: any } - ? TRouteDescription['ret'] - : undefined + TEndpoint extends keyof TRouteState & string >( options: Omit & { forceCache?: boolean; - pathname: TPath; - } & (TMethod extends 'GET' ? { method?: TMethod } : { method: TMethod }) & - // Makes sure params can only be set when types were defined - (TParams extends Params - ? GetParams - : NoParams>) -) => Promise; + endpoint: TEndpoint; + } & (TRouteState[TEndpoint] extends { params: t.Any } + ? MaybeOptional<{ params: t.TypeOf }> + : {}) +) => Promise< + TRouteState[TEndpoint] extends { ret: any } + ? TRouteState[TEndpoint]['ret'] + : unknown +>; diff --git a/x-pack/plugins/apm/server/routes/ui_filters.ts b/x-pack/plugins/apm/server/routes/ui_filters.ts index 26fe0118c02e..67e23ebbe249 100644 --- a/x-pack/plugins/apm/server/routes/ui_filters.ts +++ b/x-pack/plugins/apm/server/routes/ui_filters.ts @@ -30,16 +30,16 @@ import { getSearchAggregatedTransactions } from '../lib/helpers/aggregated_trans import { APMRequestHandlerContext } from './typings'; import { LocalUIFilterName } from '../../common/ui_filter'; -export const uiFiltersEnvironmentsRoute = createRoute(() => ({ - path: '/api/apm/ui_filters/environments', - params: { +export const uiFiltersEnvironmentsRoute = createRoute({ + endpoint: 'GET /api/apm/ui_filters/environments', + params: t.type({ query: t.intersection([ t.partial({ serviceName: t.string, }), rangeRt, ]), - }, + }), handler: async ({ context, request }) => { const setup = await setupRequest(context, request); const { serviceName } = context.params.query; @@ -53,7 +53,7 @@ export const uiFiltersEnvironmentsRoute = createRoute(() => ({ searchAggregatedTransactions, }); }, -})); +}); const filterNamesRt = t.type({ filterNames: jsonRt.pipe( @@ -74,26 +74,26 @@ const localUiBaseQueryRt = t.intersection([ ]); function createLocalFiltersRoute< - TPath extends string, + TEndpoint extends string, TProjection extends Projection, TQueryRT extends t.HasProps >({ - path, + endpoint, getProjection, queryRt, }: { - path: TPath; + endpoint: TEndpoint; getProjection: GetProjection< TProjection, t.IntersectionC<[TQueryRT, BaseQueryType]> >; queryRt: TQueryRT; }) { - return createRoute(() => ({ - path, - params: { + return createRoute({ + endpoint, + params: t.type({ query: t.intersection([localUiBaseQueryRt, queryRt]), - }, + }), handler: async ({ context, request }) => { const setup = await setupRequest(context, request); const { uiFilters } = setup; @@ -116,11 +116,11 @@ function createLocalFiltersRoute< localFilterNames: filterNames, }); }, - })); + }); } export const servicesLocalFiltersRoute = createLocalFiltersRoute({ - path: `/api/apm/ui_filters/local_filters/services`, + endpoint: `GET /api/apm/ui_filters/local_filters/services`, getProjection: async ({ context, setup }) => { const searchAggregatedTransactions = await getSearchAggregatedTransactions( setup @@ -132,7 +132,7 @@ export const servicesLocalFiltersRoute = createLocalFiltersRoute({ }); export const transactionGroupsLocalFiltersRoute = createLocalFiltersRoute({ - path: '/api/apm/ui_filters/local_filters/transactionGroups', + endpoint: 'GET /api/apm/ui_filters/local_filters/transactionGroups', getProjection: async ({ context, setup, query }) => { const { transactionType, serviceName, transactionName } = query; @@ -163,7 +163,7 @@ export const transactionGroupsLocalFiltersRoute = createLocalFiltersRoute({ }); export const tracesLocalFiltersRoute = createLocalFiltersRoute({ - path: '/api/apm/ui_filters/local_filters/traces', + endpoint: 'GET /api/apm/ui_filters/local_filters/traces', getProjection: async ({ setup, context }) => { const searchAggregatedTransactions = await getSearchAggregatedTransactions( setup @@ -178,7 +178,7 @@ export const tracesLocalFiltersRoute = createLocalFiltersRoute({ }); export const transactionsLocalFiltersRoute = createLocalFiltersRoute({ - path: '/api/apm/ui_filters/local_filters/transactions', + endpoint: 'GET /api/apm/ui_filters/local_filters/transactions', getProjection: async ({ context, setup, query }) => { const { transactionType, serviceName, transactionName } = query; @@ -202,7 +202,7 @@ export const transactionsLocalFiltersRoute = createLocalFiltersRoute({ }); export const metricsLocalFiltersRoute = createLocalFiltersRoute({ - path: '/api/apm/ui_filters/local_filters/metrics', + endpoint: 'GET /api/apm/ui_filters/local_filters/metrics', getProjection: ({ setup, query }) => { const { serviceName, serviceNodeName } = query; return getMetricsProjection({ @@ -222,7 +222,7 @@ export const metricsLocalFiltersRoute = createLocalFiltersRoute({ }); export const errorGroupsLocalFiltersRoute = createLocalFiltersRoute({ - path: '/api/apm/ui_filters/local_filters/errorGroups', + endpoint: 'GET /api/apm/ui_filters/local_filters/errorGroups', getProjection: ({ setup, query }) => { const { serviceName } = query; return getErrorGroupsProjection({ @@ -236,7 +236,7 @@ export const errorGroupsLocalFiltersRoute = createLocalFiltersRoute({ }); export const serviceNodesLocalFiltersRoute = createLocalFiltersRoute({ - path: '/api/apm/ui_filters/local_filters/serviceNodes', + endpoint: 'GET /api/apm/ui_filters/local_filters/serviceNodes', getProjection: ({ setup, query }) => { const { serviceName } = query; return getServiceNodesProjection({ @@ -250,7 +250,7 @@ export const serviceNodesLocalFiltersRoute = createLocalFiltersRoute({ }); export const rumOverviewLocalFiltersRoute = createLocalFiltersRoute({ - path: '/api/apm/ui_filters/local_filters/rumOverview', + endpoint: 'GET /api/apm/ui_filters/local_filters/rumOverview', getProjection: async ({ setup }) => { return getRumPageLoadTransactionsProjection({ setup, From b63830f1055afe31f6301dd4ae0987c853e4b131 Mon Sep 17 00:00:00 2001 From: Scotty Bollinger Date: Wed, 18 Nov 2020 13:05:14 -0600 Subject: [PATCH 13/49] [Workplace Search] Port Box changes from ent-search (#83675) --- .../components/shared/assets/box.svg | 2 +- .../workplace_search/constants.ts | 3 ++ .../applications/workplace_search/routes.ts | 3 ++ .../views/content_sources/source_data.tsx | 41 +++++++++++++++++++ 4 files changed, 48 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/box.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/box.svg index 1e7324d9581a..827f8cf0a55e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/box.svg +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/box.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/constants.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/constants.ts index 4e093f472d56..1846115d7390 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/constants.ts @@ -55,6 +55,9 @@ export const SOURCE_STATUSES = { }; export const SOURCE_NAMES = { + BOX: i18n.translate('xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.box', { + defaultMessage: 'Box', + }), CONFLUENCE: i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.sources.sourceNames.confluence', { defaultMessage: 'Confluence' } diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.ts index 6099a42e6d7c..419ae1cbfbc0 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.ts @@ -21,6 +21,7 @@ export const PRIVATE_SOURCES_DOCS_URL = `${DOCUMENT_PERMISSIONS_DOCS_URL}#source export const EXTERNAL_IDENTITIES_DOCS_URL = `${DOCS_PREFIX}/workplace-search-external-identities-api.html`; export const SECURITY_DOCS_URL = `${DOCS_PREFIX}/workplace-search-security.html`; export const SMTP_DOCS_URL = `${DOCS_PREFIX}/workplace-search-smtp-mailer.html`; +export const BOX_DOCS_URL = `${DOCS_PREFIX}/workplace-search-box-connector.html`; export const CONFLUENCE_DOCS_URL = `${DOCS_PREFIX}/workplace-search-confluence-cloud-connector.html`; export const CONFLUENCE_SERVER_DOCS_URL = `${DOCS_PREFIX}/workplace-search-confluence-server-connector.html`; export const DROPBOX_DOCS_URL = `${DOCS_PREFIX}/workplace-search-dropbox-connector.html`; @@ -59,6 +60,7 @@ export const ORG_SOURCES_PATH = `${ORG_PATH}${SOURCES_PATH}`; export const SOURCE_ADDED_PATH = `${SOURCES_PATH}/added`; export const ADD_SOURCE_PATH = `${SOURCES_PATH}/add`; +export const ADD_BOX_PATH = `${SOURCES_PATH}/add/box`; export const ADD_CONFLUENCE_PATH = `${SOURCES_PATH}/add/confluence-cloud`; export const ADD_CONFLUENCE_SERVER_PATH = `${SOURCES_PATH}/add/confluence-server`; export const ADD_DROPBOX_PATH = `${SOURCES_PATH}/add/dropbox`; @@ -93,6 +95,7 @@ export const ORG_SETTINGS_PATH = `${ORG_PATH}/settings`; export const ORG_SETTINGS_CUSTOMIZE_PATH = `${ORG_SETTINGS_PATH}/customize`; export const ORG_SETTINGS_CONNECTORS_PATH = `${ORG_SETTINGS_PATH}/connectors`; export const ORG_SETTINGS_OAUTH_APPLICATION_PATH = `${ORG_SETTINGS_PATH}/oauth`; +export const EDIT_BOX_PATH = `${ORG_SETTINGS_CONNECTORS_PATH}/box/edit`; export const EDIT_CONFLUENCE_PATH = `${ORG_SETTINGS_CONNECTORS_PATH}/confluence-cloud/edit`; export const EDIT_CONFLUENCE_SERVER_PATH = `${ORG_SETTINGS_CONNECTORS_PATH}/confluence-server/edit`; export const EDIT_DROPBOX_PATH = `${ORG_SETTINGS_CONNECTORS_PATH}/dropbox/edit`; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_data.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_data.tsx index dff9895dd84f..882c3861922e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_data.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_data.tsx @@ -7,6 +7,7 @@ import { i18n } from '@kbn/i18n'; import { + ADD_BOX_PATH, ADD_CONFLUENCE_PATH, ADD_CONFLUENCE_SERVER_PATH, ADD_DROPBOX_PATH, @@ -24,6 +25,7 @@ import { ADD_SLACK_PATH, ADD_ZENDESK_PATH, ADD_CUSTOM_PATH, + EDIT_BOX_PATH, EDIT_CONFLUENCE_PATH, EDIT_CONFLUENCE_SERVER_PATH, EDIT_DROPBOX_PATH, @@ -41,6 +43,7 @@ import { EDIT_SLACK_PATH, EDIT_ZENDESK_PATH, EDIT_CUSTOM_PATH, + BOX_DOCS_URL, CONFLUENCE_DOCS_URL, CONFLUENCE_SERVER_DOCS_URL, GITHUB_ENTERPRISE_DOCS_URL, @@ -82,6 +85,44 @@ const connectStepDescription = { }; export const staticSourceData = [ + { + name: SOURCE_NAMES.BOX, + serviceType: 'box', + addPath: ADD_BOX_PATH, + editPath: EDIT_BOX_PATH, + configuration: { + isPublicKey: false, + hasOauthRedirect: true, + needsBaseUrl: false, + documentationUrl: BOX_DOCS_URL, + applicationPortalUrl: 'https://app.box.com/developers/console', + }, + sourceDescription: i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.sourceDescriptions.box', + { + defaultMessage: + '{sourceName} is a cloud-based storage service for organizations of all sizes. Create, store, share and automatically synchronize documents across your desktop and web.', + values: { sourceName: SOURCE_NAMES.BOX }, + } + ), + connectStepDescription: connectStepDescription.files, + objTypes: [SOURCE_OBJ_TYPES.ALL_FILES], + features: { + basicOrgContext: [ + FeatureIds.SyncFrequency, + FeatureIds.SyncedItems, + FeatureIds.GlobalAccessPermissions, + ], + basicOrgContextExcludedFeatures: [FeatureIds.DocumentLevelPermissions], + platinumOrgContext: [FeatureIds.SyncFrequency, FeatureIds.SyncedItems], + platinumPrivateContext: [ + FeatureIds.Private, + FeatureIds.SyncFrequency, + FeatureIds.SyncedItems, + ], + }, + accountContextOnly: false, + }, { name: SOURCE_NAMES.CONFLUENCE, serviceType: 'confluence_cloud', From e7ff3a6f33b51fe7daf2e06330816c4fef6a8b56 Mon Sep 17 00:00:00 2001 From: Scotty Bollinger Date: Wed, 18 Nov 2020 13:05:36 -0600 Subject: [PATCH 14/49] [Workplace Search] Migrate SourceLogic from ent-search (#83593) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Initial copy/paste of source logic Only changed lodash imports and import order for linting * Add types and route * Update paths and typings Renamed IMeta -> Meta Used object instead of IObject * Remove internal flash messages in favor of globals - All instances of flashAPIErrors(e) are only placeholders until the later commit removing axios. - buttonLoading was set to false when the error flash messages were set. For now I added a `setButtonNotLoading` action to do this manually in a finally block. This will be refactored once axios is removed. - SourcesLogic is no longer needed because we set a queued flash message instead of trying to set it in SourcesLogic, which no longer has local flash messages * Add return types to callback definitions * Update routes According to the API info getSourceReConnectData is supposed to send the source ID and not the service type. In the template, we are actually sending the ID but the logic file parameterizes it as serviceType. This is fixed here. Usage: https://github.com/elastic/ent-search/blob/master/app/javascript/workplace_search/ContentSources/components/AddSource/ReAuthenticate.tsx#L38 * Replace axios with HttpLogic Also removes using history in favor of KibanaLogic’s navigateToUrl * Fix incorrect type This selector is actually an array of strings * Create GenericObject to satisfy TypeScript Previously in `ent-search`, we had a generic `IObject` interface that we could use on keyed objects. It was not migrated over since it uses `any` and Kibana has a generic `object` type we can use in most situations. However, when we are checking for keys in our code, `object` does not work. This commit is an attempt at making a generic interface we can use. * More strict object typing Removes GenericObject from last commit and adds stricter local typing * Add i18n Also added for already-merged SourcesLogic * Move button loading action to finally block * Move route strings to inline --- .../applications/workplace_search/routes.ts | 2 + .../applications/workplace_search/types.ts | 42 ++ .../views/content_sources/source_logic.ts | 633 ++++++++++++++++++ .../views/content_sources/sources_logic.ts | 21 +- 4 files changed, 696 insertions(+), 2 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.ts diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.ts index 419ae1cbfbc0..8f62984db1b5 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.ts @@ -10,6 +10,8 @@ import { CURRENT_MAJOR_VERSION } from '../../../common/version'; export const SETUP_GUIDE_PATH = '/setup_guide'; +export const NOT_FOUND_PATH = '/404'; + export const LEAVE_FEEDBACK_EMAIL = 'support@elastic.co'; export const LEAVE_FEEDBACK_URL = `mailto:${LEAVE_FEEDBACK_EMAIL}?Subject=Elastic%20Workplace%20Search%20Feedback`; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/types.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/types.ts index 801bcda2a319..1bd3cabb0227 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/types.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/types.ts @@ -8,6 +8,17 @@ export * from '../../../common/types/workplace_search'; export type SpacerSizeTypes = 'xs' | 's' | 'm' | 'l' | 'xl' | 'xxl'; +export interface MetaPage { + current: number; + size: number; + total_pages: number; + total_results: number; +} + +export interface Meta { + page: MetaPage; +} + export interface Group { id: string; name: string; @@ -89,6 +100,30 @@ export interface ContentSourceDetails extends ContentSource { boost: number; } +interface DescriptionList { + title: string; + description: string; +} + +export interface ContentSourceFullData extends ContentSourceDetails { + activities: object[]; + details: DescriptionList[]; + summary: object[]; + groups: object[]; + custom: boolean; + accessToken: string; + key: string; + urlField: string; + titleField: string; + licenseSupportsPermissions: boolean; + serviceTypeSupportsPermissions: boolean; + indexPermissions: boolean; + hasPermissions: boolean; + urlFieldIsLinkable: boolean; + createdAt: string; + serviceName: string; +} + export interface ContentSourceStatus { id: string; name: string; @@ -121,3 +156,10 @@ export enum FeatureIds { GlobalAccessPermissions = 'GlobalAccessPermissions', DocumentLevelPermissions = 'DocumentLevelPermissions', } + +export interface CustomSource { + accessToken: string; + key: string; + name: string; + id: string; +} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.ts new file mode 100644 index 000000000000..889519b8a998 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.ts @@ -0,0 +1,633 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { keys, pickBy } from 'lodash'; + +import { kea, MakeLogicType } from 'kea'; + +import { i18n } from '@kbn/i18n'; + +import { HttpLogic } from '../../../shared/http'; +import { KibanaLogic } from '../../../shared/kibana'; + +import { + flashAPIErrors, + setSuccessMessage, + setQueuedSuccessMessage, + FlashMessagesLogic, +} from '../../../shared/flash_messages'; + +import { DEFAULT_META } from '../../../shared/constants'; +import { AppLogic } from '../../app_logic'; +import { NOT_FOUND_PATH } from '../../routes'; +import { ContentSourceFullData, CustomSource, Meta } from '../../types'; + +export interface SourceActions { + onInitializeSource(contentSource: ContentSourceFullData): ContentSourceFullData; + onUpdateSourceName(name: string): string; + setSourceConfigData(sourceConfigData: SourceConfigData): SourceConfigData; + setSourceConnectData(sourceConnectData: SourceConnectData): SourceConnectData; + setSearchResults(searchResultsResponse: SearchResultsResponse): SearchResultsResponse; + initializeFederatedSummary(sourceId: string): { sourceId: string }; + onUpdateSummary(summary: object[]): object[]; + setContentFilterValue(contentFilterValue: string): string; + setActivePage(activePage: number): number; + setClientIdValue(clientIdValue: string): string; + setClientSecretValue(clientSecretValue: string): string; + setBaseUrlValue(baseUrlValue: string): string; + setCustomSourceNameValue(customSourceNameValue: string): string; + setSourceLoginValue(loginValue: string): string; + setSourcePasswordValue(passwordValue: string): string; + setSourceSubdomainValue(subdomainValue: string): string; + setSourceIndexPermissionsValue(indexPermissionsValue: boolean): boolean; + setCustomSourceData(data: CustomSource): CustomSource; + setPreContentSourceConfigData(data: PreContentSourceResponse): PreContentSourceResponse; + setSelectedGithubOrganizations(option: string): string; + searchContentSourceDocuments(sourceId: string): { sourceId: string }; + updateContentSource( + sourceId: string, + source: { name: string } + ): { sourceId: string; source: { name: string } }; + resetSourceState(): void; + removeContentSource( + sourceId: string, + successCallback: () => void + ): { sourceId: string; successCallback(): void }; + createContentSource( + serviceType: string, + successCallback: () => void, + errorCallback?: () => void + ): { serviceType: string; successCallback(): void; errorCallback?(): void }; + saveSourceConfig( + isUpdating: boolean, + successCallback?: () => void + ): { isUpdating: boolean; successCallback?(): void }; + initializeSource(sourceId: string, history: object): { sourceId: string; history: object }; + getSourceConfigData(serviceType: string): { serviceType: string }; + getSourceConnectData( + serviceType: string, + successCallback: (oauthUrl: string) => string + ): { serviceType: string; successCallback(oauthUrl: string): void }; + getSourceReConnectData(sourceId: string): { sourceId: string }; + getPreContentSourceConfigData(preContentSourceId: string): { preContentSourceId: string }; + setButtonNotLoading(): void; +} + +interface SourceConfigData { + serviceType: string; + name: string; + configured: boolean; + categories: string[]; + needsPermissions?: boolean; + privateSourcesEnabled: boolean; + configuredFields: { + publicKey: string; + privateKey: string; + consumerKey: string; + baseUrl?: string; + clientId?: string; + clientSecret?: string; + }; + accountContextOnly?: boolean; +} + +interface SourceConnectData { + oauthUrl: string; + serviceType: string; +} + +interface OrganizationsMap { + [key: string]: string | boolean; +} + +interface SourceValues { + contentSource: ContentSourceFullData; + dataLoading: boolean; + sectionLoading: boolean; + buttonLoading: boolean; + contentItems: object[]; + contentMeta: Meta; + contentFilterValue: string; + customSourceNameValue: string; + clientIdValue: string; + clientSecretValue: string; + baseUrlValue: string; + loginValue: string; + passwordValue: string; + subdomainValue: string; + indexPermissionsValue: boolean; + sourceConfigData: SourceConfigData; + sourceConnectData: SourceConnectData; + newCustomSource: CustomSource; + currentServiceType: string; + githubOrganizations: string[]; + selectedGithubOrganizationsMap: OrganizationsMap; + selectedGithubOrganizations: string[]; +} + +interface SearchResultsResponse { + results: object[]; + meta: Meta; +} + +interface PreContentSourceResponse { + id: string; + serviceType: string; + githubOrganizations: string[]; +} + +export const SourceLogic = kea>({ + actions: { + onInitializeSource: (contentSource: ContentSourceFullData) => contentSource, + onUpdateSourceName: (name: string) => name, + setSourceConfigData: (sourceConfigData: SourceConfigData) => sourceConfigData, + setSourceConnectData: (sourceConnectData: SourceConnectData) => sourceConnectData, + onUpdateSummary: (summary: object[]) => summary, + setSearchResults: (searchResultsResponse: SearchResultsResponse) => searchResultsResponse, + setContentFilterValue: (contentFilterValue: string) => contentFilterValue, + setActivePage: (activePage: number) => activePage, + setClientIdValue: (clientIdValue: string) => clientIdValue, + setClientSecretValue: (clientSecretValue: string) => clientSecretValue, + setBaseUrlValue: (baseUrlValue: string) => baseUrlValue, + setCustomSourceNameValue: (customSourceNameValue: string) => customSourceNameValue, + setSourceLoginValue: (loginValue: string) => loginValue, + setSourcePasswordValue: (passwordValue: string) => passwordValue, + setSourceSubdomainValue: (subdomainValue: string) => subdomainValue, + setSourceIndexPermissionsValue: (indexPermissionsValue: boolean) => indexPermissionsValue, + setCustomSourceData: (data: CustomSource) => data, + setPreContentSourceConfigData: (data: PreContentSourceResponse) => data, + setSelectedGithubOrganizations: (option: string) => option, + initializeSource: (sourceId: string, history: object) => ({ sourceId, history }), + initializeFederatedSummary: (sourceId: string) => ({ sourceId }), + searchContentSourceDocuments: (sourceId: string) => ({ sourceId }), + updateContentSource: (sourceId: string, source: { name: string }) => ({ sourceId, source }), + removeContentSource: (sourceId: string, successCallback: () => void) => ({ + sourceId, + successCallback, + }), + getSourceConfigData: (serviceType: string) => ({ serviceType }), + getSourceConnectData: (serviceType: string, successCallback: (oauthUrl: string) => string) => ({ + serviceType, + successCallback, + }), + getSourceReConnectData: (sourceId: string) => ({ sourceId }), + getPreContentSourceConfigData: (preContentSourceId: string) => ({ preContentSourceId }), + saveSourceConfig: (isUpdating: boolean, successCallback?: () => void) => ({ + isUpdating, + successCallback, + }), + createContentSource: ( + serviceType: string, + successCallback: () => void, + errorCallback?: () => void + ) => ({ serviceType, successCallback, errorCallback }), + resetSourceState: () => true, + setButtonNotLoading: () => false, + }, + reducers: { + contentSource: [ + {} as ContentSourceFullData, + { + onInitializeSource: (_, contentSource) => contentSource, + onUpdateSourceName: (contentSource, name) => ({ + ...contentSource, + name, + }), + onUpdateSummary: (contentSource, summary) => ({ + ...contentSource, + summary, + }), + }, + ], + sourceConfigData: [ + {} as SourceConfigData, + { + setSourceConfigData: (_, sourceConfigData) => sourceConfigData, + }, + ], + sourceConnectData: [ + {} as SourceConnectData, + { + setSourceConnectData: (_, sourceConnectData) => sourceConnectData, + }, + ], + dataLoading: [ + true, + { + onInitializeSource: () => false, + setSourceConfigData: () => false, + resetSourceState: () => false, + setPreContentSourceConfigData: () => false, + }, + ], + buttonLoading: [ + false, + { + setButtonNotLoading: () => false, + setSourceConnectData: () => false, + setSourceConfigData: () => false, + resetSourceState: () => false, + removeContentSource: () => true, + saveSourceConfig: () => true, + getSourceConnectData: () => true, + createContentSource: () => true, + }, + ], + sectionLoading: [ + true, + { + searchContentSourceDocuments: () => true, + getPreContentSourceConfigData: () => true, + setSearchResults: () => false, + setPreContentSourceConfigData: () => false, + }, + ], + contentItems: [ + [], + { + setSearchResults: (_, { results }) => results, + }, + ], + contentMeta: [ + DEFAULT_META, + { + setActivePage: (state, activePage) => setPage(state, activePage), + setContentFilterValue: (state) => setPage(state, DEFAULT_META.page.current), + setSearchResults: (_, { meta }) => meta, + }, + ], + contentFilterValue: [ + '', + { + setContentFilterValue: (_, contentFilterValue) => contentFilterValue, + resetSourceState: () => '', + }, + ], + clientIdValue: [ + '', + { + setClientIdValue: (_, clientIdValue) => clientIdValue, + setSourceConfigData: (_, { configuredFields: { clientId } }) => clientId || '', + resetSourceState: () => '', + }, + ], + clientSecretValue: [ + '', + { + setClientSecretValue: (_, clientSecretValue) => clientSecretValue, + setSourceConfigData: (_, { configuredFields: { clientSecret } }) => clientSecret || '', + resetSourceState: () => '', + }, + ], + baseUrlValue: [ + '', + { + setBaseUrlValue: (_, baseUrlValue) => baseUrlValue, + setSourceConfigData: (_, { configuredFields: { baseUrl } }) => baseUrl || '', + resetSourceState: () => '', + }, + ], + loginValue: [ + '', + { + setSourceLoginValue: (_, loginValue) => loginValue, + resetSourceState: () => '', + }, + ], + passwordValue: [ + '', + { + setSourcePasswordValue: (_, passwordValue) => passwordValue, + resetSourceState: () => '', + }, + ], + subdomainValue: [ + '', + { + setSourceSubdomainValue: (_, subdomainValue) => subdomainValue, + resetSourceState: () => '', + }, + ], + indexPermissionsValue: [ + false, + { + setSourceIndexPermissionsValue: (_, indexPermissionsValue) => indexPermissionsValue, + resetSourceState: () => false, + }, + ], + customSourceNameValue: [ + '', + { + setCustomSourceNameValue: (_, customSourceNameValue) => customSourceNameValue, + resetSourceState: () => '', + }, + ], + newCustomSource: [ + {} as CustomSource, + { + setCustomSourceData: (_, newCustomSource) => newCustomSource, + resetSourceState: () => ({} as CustomSource), + }, + ], + currentServiceType: [ + '', + { + setPreContentSourceConfigData: (_, { serviceType }) => serviceType, + resetSourceState: () => '', + }, + ], + githubOrganizations: [ + [], + { + setPreContentSourceConfigData: (_, { githubOrganizations }) => githubOrganizations, + resetSourceState: () => [], + }, + ], + selectedGithubOrganizationsMap: [ + {} as OrganizationsMap, + { + setSelectedGithubOrganizations: (state, option) => ({ + ...state, + ...{ [option]: !state[option] }, + }), + resetSourceState: () => ({}), + }, + ], + }, + selectors: ({ selectors }) => ({ + selectedGithubOrganizations: [ + () => [selectors.selectedGithubOrganizationsMap], + (orgsMap) => keys(pickBy(orgsMap)), + ], + }), + listeners: ({ actions, values }) => ({ + initializeSource: async ({ sourceId }) => { + const { isOrganization } = AppLogic.values; + const route = isOrganization + ? `/api/workplace_search/org/sources/${sourceId}` + : `/api/workplace_search/account/sources/${sourceId}`; + + try { + const response = await HttpLogic.values.http.get(route); + actions.onInitializeSource(response); + if (response.isFederatedSource) { + actions.initializeFederatedSummary(sourceId); + } + } catch (e) { + // TODO: Verify this works once components are there. Not sure if the catch gives a status code. + if (e.response.status === 404) { + KibanaLogic.values.navigateToUrl(NOT_FOUND_PATH); + } else { + flashAPIErrors(e); + } + } + }, + initializeFederatedSummary: async ({ sourceId }) => { + const route = `/api/workplace_search/org/sources/${sourceId}/federated_summary`; + try { + const response = await HttpLogic.values.http.get(route); + actions.onUpdateSummary(response.summary); + } catch (e) { + flashAPIErrors(e); + } + }, + searchContentSourceDocuments: async ({ sourceId }, breakpoint) => { + await breakpoint(300); + + const { isOrganization } = AppLogic.values; + const route = isOrganization + ? `/api/workplace_search/org/sources/${sourceId}/documents` + : `/api/workplace_search/account/sources/${sourceId}/documents`; + + const { + contentFilterValue: query, + contentMeta: { page }, + } = values; + + try { + const response = await HttpLogic.values.http.post(route, { + body: JSON.stringify({ query, page }), + }); + actions.setSearchResults(response); + } catch (e) { + flashAPIErrors(e); + } + }, + updateContentSource: async ({ sourceId, source }) => { + const { isOrganization } = AppLogic.values; + const route = isOrganization + ? `/api/workplace_search/org/sources/${sourceId}/settings` + : `/api/workplace_search/account/sources/${sourceId}/settings`; + + try { + const response = await HttpLogic.values.http.patch(route, { + body: JSON.stringify({ content_source: source }), + }); + actions.onUpdateSourceName(response.name); + } catch (e) { + flashAPIErrors(e); + } + }, + removeContentSource: async ({ sourceId, successCallback }) => { + FlashMessagesLogic.actions.clearFlashMessages(); + const { isOrganization } = AppLogic.values; + const route = isOrganization + ? `/api/workplace_search/org/sources/${sourceId}` + : `/api/workplace_search/account/sources/${sourceId}`; + + try { + const response = await HttpLogic.values.http.delete(route); + setQueuedSuccessMessage( + i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.flashMessages.contentSourceRemoved', + { + defaultMessage: 'Successfully deleted {sourceName}.', + values: { sourceName: response.name }, + } + ) + ); + successCallback(); + } catch (e) { + flashAPIErrors(e); + } finally { + actions.setButtonNotLoading(); + } + }, + getSourceConfigData: async ({ serviceType }) => { + const route = `/api/workplace_search/org/settings/connectors/${serviceType}`; + + try { + const response = await HttpLogic.values.http.get(route); + actions.setSourceConfigData(response); + } catch (e) { + flashAPIErrors(e); + } + }, + getSourceConnectData: async ({ serviceType, successCallback }) => { + FlashMessagesLogic.actions.clearFlashMessages(); + const { isOrganization } = AppLogic.values; + const { subdomainValue: subdomain, indexPermissionsValue: indexPermissions } = values; + + const route = isOrganization + ? `/api/workplace_search/org/sources/${serviceType}/prepare` + : `/api/workplace_search/account/sources/${serviceType}/prepare`; + + const params = new URLSearchParams(); + if (subdomain) params.append('subdomain', subdomain); + if (indexPermissions) params.append('index_permissions', indexPermissions.toString()); + + try { + const response = await HttpLogic.values.http.get(`${route}?${params}`); + actions.setSourceConnectData(response); + successCallback(response.oauthUrl); + } catch (e) { + flashAPIErrors(e); + } finally { + actions.setButtonNotLoading(); + } + }, + getSourceReConnectData: async ({ sourceId }) => { + const { isOrganization } = AppLogic.values; + const route = isOrganization + ? `/api/workplace_search/org/sources/${sourceId}/reauth_prepare` + : `/api/workplace_search/account/sources/${sourceId}/reauth_prepare`; + + try { + const response = await HttpLogic.values.http.get(route); + actions.setSourceConnectData(response); + } catch (e) { + flashAPIErrors(e); + } + }, + getPreContentSourceConfigData: async ({ preContentSourceId }) => { + const { isOrganization } = AppLogic.values; + const route = isOrganization + ? `/api/workplace_search/org/pre_sources/${preContentSourceId}` + : `/api/workplace_search/account/pre_sources/${preContentSourceId}`; + + try { + const response = await HttpLogic.values.http.get(route); + actions.setPreContentSourceConfigData(response); + } catch (e) { + flashAPIErrors(e); + } + }, + saveSourceConfig: async ({ isUpdating, successCallback }) => { + FlashMessagesLogic.actions.clearFlashMessages(); + const { + sourceConfigData: { serviceType }, + baseUrlValue, + clientIdValue, + clientSecretValue, + sourceConfigData, + } = values; + + const route = isUpdating + ? `/api/workplace_search/org/settings/connectors/${serviceType}` + : '/api/workplace_search/org/settings/connectors'; + + const http = isUpdating ? HttpLogic.values.http.put : HttpLogic.values.http.post; + + const params = { + base_url: baseUrlValue || undefined, + client_id: clientIdValue || undefined, + client_secret: clientSecretValue || undefined, + service_type: serviceType, + private_key: sourceConfigData.configuredFields?.privateKey, + public_key: sourceConfigData.configuredFields?.publicKey, + consumer_key: sourceConfigData.configuredFields?.consumerKey, + }; + + try { + const response = await http(route, { + body: JSON.stringify({ params }), + }); + if (isUpdating) { + setSuccessMessage( + i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.flashMessages.contentSourceConfigUpdated', + { + defaultMessage: 'Successfully updated configuration.', + } + ) + ); + } + actions.setSourceConfigData(response); + if (successCallback) successCallback(); + } catch (e) { + flashAPIErrors(e); + if (!isUpdating) throw new Error(e); + } finally { + actions.setButtonNotLoading(); + } + }, + createContentSource: async ({ serviceType, successCallback, errorCallback }) => { + FlashMessagesLogic.actions.clearFlashMessages(); + const { isOrganization } = AppLogic.values; + const route = isOrganization + ? '/api/workplace_search/org/create_source' + : '/api/workplace_search/account/create_source'; + + const { + selectedGithubOrganizations: githubOrganizations, + customSourceNameValue, + loginValue, + passwordValue, + indexPermissionsValue, + } = values; + + const params = { + service_type: serviceType, + name: customSourceNameValue || undefined, + login: loginValue || undefined, + password: passwordValue || undefined, + organizations: githubOrganizations.length > 0 ? githubOrganizations : undefined, + indexPermissions: indexPermissionsValue || undefined, + } as { + [key: string]: string | string[] | undefined; + }; + + // Remove undefined values from params + Object.keys(params).forEach((key) => params[key] === undefined && delete params[key]); + + try { + const response = await HttpLogic.values.http.post(route, { + body: JSON.stringify({ params }), + }); + actions.setCustomSourceData(response); + successCallback(); + } catch (e) { + flashAPIErrors(e); + if (errorCallback) errorCallback(); + throw new Error('Auth Error'); + } finally { + actions.setButtonNotLoading(); + } + }, + onUpdateSourceName: (name: string) => { + setSuccessMessage( + i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.flashMessages.contentSourceNameChanged', + { + defaultMessage: 'Successfully changed name to {sourceName}.', + values: { sourceName: name }, + } + ) + ); + }, + resetSourceState: () => { + FlashMessagesLogic.actions.clearFlashMessages(); + }, + }), +}); + +const setPage = (state: Meta, page: number) => ({ + ...state, + page: { + ...state.page, + current: page, + }, +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_logic.ts index eacba312d5da..5a8da7cd32fa 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_logic.ts @@ -8,6 +8,8 @@ import { cloneDeep, findIndex } from 'lodash'; import { kea, MakeLogicType } from 'kea'; +import { i18n } from '@kbn/i18n'; + import { HttpLogic } from '../../../shared/http'; import { @@ -208,10 +210,25 @@ export const SourcesLogic = kea>( } }, setAddedSource: ({ addedSourceName, additionalConfiguration }) => { + const successfullyConnectedMessage = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.flashMessages.contentSourceConnected', + { + defaultMessage: 'Successfully connected {sourceName}.', + values: { sourceName: addedSourceName }, + } + ); + + const additionalConfigurationMessage = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.flashMessages.additionalConfigurationNeeded', + { + defaultMessage: 'This source requires additional configuration.', + } + ); + setSuccessMessage( [ - `Successfully connected ${addedSourceName}.`, - additionalConfiguration ? 'This source requires additional configuration.' : '', + successfullyConnectedMessage, + additionalConfiguration ? additionalConfigurationMessage : '', ].join(' ') ); }, From 938b7624f711fe6ae5d527038028bab6e0be1ebd Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Wed, 18 Nov 2020 22:52:52 +0300 Subject: [PATCH 15/49] disable incremenetal build for legacy tsconfig.json (#82986) --- test/tsconfig.json | 2 +- tsconfig.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/tsconfig.json b/test/tsconfig.json index 2949a764d4b1..390e0b88c3d5 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "../tsconfig.base.json", "compilerOptions": { - "tsBuildInfoFile": "../build/tsbuildinfo/test", + "incremental": false, "types": ["node", "mocha", "flot"] }, "include": ["**/*", "../typings/elastic__node_crypto.d.ts", "typings/**/*"], diff --git a/tsconfig.json b/tsconfig.json index 00b33bd0b445..88ae3e1e826b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "./tsconfig.base.json", "compilerOptions": { - "tsBuildInfoFile": "./build/tsbuildinfo/kibana" + "incremental": false }, "include": ["kibana.d.ts", "src/**/*", "typings/**/*", "test_utils/**/*"], "exclude": [ From 4b603da9c6cd5f4638a87f06340b171e136c3dfb Mon Sep 17 00:00:00 2001 From: ymao1 Date: Wed, 18 Nov 2020 15:59:26 -0500 Subject: [PATCH 16/49] Not resetting server log level if level is defined (#83651) --- .../server_log/server_log_params.test.tsx | 4 +++- .../builtin_action_types/server_log/server_log_params.tsx | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/server_log/server_log_params.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/server_log/server_log_params.test.tsx index e8429a54b618..3243c37a04ee 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/server_log/server_log_params.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/server_log/server_log_params.test.tsx @@ -12,6 +12,7 @@ import { coreMock } from 'src/core/public/mocks'; describe('ServerLogParamsFields renders', () => { const mocks = coreMock.createSetup(); + const editAction = jest.fn(); test('all params fields is rendered', () => { const actionParams = { @@ -22,7 +23,7 @@ describe('ServerLogParamsFields renders', () => { {}} + editAction={editAction} index={0} defaultMessage={'test default message'} docLinks={{ ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' } as DocLinksStart} @@ -30,6 +31,7 @@ describe('ServerLogParamsFields renders', () => { http={mocks.http} /> ); + expect(editAction).not.toHaveBeenCalled(); expect(wrapper.find('[data-test-subj="loggingLevelSelect"]').length > 0).toBeTruthy(); expect( wrapper.find('[data-test-subj="loggingLevelSelect"]').first().prop('value') diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/server_log/server_log_params.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/server_log/server_log_params.tsx index a3619f96a45b..c4f434f13874 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/server_log/server_log_params.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/server_log/server_log_params.tsx @@ -25,7 +25,9 @@ export const ServerLogParamsFields: React.FunctionComponent { - editAction('level', 'info', index); + if (!actionParams?.level) { + editAction('level', 'info', index); + } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); From acc3e2f443e3c60dfc923aa1b3b179f34cf69804 Mon Sep 17 00:00:00 2001 From: ymao1 Date: Wed, 18 Nov 2020 16:02:31 -0500 Subject: [PATCH 17/49] [Alerting] Add `alert.updatedAt` field to represent date of last user edit (#83578) * Adding alert.updatedAt field that only updates on user edit * Updating unit tests * Functional tests * Updating alert attributes excluded from AAD * Fixing test * PR comments --- .../server/alerts_client/alerts_client.ts | 39 ++++++++--------- .../server/alerts_client/tests/create.test.ts | 7 +++ .../alerts_client/tests/disable.test.ts | 6 ++- .../server/alerts_client/tests/enable.test.ts | 6 ++- .../server/alerts_client/tests/find.test.ts | 1 + .../server/alerts_client/tests/get.test.ts | 1 + .../tests/get_alert_instance_summary.test.ts | 1 + .../alerts_client/tests/mute_all.test.ts | 5 ++- .../alerts_client/tests/mute_instance.test.ts | 5 ++- .../alerts_client/tests/unmute_all.test.ts | 5 ++- .../tests/unmute_instance.test.ts | 5 ++- .../server/alerts_client/tests/update.test.ts | 7 ++- .../tests/update_api_key.test.ts | 6 ++- .../alerts/server/saved_objects/index.ts | 2 + .../alerts/server/saved_objects/mappings.json | 3 ++ .../server/saved_objects/migrations.test.ts | 43 ++++++++++++++++++- .../alerts/server/saved_objects/migrations.ts | 20 +++++++++ .../partially_update_alert.test.ts | 1 + x-pack/plugins/alerts/server/types.ts | 1 + .../spaces_only/tests/alerting/create.ts | 1 + .../tests/alerting/execution_status.ts | 22 ++++++++++ .../spaces_only/tests/alerting/migrations.ts | 9 ++++ 22 files changed, 166 insertions(+), 30 deletions(-) diff --git a/x-pack/plugins/alerts/server/alerts_client/alerts_client.ts b/x-pack/plugins/alerts/server/alerts_client/alerts_client.ts index e97b37f16faf..c08ff9449d15 100644 --- a/x-pack/plugins/alerts/server/alerts_client/alerts_client.ts +++ b/x-pack/plugins/alerts/server/alerts_client/alerts_client.ts @@ -228,14 +228,17 @@ export class AlertsClient { this.validateActions(alertType, data.actions); + const createTime = Date.now(); const { references, actions } = await this.denormalizeActions(data.actions); + const rawAlert: RawAlert = { ...data, ...this.apiKeyAsAlertAttributes(createdAPIKey, username), actions, createdBy: username, updatedBy: username, - createdAt: new Date().toISOString(), + createdAt: new Date(createTime).toISOString(), + updatedAt: new Date(createTime).toISOString(), params: validatedAlertTypeParams as RawAlert['params'], muteAll: false, mutedInstanceIds: [], @@ -289,12 +292,7 @@ export class AlertsClient { }); createdAlert.attributes.scheduledTaskId = scheduledTask.id; } - return this.getAlertFromRaw( - createdAlert.id, - createdAlert.attributes, - createdAlert.updated_at, - references - ); + return this.getAlertFromRaw(createdAlert.id, createdAlert.attributes, references); } public async get({ id }: { id: string }): Promise { @@ -304,7 +302,7 @@ export class AlertsClient { result.attributes.consumer, ReadOperations.Get ); - return this.getAlertFromRaw(result.id, result.attributes, result.updated_at, result.references); + return this.getAlertFromRaw(result.id, result.attributes, result.references); } public async getAlertState({ id }: { id: string }): Promise { @@ -393,13 +391,11 @@ export class AlertsClient { type: 'alert', }); - // eslint-disable-next-line @typescript-eslint/naming-convention - const authorizedData = data.map(({ id, attributes, updated_at, references }) => { + const authorizedData = data.map(({ id, attributes, references }) => { ensureAlertTypeIsAuthorized(attributes.alertTypeId, attributes.consumer); return this.getAlertFromRaw( id, fields ? (pick(attributes, fields) as RawAlert) : attributes, - updated_at, references ); }); @@ -585,6 +581,7 @@ export class AlertsClient { params: validatedAlertTypeParams as RawAlert['params'], actions, updatedBy: username, + updatedAt: new Date().toISOString(), }); try { updatedObject = await this.unsecuredSavedObjectsClient.create( @@ -607,12 +604,7 @@ export class AlertsClient { throw e; } - return this.getPartialAlertFromRaw( - id, - updatedObject.attributes, - updatedObject.updated_at, - updatedObject.references - ); + return this.getPartialAlertFromRaw(id, updatedObject.attributes, updatedObject.references); } private apiKeyAsAlertAttributes( @@ -677,6 +669,7 @@ export class AlertsClient { await this.createAPIKey(this.generateAPIKeyName(attributes.alertTypeId, attributes.name)), username ), + updatedAt: new Date().toISOString(), updatedBy: username, }); try { @@ -751,6 +744,7 @@ export class AlertsClient { username ), updatedBy: username, + updatedAt: new Date().toISOString(), }); try { await this.unsecuredSavedObjectsClient.update('alert', id, updateAttributes, { version }); @@ -829,6 +823,7 @@ export class AlertsClient { apiKey: null, apiKeyOwner: null, updatedBy: await this.getUserName(), + updatedAt: new Date().toISOString(), }), { version } ); @@ -875,6 +870,7 @@ export class AlertsClient { muteAll: true, mutedInstanceIds: [], updatedBy: await this.getUserName(), + updatedAt: new Date().toISOString(), }); const updateOptions = { version }; @@ -913,6 +909,7 @@ export class AlertsClient { muteAll: false, mutedInstanceIds: [], updatedBy: await this.getUserName(), + updatedAt: new Date().toISOString(), }); const updateOptions = { version }; @@ -957,6 +954,7 @@ export class AlertsClient { this.updateMeta({ mutedInstanceIds, updatedBy: await this.getUserName(), + updatedAt: new Date().toISOString(), }), { version } ); @@ -999,6 +997,7 @@ export class AlertsClient { alertId, this.updateMeta({ updatedBy: await this.getUserName(), + updatedAt: new Date().toISOString(), mutedInstanceIds: mutedInstanceIds.filter((id: string) => id !== alertInstanceId), }), { version } @@ -1050,19 +1049,17 @@ export class AlertsClient { private getAlertFromRaw( id: string, rawAlert: RawAlert, - updatedAt: SavedObject['updated_at'], references: SavedObjectReference[] | undefined ): Alert { // In order to support the partial update API of Saved Objects we have to support // partial updates of an Alert, but when we receive an actual RawAlert, it is safe // to cast the result to an Alert - return this.getPartialAlertFromRaw(id, rawAlert, updatedAt, references) as Alert; + return this.getPartialAlertFromRaw(id, rawAlert, references) as Alert; } private getPartialAlertFromRaw( id: string, - { createdAt, meta, scheduledTaskId, ...rawAlert }: Partial, - updatedAt: SavedObject['updated_at'] = createdAt, + { createdAt, updatedAt, meta, scheduledTaskId, ...rawAlert }: Partial, references: SavedObjectReference[] | undefined ): PartialAlert { // Not the prettiest code here, but if we want to use most of the diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/create.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/create.test.ts index ee407b1a6d50..6d259029ac48 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/create.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/create.test.ts @@ -196,6 +196,7 @@ describe('create()', () => { createdAt: '2019-02-12T21:01:22.479Z', createdBy: 'elastic', updatedBy: 'elastic', + updatedAt: '2019-02-12T21:01:22.479Z', muteAll: false, mutedInstanceIds: [], actions: [ @@ -330,6 +331,7 @@ describe('create()', () => { "foo", ], "throttle": null, + "updatedAt": "2019-02-12T21:01:22.479Z", "updatedBy": "elastic", } `); @@ -418,6 +420,7 @@ describe('create()', () => { bar: true, }, createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), actions: [ { group: 'default', @@ -555,6 +558,7 @@ describe('create()', () => { bar: true, }, createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), actions: [ { group: 'default', @@ -631,6 +635,7 @@ describe('create()', () => { bar: true, }, createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), actions: [ { group: 'default', @@ -971,6 +976,7 @@ describe('create()', () => { createdBy: 'elastic', createdAt: '2019-02-12T21:01:22.479Z', updatedBy: 'elastic', + updatedAt: '2019-02-12T21:01:22.479Z', enabled: true, meta: { versionApiKeyLastmodified: 'v7.10.0', @@ -1092,6 +1098,7 @@ describe('create()', () => { createdBy: 'elastic', createdAt: '2019-02-12T21:01:22.479Z', updatedBy: 'elastic', + updatedAt: '2019-02-12T21:01:22.479Z', enabled: false, meta: { versionApiKeyLastmodified: 'v7.10.0', diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/disable.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/disable.test.ts index 11ce0027f82d..8c9ab9494a50 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/disable.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/disable.test.ts @@ -12,7 +12,7 @@ import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/s import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; import { AlertsAuthorization } from '../../authorization/alerts_authorization'; import { ActionsAuthorization } from '../../../../actions/server'; -import { getBeforeSetup } from './lib'; +import { getBeforeSetup, setGlobalDate } from './lib'; import { InvalidatePendingApiKey } from '../../types'; const taskManager = taskManagerMock.createStart(); @@ -45,6 +45,8 @@ beforeEach(() => { getBeforeSetup(alertsClientParams, taskManager, alertTypeRegistry); }); +setGlobalDate(); + describe('disable()', () => { let alertsClient: AlertsClient; const existingAlert = { @@ -136,6 +138,7 @@ describe('disable()', () => { scheduledTaskId: null, apiKey: null, apiKeyOwner: null, + updatedAt: '2019-02-12T21:01:22.479Z', updatedBy: 'elastic', actions: [ { @@ -190,6 +193,7 @@ describe('disable()', () => { scheduledTaskId: null, apiKey: null, apiKeyOwner: null, + updatedAt: '2019-02-12T21:01:22.479Z', updatedBy: 'elastic', actions: [ { diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/enable.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/enable.test.ts index 16e83c42d893..feec1d1b9334 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/enable.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/enable.test.ts @@ -13,7 +13,7 @@ import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; import { AlertsAuthorization } from '../../authorization/alerts_authorization'; import { ActionsAuthorization } from '../../../../actions/server'; import { TaskStatus } from '../../../../task_manager/server'; -import { getBeforeSetup } from './lib'; +import { getBeforeSetup, setGlobalDate } from './lib'; import { InvalidatePendingApiKey } from '../../types'; const taskManager = taskManagerMock.createStart(); @@ -46,6 +46,8 @@ beforeEach(() => { getBeforeSetup(alertsClientParams, taskManager, alertTypeRegistry); }); +setGlobalDate(); + describe('enable()', () => { let alertsClient: AlertsClient; const existingAlert = { @@ -186,6 +188,7 @@ describe('enable()', () => { meta: { versionApiKeyLastmodified: kibanaVersion, }, + updatedAt: '2019-02-12T21:01:22.479Z', updatedBy: 'elastic', apiKey: null, apiKeyOwner: null, @@ -292,6 +295,7 @@ describe('enable()', () => { apiKey: Buffer.from('123:abc').toString('base64'), apiKeyOwner: 'elastic', updatedBy: 'elastic', + updatedAt: '2019-02-12T21:01:22.479Z', actions: [ { group: 'default', diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/find.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/find.test.ts index 1b3a776bd23e..3d7473a74698 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/find.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/find.test.ts @@ -79,6 +79,7 @@ describe('find()', () => { bar: true, }, createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), actions: [ { group: 'default', diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/get.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/get.test.ts index 5c0d80f159b3..3f0c783f424d 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/get.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/get.test.ts @@ -59,6 +59,7 @@ describe('get()', () => { bar: true, }, createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), actions: [ { group: 'default', diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_instance_summary.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_instance_summary.test.ts index 269b2eb2ab7a..9bd61c0fe66d 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_instance_summary.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_instance_summary.test.ts @@ -76,6 +76,7 @@ const BaseAlertInstanceSummarySavedObject: SavedObject = { createdBy: null, updatedBy: null, createdAt: mockedDateString, + updatedAt: mockedDateString, apiKey: null, apiKeyOwner: null, throttle: null, diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/mute_all.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/mute_all.test.ts index 868fa3d8c6aa..14ebca213558 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/mute_all.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/mute_all.test.ts @@ -12,7 +12,7 @@ import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/s import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; import { AlertsAuthorization } from '../../authorization/alerts_authorization'; import { ActionsAuthorization } from '../../../../actions/server'; -import { getBeforeSetup } from './lib'; +import { getBeforeSetup, setGlobalDate } from './lib'; const taskManager = taskManagerMock.createStart(); const alertTypeRegistry = alertTypeRegistryMock.create(); @@ -43,6 +43,8 @@ beforeEach(() => { getBeforeSetup(alertsClientParams, taskManager, alertTypeRegistry); }); +setGlobalDate(); + describe('muteAll()', () => { test('mutes an alert', async () => { const alertsClient = new AlertsClient(alertsClientParams); @@ -74,6 +76,7 @@ describe('muteAll()', () => { { muteAll: true, mutedInstanceIds: [], + updatedAt: '2019-02-12T21:01:22.479Z', updatedBy: 'elastic', }, { diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/mute_instance.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/mute_instance.test.ts index 05ca741f480c..c2188f128cb4 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/mute_instance.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/mute_instance.test.ts @@ -12,7 +12,7 @@ import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/s import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; import { AlertsAuthorization } from '../../authorization/alerts_authorization'; import { ActionsAuthorization } from '../../../../actions/server'; -import { getBeforeSetup } from './lib'; +import { getBeforeSetup, setGlobalDate } from './lib'; const taskManager = taskManagerMock.createStart(); const alertTypeRegistry = alertTypeRegistryMock.create(); @@ -44,6 +44,8 @@ beforeEach(() => { getBeforeSetup(alertsClientParams, taskManager, alertTypeRegistry); }); +setGlobalDate(); + describe('muteInstance()', () => { test('mutes an alert instance', async () => { const alertsClient = new AlertsClient(alertsClientParams); @@ -68,6 +70,7 @@ describe('muteInstance()', () => { '1', { mutedInstanceIds: ['2'], + updatedAt: '2019-02-12T21:01:22.479Z', updatedBy: 'elastic', }, { diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/unmute_all.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/unmute_all.test.ts index 5ef1af9b6f0e..d92304ab873b 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/unmute_all.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/unmute_all.test.ts @@ -12,7 +12,7 @@ import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/s import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; import { AlertsAuthorization } from '../../authorization/alerts_authorization'; import { ActionsAuthorization } from '../../../../actions/server'; -import { getBeforeSetup } from './lib'; +import { getBeforeSetup, setGlobalDate } from './lib'; const taskManager = taskManagerMock.createStart(); const alertTypeRegistry = alertTypeRegistryMock.create(); @@ -44,6 +44,8 @@ beforeEach(() => { getBeforeSetup(alertsClientParams, taskManager, alertTypeRegistry); }); +setGlobalDate(); + describe('unmuteAll()', () => { test('unmutes an alert', async () => { const alertsClient = new AlertsClient(alertsClientParams); @@ -75,6 +77,7 @@ describe('unmuteAll()', () => { { muteAll: false, mutedInstanceIds: [], + updatedAt: '2019-02-12T21:01:22.479Z', updatedBy: 'elastic', }, { diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/unmute_instance.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/unmute_instance.test.ts index 88692239ac2f..3486df98f2f0 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/unmute_instance.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/unmute_instance.test.ts @@ -12,7 +12,7 @@ import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/s import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; import { AlertsAuthorization } from '../../authorization/alerts_authorization'; import { ActionsAuthorization } from '../../../../actions/server'; -import { getBeforeSetup } from './lib'; +import { getBeforeSetup, setGlobalDate } from './lib'; const taskManager = taskManagerMock.createStart(); const alertTypeRegistry = alertTypeRegistryMock.create(); @@ -44,6 +44,8 @@ beforeEach(() => { getBeforeSetup(alertsClientParams, taskManager, alertTypeRegistry); }); +setGlobalDate(); + describe('unmuteInstance()', () => { test('unmutes an alert instance', async () => { const alertsClient = new AlertsClient(alertsClientParams); @@ -69,6 +71,7 @@ describe('unmuteInstance()', () => { { mutedInstanceIds: [], updatedBy: 'elastic', + updatedAt: '2019-02-12T21:01:22.479Z', }, { version: '123' } ); diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/update.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/update.test.ts index ad58e36ade72..d0bb2607f7a4 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/update.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/update.test.ts @@ -140,8 +140,8 @@ describe('update()', () => { ], scheduledTaskId: 'task-123', createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), }, - updated_at: new Date().toISOString(), references: [ { name: 'action_0', @@ -300,6 +300,7 @@ describe('update()', () => { "foo", ], "throttle": null, + "updatedAt": "2019-02-12T21:01:22.479Z", "updatedBy": "elastic", } `); @@ -362,6 +363,7 @@ describe('update()', () => { bar: true, }, createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), actions: [ { group: 'default', @@ -484,6 +486,7 @@ describe('update()', () => { "foo", ], "throttle": "5m", + "updatedAt": "2019-02-12T21:01:22.479Z", "updatedBy": "elastic", } `); @@ -534,6 +537,7 @@ describe('update()', () => { bar: true, }, createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), actions: [ { group: 'default', @@ -648,6 +652,7 @@ describe('update()', () => { "foo", ], "throttle": "5m", + "updatedAt": "2019-02-12T21:01:22.479Z", "updatedBy": "elastic", } `); diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/update_api_key.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/update_api_key.test.ts index af178a1fac5f..ca5f44078f51 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/update_api_key.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/update_api_key.test.ts @@ -12,7 +12,7 @@ import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/s import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; import { AlertsAuthorization } from '../../authorization/alerts_authorization'; import { ActionsAuthorization } from '../../../../actions/server'; -import { getBeforeSetup } from './lib'; +import { getBeforeSetup, setGlobalDate } from './lib'; import { InvalidatePendingApiKey } from '../../types'; const taskManager = taskManagerMock.createStart(); @@ -44,6 +44,8 @@ beforeEach(() => { getBeforeSetup(alertsClientParams, taskManager, alertTypeRegistry); }); +setGlobalDate(); + describe('updateApiKey()', () => { let alertsClient: AlertsClient; const existingAlert = { @@ -113,6 +115,7 @@ describe('updateApiKey()', () => { apiKey: Buffer.from('234:abc').toString('base64'), apiKeyOwner: 'elastic', updatedBy: 'elastic', + updatedAt: '2019-02-12T21:01:22.479Z', actions: [ { group: 'default', @@ -162,6 +165,7 @@ describe('updateApiKey()', () => { enabled: true, apiKey: Buffer.from('234:abc').toString('base64'), apiKeyOwner: 'elastic', + updatedAt: '2019-02-12T21:01:22.479Z', updatedBy: 'elastic', actions: [ { diff --git a/x-pack/plugins/alerts/server/saved_objects/index.ts b/x-pack/plugins/alerts/server/saved_objects/index.ts index da30273e93c6..dfe122f56bc4 100644 --- a/x-pack/plugins/alerts/server/saved_objects/index.ts +++ b/x-pack/plugins/alerts/server/saved_objects/index.ts @@ -16,6 +16,7 @@ export const AlertAttributesExcludedFromAAD = [ 'muteAll', 'mutedInstanceIds', 'updatedBy', + 'updatedAt', 'executionStatus', ]; @@ -28,6 +29,7 @@ export type AlertAttributesExcludedFromAADType = | 'muteAll' | 'mutedInstanceIds' | 'updatedBy' + | 'updatedAt' | 'executionStatus'; export function setupSavedObjects( diff --git a/x-pack/plugins/alerts/server/saved_objects/mappings.json b/x-pack/plugins/alerts/server/saved_objects/mappings.json index a6c92080f18b..f40a7d9075ee 100644 --- a/x-pack/plugins/alerts/server/saved_objects/mappings.json +++ b/x-pack/plugins/alerts/server/saved_objects/mappings.json @@ -62,6 +62,9 @@ "createdAt": { "type": "date" }, + "updatedAt": { + "type": "date" + }, "apiKey": { "type": "binary" }, diff --git a/x-pack/plugins/alerts/server/saved_objects/migrations.test.ts b/x-pack/plugins/alerts/server/saved_objects/migrations.test.ts index 8c9d10769b18..a4cbc18e13b4 100644 --- a/x-pack/plugins/alerts/server/saved_objects/migrations.test.ts +++ b/x-pack/plugins/alerts/server/saved_objects/migrations.test.ts @@ -261,8 +261,48 @@ describe('7.10.0 migrates with failure', () => { }); }); +describe('7.11.0', () => { + beforeEach(() => { + jest.resetAllMocks(); + encryptedSavedObjectsSetup.createMigration.mockImplementation( + (shouldMigrateWhenPredicate, migration) => migration + ); + }); + + test('add updatedAt field to alert - set to SavedObject updated_at attribute', () => { + const migration711 = getMigrations(encryptedSavedObjectsSetup)['7.11.0']; + const alert = getMockData({}, true); + expect(migration711(alert, { log })).toEqual({ + ...alert, + attributes: { + ...alert.attributes, + updatedAt: alert.updated_at, + }, + }); + }); + + test('add updatedAt field to alert - set to createdAt when SavedObject updated_at is not defined', () => { + const migration711 = getMigrations(encryptedSavedObjectsSetup)['7.11.0']; + const alert = getMockData({}); + expect(migration711(alert, { log })).toEqual({ + ...alert, + attributes: { + ...alert.attributes, + updatedAt: alert.attributes.createdAt, + }, + }); + }); +}); + +function getUpdatedAt(): string { + const updatedAt = new Date(); + updatedAt.setHours(updatedAt.getHours() + 2); + return updatedAt.toISOString(); +} + function getMockData( - overwrites: Record = {} + overwrites: Record = {}, + withSavedObjectUpdatedAt: boolean = false ): SavedObjectUnsanitizedDoc> { return { attributes: { @@ -295,6 +335,7 @@ function getMockData( ], ...overwrites, }, + updated_at: withSavedObjectUpdatedAt ? getUpdatedAt() : undefined, id: uuid.v4(), type: 'alert', }; diff --git a/x-pack/plugins/alerts/server/saved_objects/migrations.ts b/x-pack/plugins/alerts/server/saved_objects/migrations.ts index 0b2c86b84f67..d8ebced03c5a 100644 --- a/x-pack/plugins/alerts/server/saved_objects/migrations.ts +++ b/x-pack/plugins/alerts/server/saved_objects/migrations.ts @@ -37,8 +37,15 @@ export function getMigrations( ) ); + const migrationAlertUpdatedAtDate = encryptedSavedObjects.createMigration( + // migrate all documents in 7.11 in order to add the "updatedAt" field + (doc): doc is SavedObjectUnsanitizedDoc => true, + pipeMigrations(setAlertUpdatedAtDate) + ); + return { '7.10.0': executeMigrationWithErrorHandling(migrationWhenRBACWasIntroduced, '7.10.0'), + '7.11.0': executeMigrationWithErrorHandling(migrationAlertUpdatedAtDate, '7.11.0'), }; } @@ -59,6 +66,19 @@ function executeMigrationWithErrorHandling( }; } +const setAlertUpdatedAtDate = ( + doc: SavedObjectUnsanitizedDoc +): SavedObjectUnsanitizedDoc => { + const updatedAt = doc.updated_at || doc.attributes.createdAt; + return { + ...doc, + attributes: { + ...doc.attributes, + updatedAt, + }, + }; +}; + const consumersToChange: Map = new Map( Object.entries({ alerting: 'alerts', diff --git a/x-pack/plugins/alerts/server/saved_objects/partially_update_alert.test.ts b/x-pack/plugins/alerts/server/saved_objects/partially_update_alert.test.ts index 50815c797e39..8041ec551bb0 100644 --- a/x-pack/plugins/alerts/server/saved_objects/partially_update_alert.test.ts +++ b/x-pack/plugins/alerts/server/saved_objects/partially_update_alert.test.ts @@ -95,6 +95,7 @@ const DefaultAttributes = { muteAll: true, mutedInstanceIds: ['muted-instance-id-1', 'muted-instance-id-2'], updatedBy: 'someone', + updatedAt: '2019-02-12T21:01:22.479Z', }; const InvalidAttributes = { ...DefaultAttributes, foo: 'bar' }; diff --git a/x-pack/plugins/alerts/server/types.ts b/x-pack/plugins/alerts/server/types.ts index dde162815665..4ccf251540a1 100644 --- a/x-pack/plugins/alerts/server/types.ts +++ b/x-pack/plugins/alerts/server/types.ts @@ -148,6 +148,7 @@ export interface RawAlert extends SavedObjectAttributes { createdBy: string | null; updatedBy: string | null; createdAt: string; + updatedAt: string; apiKey: string | null; apiKeyOwner: string | null; throttle: string | null; diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts index 41f6b66c30aa..cf7fc9edd952 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts @@ -91,6 +91,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { }); expect(Date.parse(response.body.createdAt)).to.be.greaterThan(0); expect(Date.parse(response.body.updatedAt)).to.be.greaterThan(0); + expect(Date.parse(response.body.updatedAt)).to.eql(Date.parse(response.body.createdAt)); expect(typeof response.body.scheduledTaskId).to.be('string'); const { _source: taskRecord } = await getScheduledTask(response.body.scheduledTaskId); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/execution_status.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/execution_status.ts index 5ebce8edf6fb..642173a7c2c6 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/execution_status.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/execution_status.ts @@ -63,6 +63,7 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon ); expect(response.status).to.eql(200); const alertId = response.body.id; + const alertUpdatedAt = response.body.updatedAt; dates.push(response.body.executionStatus.lastExecutionDate); objectRemover.add(Spaces.space1.id, alertId, 'alert', 'alerts'); @@ -70,6 +71,7 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon dates.push(executionStatus.lastExecutionDate); dates.push(Date.now()); ensureDatetimesAreOrdered(dates); + ensureAlertUpdatedAtHasNotChanged(alertId, alertUpdatedAt); // Ensure AAD isn't broken await checkAAD({ @@ -97,6 +99,7 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon ); expect(response.status).to.eql(200); const alertId = response.body.id; + const alertUpdatedAt = response.body.updatedAt; dates.push(response.body.executionStatus.lastExecutionDate); objectRemover.add(Spaces.space1.id, alertId, 'alert', 'alerts'); @@ -104,6 +107,7 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon dates.push(executionStatus.lastExecutionDate); dates.push(Date.now()); ensureDatetimesAreOrdered(dates); + ensureAlertUpdatedAtHasNotChanged(alertId, alertUpdatedAt); // Ensure AAD isn't broken await checkAAD({ @@ -128,6 +132,7 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon ); expect(response.status).to.eql(200); const alertId = response.body.id; + const alertUpdatedAt = response.body.updatedAt; dates.push(response.body.executionStatus.lastExecutionDate); objectRemover.add(Spaces.space1.id, alertId, 'alert', 'alerts'); @@ -135,6 +140,7 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon dates.push(executionStatus.lastExecutionDate); dates.push(Date.now()); ensureDatetimesAreOrdered(dates); + ensureAlertUpdatedAtHasNotChanged(alertId, alertUpdatedAt); // Ensure AAD isn't broken await checkAAD({ @@ -162,12 +168,14 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon ); expect(response.status).to.eql(200); const alertId = response.body.id; + const alertUpdatedAt = response.body.updatedAt; objectRemover.add(Spaces.space1.id, alertId, 'alert', 'alerts'); const executionStatus = await waitForStatus(alertId, new Set(['error'])); expect(executionStatus.error).to.be.ok(); expect(executionStatus.error.reason).to.be('execute'); expect(executionStatus.error.message).to.be('this alert is intended to fail'); + ensureAlertUpdatedAtHasNotChanged(alertId, alertUpdatedAt); }); it('should eventually have error reason "unknown" when appropriate', async () => { @@ -183,6 +191,7 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon ); expect(response.status).to.eql(200); const alertId = response.body.id; + const alertUpdatedAt = response.body.updatedAt; objectRemover.add(Spaces.space1.id, alertId, 'alert', 'alerts'); let executionStatus = await waitForStatus(alertId, new Set(['ok'])); @@ -201,6 +210,7 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon executionStatus = await waitForStatus(alertId, new Set(['error'])); expect(executionStatus.error).to.be.ok(); expect(executionStatus.error.reason).to.be('unknown'); + ensureAlertUpdatedAtHasNotChanged(alertId, alertUpdatedAt); const message = 'params invalid: [param1]: expected value of type [string] but got [number]'; expect(executionStatus.error.message).to.be(message); @@ -306,6 +316,18 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon await delay(WaitForStatusIncrement); return await waitForStatus(id, statuses, waitMillis - WaitForStatusIncrement); } + + async function ensureAlertUpdatedAtHasNotChanged(alertId: string, originalUpdatedAt: string) { + const response = await supertest.get( + `${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${alertId}` + ); + const { updatedAt, executionStatus } = response.body; + expect(Date.parse(updatedAt)).to.be.greaterThan(0); + expect(Date.parse(updatedAt)).to.eql(Date.parse(originalUpdatedAt)); + expect(Date.parse(executionStatus.lastExecutionDate)).to.be.greaterThan( + Date.parse(originalUpdatedAt) + ); + } } function expectErrorExecutionStatus(executionStatus: Record, startDate: number) { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/migrations.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/migrations.ts index 17070a14069c..bd6afacf206d 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/migrations.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/migrations.ts @@ -82,5 +82,14 @@ export default function createGetTests({ getService }: FtrProviderContext) { }, ]); }); + + it('7.11.0 migrates alerts to contain `updatedAt` field', async () => { + const response = await supertest.get( + `${getUrlPrefix(``)}/api/alerts/alert/74f3e6d7-b7bb-477d-ac28-92ee22728e6e` + ); + + expect(response.status).to.eql(200); + expect(response.body.updatedAt).to.eql('2020-06-17T15:35:39.839Z'); + }); }); } From 3651748b77472754dedea50a83acc2c6e53a5ffe Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko Date: Wed, 18 Nov 2020 13:13:50 -0800 Subject: [PATCH 18/49] Fixed console error, which appears when saving changes in Edit Alert flyout (#83610) --- .../alert_details/components/alert_details.tsx | 12 ++++++++++-- .../sections/alert_form/alert_edit.tsx | 17 ++++------------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.tsx index abd812796256..603058e6fcb5 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useState, Fragment, useEffect } from 'react'; +import React, { useState, Fragment, useEffect, useReducer } from 'react'; import { keyBy } from 'lodash'; import { useHistory } from 'react-router-dom'; import { @@ -41,6 +41,7 @@ import { AlertEdit } from '../../alert_form'; import { AlertsContextProvider } from '../../../context/alerts_context'; import { routeToAlertDetails } from '../../../constants'; import { alertsErrorReasonTranslationsMapping } from '../../alerts_list/translations'; +import { alertReducer } from '../../alert_form/alert_reducer'; type AlertDetailsProps = { alert: Alert; @@ -73,6 +74,10 @@ export const AlertDetails: React.FunctionComponent = ({ setBreadcrumbs, chrome, } = useAppDependencies(); + const [{}, dispatch] = useReducer(alertReducer, { alert }); + const setInitialAlert = (key: string, value: any) => { + dispatch({ command: { type: 'setAlert' }, payload: { key, value } }); + }; // Set breadcrumb and page title useEffect(() => { @@ -166,7 +171,10 @@ export const AlertDetails: React.FunctionComponent = ({ > setEditFlyoutVisibility(false)} + onClose={() => { + setInitialAlert('alert', alert); + setEditFlyoutVisibility(false); + }} /> )} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.tsx index 5eadc742a9dc..d5ae701546c6 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.tsx @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import React, { Fragment, useCallback, useReducer, useState } from 'react'; +import React, { Fragment, useReducer, useState } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiTitle, @@ -40,9 +40,6 @@ export const AlertEdit = ({ initialAlert, onClose }: AlertEditProps) => { const [hasActionsWithBrokenConnector, setHasActionsWithBrokenConnector] = useState( false ); - const setAlert = (key: string, value: any) => { - dispatch({ command: { type: 'setAlert' }, payload: { key, value } }); - }; const { reloadAlerts, @@ -53,12 +50,6 @@ export const AlertEdit = ({ initialAlert, onClose }: AlertEditProps) => { docLinks, } = useAlertsContext(); - const closeFlyout = useCallback(() => { - onClose(); - setAlert('alert', initialAlert); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [onClose]); - const alertType = alertTypeRegistry.get(alert.alertTypeId); const errors = { @@ -105,7 +96,7 @@ export const AlertEdit = ({ initialAlert, onClose }: AlertEditProps) => { return ( onClose()} aria-labelledby="flyoutAlertEditTitle" size="m" maxWidth={620} @@ -155,7 +146,7 @@ export const AlertEdit = ({ initialAlert, onClose }: AlertEditProps) => { onClose()} > {i18n.translate( 'xpack.triggersActionsUI.sections.alertEdit.cancelButtonLabel', @@ -179,7 +170,7 @@ export const AlertEdit = ({ initialAlert, onClose }: AlertEditProps) => { const savedAlert = await onSaveAlert(); setIsSaving(false); if (savedAlert) { - closeFlyout(); + onClose(); if (reloadAlerts) { reloadAlerts(); } From a2d288d134cf86458fc821b9ae07f6004ee2de22 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 18 Nov 2020 21:46:42 +0000 Subject: [PATCH 19/49] fix(NA): search examples kibana version declaration (#83182) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- examples/search_examples/kibana.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/search_examples/kibana.json b/examples/search_examples/kibana.json index 7e392b841736..9577ec353a4c 100644 --- a/examples/search_examples/kibana.json +++ b/examples/search_examples/kibana.json @@ -1,6 +1,7 @@ { "id": "searchExamples", - "version": "8.0.0", + "version": "0.0.1", + "kibanaVersion": "kibana", "server": true, "ui": true, "requiredPlugins": ["navigation", "data", "developerExamples"], From 0546f98070943e8750398e64dba1ff8a07e894c3 Mon Sep 17 00:00:00 2001 From: Aaron Caldwell Date: Wed, 18 Nov 2020 14:47:46 -0700 Subject: [PATCH 20/49] [Maps] Add query bar inputs to geo threshold alerts tracked points & boundaries (#80871) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/plugins/stack_alerts/kibana.json | 2 +- ...eshold_alert_type_expression.test.tsx.snap | 210 ++++++++++++++++++ ...o_threshold_alert_type_expression.test.tsx | 94 ++++++++ .../geo_threshold/query_builder/index.tsx | 67 ++++++ .../public/alert_types/geo_threshold/types.ts | 4 + .../alert_types/geo_threshold/alert_type.ts | 5 + .../geo_threshold/es_query_builder.ts | 80 +++++-- .../geo_threshold/geo_threshold.ts | 3 +- .../tests/es_query_builder.test.ts | 67 ++++++ .../plugins/triggers_actions_ui/kibana.json | 4 +- .../public/application/app.tsx | 8 +- .../public/application/app_context.tsx | 7 +- .../public/application/boot.tsx | 9 +- .../actions_connectors_list.test.tsx | 10 +- .../components/alert_details.test.tsx | 2 +- .../components/alert_details.tsx | 8 +- .../sections/alert_form/alert_add.test.tsx | 2 +- .../components/alerts_list.test.tsx | 8 +- .../alerts_list/components/alerts_list.tsx | 8 +- .../public/application/test_utils/index.ts | 12 +- .../triggers_actions_ui/public/index.ts | 1 + .../triggers_actions_ui/public/plugin.ts | 6 +- 22 files changed, 559 insertions(+), 58 deletions(-) create mode 100644 x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/query_builder/__snapshots__/geo_threshold_alert_type_expression.test.tsx.snap create mode 100644 x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/query_builder/geo_threshold_alert_type_expression.test.tsx create mode 100644 x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/tests/es_query_builder.test.ts diff --git a/x-pack/plugins/stack_alerts/kibana.json b/x-pack/plugins/stack_alerts/kibana.json index b7405c38d161..884d33ef669e 100644 --- a/x-pack/plugins/stack_alerts/kibana.json +++ b/x-pack/plugins/stack_alerts/kibana.json @@ -3,7 +3,7 @@ "server": true, "version": "8.0.0", "kibanaVersion": "kibana", - "requiredPlugins": ["alerts", "features", "triggersActionsUi", "kibanaReact"], + "requiredPlugins": ["alerts", "features", "triggersActionsUi", "kibanaReact", "savedObjects", "data"], "configPath": ["xpack", "stack_alerts"], "ui": true } diff --git a/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/query_builder/__snapshots__/geo_threshold_alert_type_expression.test.tsx.snap b/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/query_builder/__snapshots__/geo_threshold_alert_type_expression.test.tsx.snap new file mode 100644 index 000000000000..dae168417b0b --- /dev/null +++ b/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/query_builder/__snapshots__/geo_threshold_alert_type_expression.test.tsx.snap @@ -0,0 +1,210 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render BoundaryIndexExpression 1`] = ` + + + + + + + + + + + + } +/> +`; + +exports[`should render EntityIndexExpression 1`] = ` + + + + + + } + labelType="label" + > + + + + + + + } +/> +`; + +exports[`should render EntityIndexExpression w/ invalid flag if invalid 1`] = ` + + + + + + } + labelType="label" + > + + + + + + + } +/> +`; diff --git a/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/query_builder/geo_threshold_alert_type_expression.test.tsx b/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/query_builder/geo_threshold_alert_type_expression.test.tsx new file mode 100644 index 000000000000..d115dbeb76e3 --- /dev/null +++ b/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/query_builder/geo_threshold_alert_type_expression.test.tsx @@ -0,0 +1,94 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; +import { EntityIndexExpression } from './expressions/entity_index_expression'; +import { BoundaryIndexExpression } from './expressions/boundary_index_expression'; +import { ApplicationStart, DocLinksStart, HttpSetup, ToastsStart } from 'kibana/public'; +import { + ActionTypeRegistryContract, + AlertTypeRegistryContract, + IErrorObject, +} from '../../../../../triggers_actions_ui/public'; +import { IIndexPattern } from '../../../../../../../src/plugins/data/common'; + +const alertsContext = { + http: (null as unknown) as HttpSetup, + alertTypeRegistry: (null as unknown) as AlertTypeRegistryContract, + actionTypeRegistry: (null as unknown) as ActionTypeRegistryContract, + toastNotifications: (null as unknown) as ToastsStart, + docLinks: (null as unknown) as DocLinksStart, + capabilities: (null as unknown) as ApplicationStart['capabilities'], +}; + +const alertParams = { + index: '', + indexId: '', + geoField: '', + entity: '', + dateField: '', + trackingEvent: '', + boundaryType: '', + boundaryIndexTitle: '', + boundaryIndexId: '', + boundaryGeoField: '', +}; + +test('should render EntityIndexExpression', async () => { + const component = shallow( + {}} + setAlertParamsGeoField={() => {}} + setAlertProperty={() => {}} + setIndexPattern={() => {}} + indexPattern={('' as unknown) as IIndexPattern} + isInvalid={false} + /> + ); + + expect(component).toMatchSnapshot(); +}); + +test('should render EntityIndexExpression w/ invalid flag if invalid', async () => { + const component = shallow( + {}} + setAlertParamsGeoField={() => {}} + setAlertProperty={() => {}} + setIndexPattern={() => {}} + indexPattern={('' as unknown) as IIndexPattern} + isInvalid={true} + /> + ); + + expect(component).toMatchSnapshot(); +}); + +test('should render BoundaryIndexExpression', async () => { + const component = shallow( + {}} + setBoundaryGeoField={() => {}} + setBoundaryNameField={() => {}} + boundaryNameField={'testNameField'} + /> + ); + + expect(component).toMatchSnapshot(); +}); diff --git a/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/query_builder/index.tsx b/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/query_builder/index.tsx index f138c08c0f99..623223d66ea0 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/query_builder/index.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/query_builder/index.tsx @@ -30,6 +30,12 @@ import { EntityIndexExpression } from './expressions/entity_index_expression'; import { EntityByExpression } from './expressions/entity_by_expression'; import { BoundaryIndexExpression } from './expressions/boundary_index_expression'; import { IIndexPattern } from '../../../../../../../src/plugins/data/common/index_patterns'; +import { + esQuery, + esKuery, + Query, + QueryStringInput, +} from '../../../../../../../src/plugins/data/public'; const DEFAULT_VALUES = { TRACKING_EVENT: '', @@ -67,6 +73,18 @@ const labelForDelayOffset = ( ); +function validateQuery(query: Query) { + try { + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + query.language === 'kuery' + ? esKuery.fromKueryExpression(query.query) + : esQuery.luceneStringToDsl(query.query); + } catch (err) { + return false; + } + return true; +} + export const GeoThresholdAlertTypeExpression: React.FunctionComponent( + indexQuery || { + query: '', + language: 'kuery', + } + ); const [boundaryIndexPattern, _setBoundaryIndexPattern] = useState({ id: '', fields: [], @@ -118,6 +144,12 @@ export const GeoThresholdAlertTypeExpression: React.FunctionComponent( + boundaryIndexQuery || { + query: '', + language: 'kuery', + } + ); const [delayOffset, _setDelayOffset] = useState(0); function setDelayOffset(_delayOffset: number) { setAlertParams('delayOffsetWithUnits', `${_delayOffset}${delayOffsetUnit}`); @@ -248,6 +280,23 @@ export const GeoThresholdAlertTypeExpression: React.FunctionComponent + + + { + if (query.language) { + if (validateQuery(query)) { + setAlertParams('indexQuery', query); + } + setIndexQueryInput(query); + } + }} + /> + @@ -313,6 +362,24 @@ export const GeoThresholdAlertTypeExpression: React.FunctionComponent + + + { + if (query.language) { + if (validateQuery(query)) { + setAlertParams('boundaryIndexQuery', query); + } + setBoundaryIndexQueryInput(query); + } + }} + /> + + ); }; diff --git a/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/types.ts b/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/types.ts index 0358fcd66a46..86faa4ed2fb4 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/types.ts +++ b/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/types.ts @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +import { Query } from '../../../../../../src/plugins/data/common'; + export enum TrackingEvent { entered = 'entered', exited = 'exited', @@ -22,6 +24,8 @@ export interface GeoThresholdAlertParams { boundaryGeoField: string; boundaryNameField?: string; delayOffsetWithUnits?: string; + indexQuery?: Query; + boundaryIndexQuery?: Query; } // Will eventually include 'geo_shape' diff --git a/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/alert_type.ts b/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/alert_type.ts index 9fc46fe2f258..0c40f5b5f386 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/alert_type.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/alert_type.ts @@ -15,6 +15,7 @@ import { ActionVariable, AlertTypeState, } from '../../../../alerts/server'; +import { Query } from '../../../../../../src/plugins/data/common/query'; export const GEO_THRESHOLD_ID = '.geo-threshold'; export type TrackingEvent = 'entered' | 'exited'; @@ -155,6 +156,8 @@ export const ParamsSchema = schema.object({ boundaryGeoField: schema.string({ minLength: 1 }), boundaryNameField: schema.maybe(schema.string({ minLength: 1 })), delayOffsetWithUnits: schema.maybe(schema.string({ minLength: 1 })), + indexQuery: schema.maybe(schema.any({})), + boundaryIndexQuery: schema.maybe(schema.any({})), }); export interface GeoThresholdParams { @@ -170,6 +173,8 @@ export interface GeoThresholdParams { boundaryGeoField: string; boundaryNameField?: string; delayOffsetWithUnits?: string; + indexQuery?: Query; + boundaryIndexQuery?: Query; } export function getAlertType( diff --git a/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/es_query_builder.ts b/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/es_query_builder.ts index 97be51b2a625..02ac19e7b6f1 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/es_query_builder.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/es_query_builder.ts @@ -7,6 +7,13 @@ import { ILegacyScopedClusterClient } from 'kibana/server'; import { SearchResponse } from 'elasticsearch'; import { Logger } from 'src/core/server'; +import { + Query, + IIndexPattern, + fromKueryExpression, + toElasticsearchQuery, + luceneStringToDsl, +} from '../../../../../../src/plugins/data/common'; export const OTHER_CATEGORY = 'other'; // Consider dynamically obtaining from config? @@ -14,6 +21,19 @@ const MAX_TOP_LEVEL_QUERY_SIZE = 0; const MAX_SHAPES_QUERY_SIZE = 10000; const MAX_BUCKETS_LIMIT = 65535; +export const getEsFormattedQuery = (query: Query, indexPattern?: IIndexPattern) => { + let esFormattedQuery; + + const queryLanguage = query.language; + if (queryLanguage === 'kuery') { + const ast = fromKueryExpression(query.query); + esFormattedQuery = toElasticsearchQuery(ast, indexPattern); + } else { + esFormattedQuery = luceneStringToDsl(query.query); + } + return esFormattedQuery; +}; + export async function getShapesFilters( boundaryIndexTitle: string, boundaryGeoField: string, @@ -21,7 +41,8 @@ export async function getShapesFilters( callCluster: ILegacyScopedClusterClient['callAsCurrentUser'], log: Logger, alertId: string, - boundaryNameField?: string + boundaryNameField?: string, + boundaryIndexQuery?: Query ) { const filters: Record = {}; const shapesIdsNamesMap: Record = {}; @@ -30,8 +51,10 @@ export async function getShapesFilters( index: boundaryIndexTitle, body: { size: MAX_SHAPES_QUERY_SIZE, + ...(boundaryIndexQuery ? { query: getEsFormattedQuery(boundaryIndexQuery) } : {}), }, }); + boundaryData.hits.hits.forEach(({ _index, _id }) => { filters[_id] = { geo_shape: { @@ -66,6 +89,7 @@ export async function executeEsQueryFactory( boundaryGeoField, geoField, boundaryIndexTitle, + indexQuery, }: { entity: string; index: string; @@ -74,6 +98,7 @@ export async function executeEsQueryFactory( geoField: string; boundaryIndexTitle: string; boundaryNameField?: string; + indexQuery?: Query; }, { callCluster }: { callCluster: ILegacyScopedClusterClient['callAsCurrentUser'] }, log: Logger, @@ -83,6 +108,19 @@ export async function executeEsQueryFactory( gteDateTime: Date | null, ltDateTime: Date | null ): Promise | undefined> => { + let esFormattedQuery; + if (indexQuery) { + const gteEpochDateTime = gteDateTime ? new Date(gteDateTime).getTime() : null; + const ltEpochDateTime = ltDateTime ? new Date(ltDateTime).getTime() : null; + const dateRangeUpdatedQuery = + indexQuery.language === 'kuery' + ? `(${dateField} >= "${gteEpochDateTime}" and ${dateField} < "${ltEpochDateTime}") and (${indexQuery.query})` + : `(${dateField}:[${gteDateTime} TO ${ltDateTime}]) AND (${indexQuery.query})`; + esFormattedQuery = getEsFormattedQuery({ + query: dateRangeUpdatedQuery, + language: indexQuery.language, + }); + } // eslint-disable-next-line @typescript-eslint/no-explicit-any const esQuery: Record = { index, @@ -120,27 +158,29 @@ export async function executeEsQueryFactory( }, }, }, - query: { - bool: { - must: [], - filter: [ - { - match_all: {}, - }, - { - range: { - [dateField]: { - ...(gteDateTime ? { gte: gteDateTime } : {}), - lt: ltDateTime, // 'less than' to prevent overlap between intervals - format: 'strict_date_optional_time', + query: esFormattedQuery + ? esFormattedQuery + : { + bool: { + must: [], + filter: [ + { + match_all: {}, }, - }, + { + range: { + [dateField]: { + ...(gteDateTime ? { gte: gteDateTime } : {}), + lt: ltDateTime, // 'less than' to prevent overlap between intervals + format: 'strict_date_optional_time', + }, + }, + }, + ], + should: [], + must_not: [], }, - ], - should: [], - must_not: [], - }, - }, + }, stored_fields: ['*'], docvalue_fields: [ { diff --git a/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/geo_threshold.ts b/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/geo_threshold.ts index e223cdb7ea54..8247cc787d36 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/geo_threshold.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/geo_threshold.ts @@ -194,7 +194,8 @@ export const getGeoThresholdExecutor = (log: Logger) => services.callCluster, log, alertId, - params.boundaryNameField + params.boundaryNameField, + params.boundaryIndexQuery ); const executeEsQuery = await executeEsQueryFactory(params, services, log, shapesFilters); diff --git a/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/tests/es_query_builder.test.ts b/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/tests/es_query_builder.test.ts new file mode 100644 index 000000000000..d577a88e8e2f --- /dev/null +++ b/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/tests/es_query_builder.test.ts @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { getEsFormattedQuery } from '../es_query_builder'; + +describe('esFormattedQuery', () => { + it('lucene queries are converted correctly', async () => { + const testLuceneQuery1 = { + query: `"airport": "Denver"`, + language: 'lucene', + }; + const esFormattedQuery1 = getEsFormattedQuery(testLuceneQuery1); + expect(esFormattedQuery1).toStrictEqual({ query_string: { query: '"airport": "Denver"' } }); + const testLuceneQuery2 = { + query: `title:"Fun with turnips" AND text:Cabbage, cabbage and more cabbage!`, + language: 'lucene', + }; + const esFormattedQuery2 = getEsFormattedQuery(testLuceneQuery2); + expect(esFormattedQuery2).toStrictEqual({ + query_string: { + query: `title:"Fun with turnips" AND text:Cabbage, cabbage and more cabbage!`, + }, + }); + }); + + it('kuery queries are converted correctly', async () => { + const testKueryQuery1 = { + query: `"airport": "Denver"`, + language: 'kuery', + }; + const esFormattedQuery1 = getEsFormattedQuery(testKueryQuery1); + expect(esFormattedQuery1).toStrictEqual({ + bool: { minimum_should_match: 1, should: [{ match_phrase: { airport: 'Denver' } }] }, + }); + const testKueryQuery2 = { + query: `"airport": "Denver" and ("animal": "goat" or "animal": "narwhal")`, + language: 'kuery', + }; + const esFormattedQuery2 = getEsFormattedQuery(testKueryQuery2); + expect(esFormattedQuery2).toStrictEqual({ + bool: { + filter: [ + { bool: { should: [{ match_phrase: { airport: 'Denver' } }], minimum_should_match: 1 } }, + { + bool: { + should: [ + { + bool: { should: [{ match_phrase: { animal: 'goat' } }], minimum_should_match: 1 }, + }, + { + bool: { + should: [{ match_phrase: { animal: 'narwhal' } }], + minimum_should_match: 1, + }, + }, + ], + minimum_should_match: 1, + }, + }, + ], + }, + }); + }); +}); diff --git a/x-pack/plugins/triggers_actions_ui/kibana.json b/x-pack/plugins/triggers_actions_ui/kibana.json index 9d79ab9232bf..ab2d6c6a3c40 100644 --- a/x-pack/plugins/triggers_actions_ui/kibana.json +++ b/x-pack/plugins/triggers_actions_ui/kibana.json @@ -4,8 +4,8 @@ "server": true, "ui": true, "optionalPlugins": ["alerts", "features", "home"], - "requiredPlugins": ["management", "charts", "data"], + "requiredPlugins": ["management", "charts", "data", "kibanaReact", "savedObjects"], "configPath": ["xpack", "trigger_actions_ui"], "extraPublicDirs": ["public/common", "public/common/constants"], - "requiredBundles": ["home", "alerts", "esUiShared"] + "requiredBundles": ["home", "alerts", "esUiShared", "kibanaReact", "kibanaUtils"] } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/app.tsx b/x-pack/plugins/triggers_actions_ui/public/application/app.tsx index 5c1e0aa0100e..fa38c4501379 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/app.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/app.tsx @@ -15,6 +15,7 @@ import { ChromeBreadcrumb, CoreStart, ScopedHistory, + SavedObjectsClientContract, } from 'kibana/public'; import { KibanaFeature } from '../../../features/common'; import { Section, routeToAlertDetails } from './constants'; @@ -24,6 +25,7 @@ import { ChartsPluginStart } from '../../../../../src/plugins/charts/public'; import { DataPublicPluginStart } from '../../../../../src/plugins/data/public'; import { PluginStartContract as AlertingStart } from '../../../alerts/public'; import { suspendedComponentWithProps } from './lib/suspended_component_with_props'; +import { Storage } from '../../../../../src/plugins/kibana_utils/public'; const TriggersActionsUIHome = lazy(async () => import('./home')); const AlertDetailsRoute = lazy( @@ -31,13 +33,14 @@ const AlertDetailsRoute = lazy( ); export interface AppDeps { - dataPlugin: DataPublicPluginStart; + data: DataPublicPluginStart; charts: ChartsPluginStart; chrome: ChromeStart; alerts?: AlertingStart; navigateToApp: CoreStart['application']['navigateToApp']; docLinks: DocLinksStart; toastNotifications: ToastsSetup; + storage?: Storage; http: HttpSetup; uiSettings: IUiSettingsClient; setBreadcrumbs: (crumbs: ChromeBreadcrumb[]) => void; @@ -45,6 +48,9 @@ export interface AppDeps { actionTypeRegistry: ActionTypeRegistryContract; alertTypeRegistry: AlertTypeRegistryContract; history: ScopedHistory; + savedObjects?: { + client: SavedObjectsClientContract; + }; kibanaFeatures: KibanaFeature[]; } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/app_context.tsx b/x-pack/plugins/triggers_actions_ui/public/application/app_context.tsx index bf2e0c7274e7..a4568d069c21 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/app_context.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/app_context.tsx @@ -5,6 +5,7 @@ */ import React, { createContext, useContext } from 'react'; +import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public'; import { AppDeps } from './app'; const AppContext = createContext(null); @@ -16,7 +17,11 @@ export const AppContextProvider = ({ appDeps: AppDeps | null; children: React.ReactNode; }) => { - return appDeps ? {children} : null; + return appDeps ? ( + + {children} + + ) : null; }; export const useAppDependencies = (): AppDeps => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/boot.tsx b/x-pack/plugins/triggers_actions_ui/public/application/boot.tsx index bb46fd02a98a..e18bf4ce8487 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/boot.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/boot.tsx @@ -6,21 +6,20 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; -import { SavedObjectsClientContract } from 'src/core/public'; - import { App, AppDeps } from './app'; import { setSavedObjectsClient } from '../common/lib/data_apis'; interface BootDeps extends AppDeps { element: HTMLElement; - savedObjects: SavedObjectsClientContract; I18nContext: any; } export const boot = (bootDeps: BootDeps) => { - const { I18nContext, element, savedObjects, ...appDeps } = bootDeps; + const { I18nContext, element, ...appDeps } = bootDeps; - setSavedObjectsClient(savedObjects); + if (appDeps.savedObjects) { + setSavedObjectsClient(appDeps.savedObjects.client); + } render( diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx index 65d538907888..71e1c60a92ae 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx @@ -55,7 +55,7 @@ describe('actions_connectors_list component empty', () => { const deps = { chrome, docLinks, - dataPlugin: dataPluginMock.createStartContract(), + data: dataPluginMock.createStartContract(), charts: chartPluginMock.createStartContract(), alerting: alertingPluginMock.createStartContract(), toastNotifications: mockes.notifications.toasts, @@ -165,7 +165,7 @@ describe('actions_connectors_list component with items', () => { const deps = { chrome, docLinks, - dataPlugin: dataPluginMock.createStartContract(), + data: dataPluginMock.createStartContract(), charts: chartPluginMock.createStartContract(), alerting: alertingPluginMock.createStartContract(), toastNotifications: mockes.notifications.toasts, @@ -256,7 +256,7 @@ describe('actions_connectors_list component empty with show only capability', () const deps = { chrome, docLinks, - dataPlugin: dataPluginMock.createStartContract(), + data: dataPluginMock.createStartContract(), charts: chartPluginMock.createStartContract(), alerting: alertingPluginMock.createStartContract(), toastNotifications: mockes.notifications.toasts, @@ -348,7 +348,7 @@ describe('actions_connectors_list with show only capability', () => { const deps = { chrome, docLinks, - dataPlugin: dataPluginMock.createStartContract(), + data: dataPluginMock.createStartContract(), charts: chartPluginMock.createStartContract(), alerting: alertingPluginMock.createStartContract(), toastNotifications: mockes.notifications.toasts, @@ -452,7 +452,7 @@ describe('actions_connectors_list component with disabled items', () => { const deps = { chrome, docLinks, - dataPlugin: dataPluginMock.createStartContract(), + data: dataPluginMock.createStartContract(), charts: chartPluginMock.createStartContract(), toastNotifications: mockes.notifications.toasts, injectedMetadata: mockes.injectedMetadata, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx index 70b6fb0b750d..c2a7635b4cf9 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx @@ -42,7 +42,7 @@ jest.mock('../../../app_context', () => ({ toastNotifications: mockes.notifications.toasts, docLinks: { ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' }, uiSettings: mockes.uiSettings, - dataPlugin: jest.fn(), + data: jest.fn(), charts: jest.fn(), })), })); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.tsx index 603058e6fcb5..b38f0e749a28 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.tsx @@ -70,7 +70,7 @@ export const AlertDetails: React.FunctionComponent = ({ uiSettings, docLinks, charts, - dataPlugin, + data, setBreadcrumbs, chrome, } = useAppDependencies(); @@ -162,11 +162,11 @@ export const AlertDetails: React.FunctionComponent = ({ uiSettings, docLinks, charts, - dataFieldsFormats: dataPlugin.fieldFormats, + dataFieldsFormats: data.fieldFormats, reloadAlerts: setAlert, capabilities, - dataUi: dataPlugin.ui, - dataIndexPatterns: dataPlugin.indexPatterns, + dataUi: data.ui, + dataIndexPatterns: data.indexPatterns, }} > { toastNotifications: mocks.notifications.toasts, http: mocks.http, uiSettings: mocks.uiSettings, - dataPlugin: dataPluginMock.createStartContract(), + data: dataPluginMock.createStartContract(), charts: chartPluginMock.createStartContract(), actionTypeRegistry, alertTypeRegistry, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.test.tsx index 611846cf4a52..a29c112b536f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.test.tsx @@ -108,7 +108,7 @@ describe('alerts_list component empty', () => { const deps = { chrome, docLinks, - dataPlugin: dataPluginMock.createStartContract(), + data: dataPluginMock.createStartContract(), charts: chartPluginMock.createStartContract(), alerting: alertingPluginMock.createStartContract(), toastNotifications: mockes.notifications.toasts, @@ -279,7 +279,7 @@ describe('alerts_list component with items', () => { const deps = { chrome, docLinks, - dataPlugin: dataPluginMock.createStartContract(), + data: dataPluginMock.createStartContract(), charts: chartPluginMock.createStartContract(), alerting: alertingPluginMock.createStartContract(), toastNotifications: mockes.notifications.toasts, @@ -362,7 +362,7 @@ describe('alerts_list component empty with show only capability', () => { const deps = { chrome, docLinks, - dataPlugin: dataPluginMock.createStartContract(), + data: dataPluginMock.createStartContract(), charts: chartPluginMock.createStartContract(), alerting: alertingPluginMock.createStartContract(), toastNotifications: mockes.notifications.toasts, @@ -483,7 +483,7 @@ describe('alerts_list with show only capability', () => { const deps = { chrome, docLinks, - dataPlugin: dataPluginMock.createStartContract(), + data: dataPluginMock.createStartContract(), charts: chartPluginMock.createStartContract(), alerting: alertingPluginMock.createStartContract(), toastNotifications: mockes.notifications.toasts, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx index 75f359888a85..11d6f3470fec 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.tsx @@ -83,7 +83,7 @@ export const AlertsList: React.FunctionComponent = () => { uiSettings, docLinks, charts, - dataPlugin, + data, kibanaFeatures, } = useAppDependencies(); const canExecuteActions = hasExecuteActionsCapability(capabilities); @@ -668,10 +668,10 @@ export const AlertsList: React.FunctionComponent = () => { uiSettings, docLinks, charts, - dataFieldsFormats: dataPlugin.fieldFormats, + dataFieldsFormats: data.fieldFormats, capabilities, - dataUi: dataPlugin.ui, - dataIndexPatterns: dataPlugin.indexPatterns, + dataUi: data.ui, + dataIndexPatterns: data.indexPatterns, kibanaFeatures, }} > diff --git a/x-pack/plugins/triggers_actions_ui/public/application/test_utils/index.ts b/x-pack/plugins/triggers_actions_ui/public/application/test_utils/index.ts index b5ab53d868cf..061f3faaa6c0 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/test_utils/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/test_utils/index.ts @@ -26,20 +26,20 @@ export async function getMockedAppDependencies() { const kibanaFeatures = await featuresPluginMock.createStart().getFeatures(); return { + data: dataPluginMock.createStartContract(), + charts: chartPluginMock.createStartContract(), chrome, + navigateToApp, docLinks, - dataPlugin: dataPluginMock.createStartContract(), - charts: chartPluginMock.createStartContract(), - alerting: alertingPluginMock.createStartContract(), toastNotifications: coreSetupMock.notifications.toasts, http: coreSetupMock.http, uiSettings: coreSetupMock.uiSettings, - navigateToApp, - capabilities, - history: scopedHistoryMock.create(), setBreadcrumbs: jest.fn(), + capabilities, actionTypeRegistry, alertTypeRegistry, + history: scopedHistoryMock.create(), + alerting: alertingPluginMock.createStartContract(), kibanaFeatures, }; } diff --git a/x-pack/plugins/triggers_actions_ui/public/index.ts b/x-pack/plugins/triggers_actions_ui/public/index.ts index 3794112e1d50..3187451d2600 100644 --- a/x-pack/plugins/triggers_actions_ui/public/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/index.ts @@ -17,6 +17,7 @@ export { AlertTypeModel, ActionType, ActionTypeRegistryContract, + AlertTypeRegistryContract, AlertTypeParamsExpressionProps, ValidationResult, ActionVariable, diff --git a/x-pack/plugins/triggers_actions_ui/public/plugin.ts b/x-pack/plugins/triggers_actions_ui/public/plugin.ts index 2d93d368ad8e..a30747afe691 100644 --- a/x-pack/plugins/triggers_actions_ui/public/plugin.ts +++ b/x-pack/plugins/triggers_actions_ui/public/plugin.ts @@ -22,6 +22,7 @@ import { import { ChartsPluginStart } from '../../../../src/plugins/charts/public'; import { PluginStartContract as AlertingStart } from '../../alerts/public'; import { DataPublicPluginStart } from '../../../../src/plugins/data/public'; +import { Storage } from '../../../../src/plugins/kibana_utils/public'; export interface TriggersAndActionsUIPublicPluginSetup { actionTypeRegistry: TypeRegistry; @@ -102,16 +103,17 @@ export class Plugin const { boot } = await import('./application/boot'); const kibanaFeatures = await pluginsStart.features.getFeatures(); return boot({ - dataPlugin: pluginsStart.data, + data: pluginsStart.data, charts: pluginsStart.charts, alerts: pluginsStart.alerts, element: params.element, toastNotifications: coreStart.notifications.toasts, + storage: new Storage(window.localStorage), http: coreStart.http, uiSettings: coreStart.uiSettings, docLinks: coreStart.docLinks, chrome: coreStart.chrome, - savedObjects: coreStart.savedObjects.client, + savedObjects: coreStart.savedObjects, I18nContext: coreStart.i18n.Context, capabilities: coreStart.application.capabilities, navigateToApp: coreStart.application.navigateToApp, From 47d6612baed59b9fd21762b0c33f78452c0ad893 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yulia=20=C4=8Cech?= <6585477+yuliacech@users.noreply.github.com> Date: Wed, 18 Nov 2020 23:16:18 +0100 Subject: [PATCH 21/49] Add Managed label to data streams and a view switch for the table (#83049) * Add Managed label to data streams and a view switch for the table * Fix i18n errors * Updated some wording and made filter function easier (managed data streams) * Update x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts Co-authored-by: Alison Goryachev * Renamed view to include (managed data streams) * Update x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx Co-authored-by: James Rodewig <40268737+jrodewig@users.noreply.github.com> * Update x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx Co-authored-by: James Rodewig <40268737+jrodewig@users.noreply.github.com> * Update x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx Co-authored-by: James Rodewig <40268737+jrodewig@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Alison Goryachev Co-authored-by: James Rodewig <40268737+jrodewig@users.noreply.github.com> --- .../helpers/test_subjects.ts | 1 + .../home/data_streams_tab.helpers.ts | 12 ++- .../home/data_streams_tab.test.ts | 78 ++++++++++++++++--- .../home/indices_tab.test.ts | 4 +- .../common/lib/data_stream_serialization.ts | 2 + .../common/types/data_streams.ts | 10 +++ .../public/application/lib/data_streams.tsx | 15 ++++ .../data_stream_list/data_stream_list.tsx | 37 ++++++++- .../data_stream_table/data_stream_table.tsx | 50 +++++++++--- .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - 11 files changed, 182 insertions(+), 29 deletions(-) create mode 100644 x-pack/plugins/index_management/public/application/lib/data_streams.tsx diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts b/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts index 313ebefb8530..04843cae6a57 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts @@ -21,6 +21,7 @@ export type TestSubjects = | 'filterList.filterItem' | 'ilmPolicyLink' | 'includeStatsSwitch' + | 'includeManagedSwitch' | 'indexTable' | 'indexTableIncludeHiddenIndicesToggle' | 'indexTableIndexNameLink' diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts index 148b20e5de53..4e0486e55720 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts @@ -19,6 +19,7 @@ export interface DataStreamsTabTestBed extends TestBed { goToDataStreamsList: () => void; clickEmptyPromptIndexTemplateLink: () => void; clickIncludeStatsSwitch: () => void; + clickIncludeManagedSwitch: () => void; clickReloadButton: () => void; clickNameAt: (index: number) => void; clickIndicesAt: (index: number) => void; @@ -80,6 +81,11 @@ export const setup = async (overridingDependencies: any = {}): Promise { + const { find } = testBed; + find('includeManagedSwitch').simulate('click'); + }; + const clickReloadButton = () => { const { find } = testBed; find('reloadButton').simulate('click'); @@ -183,6 +189,7 @@ export const setup = async (overridingDependencies: any = {}): Promise ({ - name, +export const createDataStreamPayload = (dataStream: Partial): DataStream => ({ + name: 'my-data-stream', timeStampField: { name: '@timestamp' }, indices: [ { @@ -216,6 +223,7 @@ export const createDataStreamPayload = (name: string): DataStream => ({ indexTemplateName: 'indexTemplate', storageSize: '1b', maxTimeStamp: 420, + ...dataStream, }); export const createDataStreamBackingIndex = (indexName: string, dataStreamName: string) => ({ diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts index 633184c9afec..a76d5dc99cba 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts @@ -99,10 +99,10 @@ describe('Data Streams tab', () => { createNonDataStreamIndex('non-data-stream-index'), ]); - const dataStreamForDetailPanel = createDataStreamPayload('dataStream1'); + const dataStreamForDetailPanel = createDataStreamPayload({ name: 'dataStream1' }); setLoadDataStreamsResponse([ dataStreamForDetailPanel, - createDataStreamPayload('dataStream2'), + createDataStreamPayload({ name: 'dataStream2' }), ]); setLoadDataStreamResponse(dataStreamForDetailPanel); @@ -287,9 +287,9 @@ describe('Data Streams tab', () => { createDataStreamBackingIndex('data-stream-index2', 'dataStream2'), ]); - const dataStreamDollarSign = createDataStreamPayload('%dataStream'); - setLoadDataStreamsResponse([dataStreamDollarSign]); - setLoadDataStreamResponse(dataStreamDollarSign); + const dataStreamPercentSign = createDataStreamPayload({ name: '%dataStream' }); + setLoadDataStreamsResponse([dataStreamPercentSign]); + setLoadDataStreamResponse(dataStreamPercentSign); testBed = await setup({ history: createMemoryHistory(), @@ -327,10 +327,10 @@ describe('Data Streams tab', () => { test('with an ILM url generator and an ILM policy', async () => { const { setLoadDataStreamsResponse, setLoadDataStreamResponse } = httpRequestsMockHelpers; - const dataStreamForDetailPanel = { - ...createDataStreamPayload('dataStream1'), + const dataStreamForDetailPanel = createDataStreamPayload({ + name: 'dataStream1', ilmPolicyName: 'my_ilm_policy', - }; + }); setLoadDataStreamsResponse([dataStreamForDetailPanel]); setLoadDataStreamResponse(dataStreamForDetailPanel); @@ -351,7 +351,7 @@ describe('Data Streams tab', () => { test('with an ILM url generator and no ILM policy', async () => { const { setLoadDataStreamsResponse, setLoadDataStreamResponse } = httpRequestsMockHelpers; - const dataStreamForDetailPanel = createDataStreamPayload('dataStream1'); + const dataStreamForDetailPanel = createDataStreamPayload({ name: 'dataStream1' }); setLoadDataStreamsResponse([dataStreamForDetailPanel]); setLoadDataStreamResponse(dataStreamForDetailPanel); @@ -373,10 +373,10 @@ describe('Data Streams tab', () => { test('without an ILM url generator and with an ILM policy', async () => { const { setLoadDataStreamsResponse, setLoadDataStreamResponse } = httpRequestsMockHelpers; - const dataStreamForDetailPanel = { - ...createDataStreamPayload('dataStream1'), + const dataStreamForDetailPanel = createDataStreamPayload({ + name: 'dataStream1', ilmPolicyName: 'my_ilm_policy', - }; + }); setLoadDataStreamsResponse([dataStreamForDetailPanel]); setLoadDataStreamResponse(dataStreamForDetailPanel); @@ -395,4 +395,58 @@ describe('Data Streams tab', () => { expect(findDetailPanelIlmPolicyName().contains('my_ilm_policy')).toBeTruthy(); }); }); + + describe('managed data streams', () => { + const nonBreakingSpace = ' '; + beforeEach(async () => { + const managedDataStream = createDataStreamPayload({ + name: 'managed-data-stream', + _meta: { + package: 'test', + managed: true, + managed_by: 'ingest-manager', + }, + }); + const nonManagedDataStream = createDataStreamPayload({ name: 'non-managed-data-stream' }); + httpRequestsMockHelpers.setLoadDataStreamsResponse([managedDataStream, nonManagedDataStream]); + + testBed = await setup({ + history: createMemoryHistory(), + }); + await act(async () => { + testBed.actions.goToDataStreamsList(); + }); + testBed.component.update(); + }); + + test('listed in the table with Managed label', () => { + const { table } = testBed; + const { tableCellsValues } = table.getMetaData('dataStreamTable'); + + expect(tableCellsValues).toEqual([ + ['', `managed-data-stream${nonBreakingSpace}Managed`, 'green', '1', 'Delete'], + ['', 'non-managed-data-stream', 'green', '1', 'Delete'], + ]); + }); + + test('turning off "Include managed" switch hides managed data streams', async () => { + const { exists, actions, component, table } = testBed; + let { tableCellsValues } = table.getMetaData('dataStreamTable'); + + expect(tableCellsValues).toEqual([ + ['', `managed-data-stream${nonBreakingSpace}Managed`, 'green', '1', 'Delete'], + ['', 'non-managed-data-stream', 'green', '1', 'Delete'], + ]); + + expect(exists('includeManagedSwitch')).toBe(true); + + await act(async () => { + actions.clickIncludeManagedSwitch(); + }); + component.update(); + + ({ tableCellsValues } = table.getMetaData('dataStreamTable')); + expect(tableCellsValues).toEqual([['', 'non-managed-data-stream', 'green', '1', 'Delete']]); + }); + }); }); diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.ts index db4624d4389f..9a5dca01e1b9 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.ts @@ -74,7 +74,9 @@ describe('', () => { // The detail panel should still appear even if there are no data streams. httpRequestsMockHelpers.setLoadDataStreamsResponse([]); - httpRequestsMockHelpers.setLoadDataStreamResponse(createDataStreamPayload('dataStream1')); + httpRequestsMockHelpers.setLoadDataStreamResponse( + createDataStreamPayload({ name: 'dataStream1' }) + ); testBed = await setup({ history: createMemoryHistory(), diff --git a/x-pack/plugins/index_management/common/lib/data_stream_serialization.ts b/x-pack/plugins/index_management/common/lib/data_stream_serialization.ts index 69004eaa020e..2d8e038d2a60 100644 --- a/x-pack/plugins/index_management/common/lib/data_stream_serialization.ts +++ b/x-pack/plugins/index_management/common/lib/data_stream_serialization.ts @@ -17,6 +17,7 @@ export function deserializeDataStream(dataStreamFromEs: DataStreamFromEs): DataS ilm_policy: ilmPolicyName, store_size: storageSize, maximum_timestamp: maxTimeStamp, + _meta, } = dataStreamFromEs; return { @@ -35,6 +36,7 @@ export function deserializeDataStream(dataStreamFromEs: DataStreamFromEs): DataS ilmPolicyName, storageSize, maxTimeStamp, + _meta, }; } diff --git a/x-pack/plugins/index_management/common/types/data_streams.ts b/x-pack/plugins/index_management/common/types/data_streams.ts index 7c348f9a8085..adb7104043fb 100644 --- a/x-pack/plugins/index_management/common/types/data_streams.ts +++ b/x-pack/plugins/index_management/common/types/data_streams.ts @@ -10,6 +10,14 @@ interface TimestampFieldFromEs { type TimestampField = TimestampFieldFromEs; +interface MetaFieldFromEs { + managed_by: string; + package: any; + managed: boolean; +} + +type MetaField = MetaFieldFromEs; + export type HealthFromEs = 'GREEN' | 'YELLOW' | 'RED'; export interface DataStreamFromEs { @@ -17,6 +25,7 @@ export interface DataStreamFromEs { timestamp_field: TimestampFieldFromEs; indices: DataStreamIndexFromEs[]; generation: number; + _meta?: MetaFieldFromEs; status: HealthFromEs; template: string; ilm_policy?: string; @@ -41,6 +50,7 @@ export interface DataStream { ilmPolicyName?: string; storageSize?: string; maxTimeStamp?: number; + _meta?: MetaField; } export interface DataStreamIndex { diff --git a/x-pack/plugins/index_management/public/application/lib/data_streams.tsx b/x-pack/plugins/index_management/public/application/lib/data_streams.tsx new file mode 100644 index 000000000000..ca5297e39933 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/lib/data_streams.tsx @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { DataStream } from '../../../common'; + +export const isManagedByIngestManager = (dataStream: DataStream): boolean => { + return Boolean(dataStream._meta?.managed && dataStream._meta?.managed_by === 'ingest-manager'); +}; + +export const filterDataStreams = (dataStreams: DataStream[]): DataStream[] => { + return dataStreams.filter((dataStream: DataStream) => !isManagedByIngestManager(dataStream)); +}; diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx index 20b93d9d71d0..0df5697a4281 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx @@ -32,6 +32,7 @@ import { documentationService } from '../../../services/documentation'; import { Section } from '../home'; import { DataStreamTable } from './data_stream_table'; import { DataStreamDetailPanel } from './data_stream_detail_panel'; +import { filterDataStreams } from '../../../lib/data_streams'; interface MatchParams { dataStreamName?: string; @@ -52,6 +53,7 @@ export const DataStreamList: React.FunctionComponent ); } else if (Array.isArray(dataStreams) && dataStreams.length > 0) { + const filteredDataStreams = isIncludeManagedChecked + ? dataStreams + : filterDataStreams(dataStreams); content = ( <> - {/* TODO: Add a switch for toggling on data streams created by Ingest Manager */} + + + + setIsIncludeManagedChecked(e.target.checked)} + data-test-subj="includeManagedSwitch" + /> + + + + + + +
@@ -212,7 +245,7 @@ export const DataStreamList: React.FunctionComponent = ({ }), truncateText: true, sortable: true, - render: (name: DataStream['name']) => { + render: (name: DataStream['name'], dataStream: DataStream) => { return ( - - {name} - + + + {name} + + {isManagedByIngestManager(dataStream) ? ( + +   + + + + + + + ) : null} + ); }, }); @@ -121,7 +151,7 @@ export const DataStreamTable: React.FunctionComponent = ({ name: i18n.translate('xpack.idxMgmt.dataStreamList.table.actionDeleteText', { defaultMessage: 'Delete', }), - description: i18n.translate('xpack.idxMgmt.dataStreamList.table.actionDeleteDecription', { + description: i18n.translate('xpack.idxMgmt.dataStreamList.table.actionDeleteDescription', { defaultMessage: 'Delete this data stream', }), icon: 'trash', diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index cd45a4f01fc6..7115f8c6eeb6 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -8143,7 +8143,6 @@ "xpack.idxMgmt.dataStreamList.loadingDataStreamsErrorMessage": "データストリームの読み込み中にエラーが発生", "xpack.idxMgmt.dataStreamList.reloadDataStreamsButtonLabel": "再読み込み", "xpack.idxMgmt.dataStreamList.table.actionColumnTitle": "アクション", - "xpack.idxMgmt.dataStreamList.table.actionDeleteDecription": "このデータストリームを削除", "xpack.idxMgmt.dataStreamList.table.actionDeleteText": "削除", "xpack.idxMgmt.dataStreamList.table.deleteDataStreamsButtonLabel": "{count, plural, one {個のデータストリーム} other {個のデータストリーム}}を削除", "xpack.idxMgmt.dataStreamList.table.healthColumnTitle": "ヘルス", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 97396b09ca6c..b945c443741b 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -8151,7 +8151,6 @@ "xpack.idxMgmt.dataStreamList.loadingDataStreamsErrorMessage": "加载数据流时出错", "xpack.idxMgmt.dataStreamList.reloadDataStreamsButtonLabel": "重新加载", "xpack.idxMgmt.dataStreamList.table.actionColumnTitle": "操作", - "xpack.idxMgmt.dataStreamList.table.actionDeleteDecription": "删除此数据流", "xpack.idxMgmt.dataStreamList.table.actionDeleteText": "删除", "xpack.idxMgmt.dataStreamList.table.deleteDataStreamsButtonLabel": "删除{count, plural, one {数据流} other {数据流} }", "xpack.idxMgmt.dataStreamList.table.healthColumnTitle": "运行状况", From 8ede715869de8acab83ea58d1019fe03af40e0a1 Mon Sep 17 00:00:00 2001 From: Brandon Kobel Date: Wed, 18 Nov 2020 14:26:25 -0800 Subject: [PATCH 22/49] Updating code-owners to use new core/app-services team names (#83731) * Updating code-owners to use new core/app-services team names * And the comment as well --- .github/CODEOWNERS | 126 ++++++++++++++++++++++----------------------- 1 file changed, 63 insertions(+), 63 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index d92725b233e3..5b43f9883a2c 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -30,40 +30,40 @@ /src/plugins/visualizations/ @elastic/kibana-app # Application Services -/examples/bfetch_explorer/ @elastic/kibana-app-arch -/examples/dashboard_embeddable_examples/ @elastic/kibana-app-arch -/examples/demo_search/ @elastic/kibana-app-arch -/examples/developer_examples/ @elastic/kibana-app-arch -/examples/embeddable_examples/ @elastic/kibana-app-arch -/examples/embeddable_explorer/ @elastic/kibana-app-arch -/examples/state_containers_examples/ @elastic/kibana-app-arch -/examples/ui_action_examples/ @elastic/kibana-app-arch -/examples/ui_actions_explorer/ @elastic/kibana-app-arch -/examples/url_generators_examples/ @elastic/kibana-app-arch -/examples/url_generators_explorer/ @elastic/kibana-app-arch -/packages/elastic-datemath/ @elastic/kibana-app-arch -/packages/kbn-interpreter/ @elastic/kibana-app-arch -/src/plugins/bfetch/ @elastic/kibana-app-arch -/src/plugins/data/ @elastic/kibana-app-arch -/src/plugins/embeddable/ @elastic/kibana-app-arch -/src/plugins/expressions/ @elastic/kibana-app-arch -/src/plugins/inspector/ @elastic/kibana-app-arch -/src/plugins/kibana_react/ @elastic/kibana-app-arch +/examples/bfetch_explorer/ @elastic/kibana-app-services +/examples/dashboard_embeddable_examples/ @elastic/kibana-app-services +/examples/demo_search/ @elastic/kibana-app-services +/examples/developer_examples/ @elastic/kibana-app-services +/examples/embeddable_examples/ @elastic/kibana-app-services +/examples/embeddable_explorer/ @elastic/kibana-app-services +/examples/state_containers_examples/ @elastic/kibana-app-services +/examples/ui_action_examples/ @elastic/kibana-app-services +/examples/ui_actions_explorer/ @elastic/kibana-app-services +/examples/url_generators_examples/ @elastic/kibana-app-services +/examples/url_generators_explorer/ @elastic/kibana-app-services +/packages/elastic-datemath/ @elastic/kibana-app-services +/packages/kbn-interpreter/ @elastic/kibana-app-services +/src/plugins/bfetch/ @elastic/kibana-app-services +/src/plugins/data/ @elastic/kibana-app-services +/src/plugins/embeddable/ @elastic/kibana-app-services +/src/plugins/expressions/ @elastic/kibana-app-services +/src/plugins/inspector/ @elastic/kibana-app-services +/src/plugins/kibana_react/ @elastic/kibana-app-services /src/plugins/kibana_react/public/code_editor @elastic/kibana-presentation -/src/plugins/kibana_utils/ @elastic/kibana-app-arch -/src/plugins/navigation/ @elastic/kibana-app-arch -/src/plugins/share/ @elastic/kibana-app-arch -/src/plugins/ui_actions/ @elastic/kibana-app-arch -/x-pack/examples/ui_actions_enhanced_examples/ @elastic/kibana-app-arch -/x-pack/plugins/data_enhanced/ @elastic/kibana-app-arch -/x-pack/plugins/embeddable_enhanced/ @elastic/kibana-app-arch -/x-pack/plugins/ui_actions_enhanced/ @elastic/kibana-app-arch -#CC# /src/plugins/bfetch/ @elastic/kibana-app-arch -#CC# /src/plugins/index_pattern_management/ @elastic/kibana-app-arch -#CC# /src/plugins/inspector/ @elastic/kibana-app-arch -#CC# /src/plugins/share/ @elastic/kibana-app-arch -#CC# /x-pack/plugins/drilldowns/ @elastic/kibana-app-arch -#CC# /packages/kbn-interpreter/ @elastic/kibana-app-arch +/src/plugins/kibana_utils/ @elastic/kibana-app-services +/src/plugins/navigation/ @elastic/kibana-app-services +/src/plugins/share/ @elastic/kibana-app-services +/src/plugins/ui_actions/ @elastic/kibana-app-services +/x-pack/examples/ui_actions_enhanced_examples/ @elastic/kibana-app-services +/x-pack/plugins/data_enhanced/ @elastic/kibana-app-services +/x-pack/plugins/embeddable_enhanced/ @elastic/kibana-app-services +/x-pack/plugins/ui_actions_enhanced/ @elastic/kibana-app-services +#CC# /src/plugins/bfetch/ @elastic/kibana-app-services +#CC# /src/plugins/index_pattern_management/ @elastic/kibana-app-services +#CC# /src/plugins/inspector/ @elastic/kibana-app-services +#CC# /src/plugins/share/ @elastic/kibana-app-services +#CC# /x-pack/plugins/drilldowns/ @elastic/kibana-app-services +#CC# /packages/kbn-interpreter/ @elastic/kibana-app-services # APM /x-pack/plugins/apm/ @elastic/apm-ui @@ -172,38 +172,38 @@ /test/functional/services/lib @elastic/kibana-qa /test/functional/services/remote @elastic/kibana-qa -# Platform -/src/core/ @elastic/kibana-platform -/src/plugins/saved_objects_tagging_oss @elastic/kibana-platform -/config/kibana.yml @elastic/kibana-platform -/x-pack/plugins/features/ @elastic/kibana-platform -/x-pack/plugins/licensing/ @elastic/kibana-platform -/x-pack/plugins/global_search/ @elastic/kibana-platform -/x-pack/plugins/cloud/ @elastic/kibana-platform -/x-pack/plugins/saved_objects_tagging/ @elastic/kibana-platform -/x-pack/test/saved_objects_field_count/ @elastic/kibana-platform -/x-pack/test/saved_object_tagging/ @elastic/kibana-platform -/packages/kbn-config-schema/ @elastic/kibana-platform -/packages/kbn-std/ @elastic/kibana-platform -/src/legacy/server/config/ @elastic/kibana-platform -/src/legacy/server/http/ @elastic/kibana-platform -/src/legacy/server/logging/ @elastic/kibana-platform -/src/plugins/status_page/ @elastic/kibana-platform -/src/plugins/saved_objects_management/ @elastic/kibana-platform -/src/dev/run_check_published_api_changes.ts @elastic/kibana-platform -#CC# /src/core/server/csp/ @elastic/kibana-platform -#CC# /src/legacy/server/config/ @elastic/kibana-platform -#CC# /src/legacy/server/http/ @elastic/kibana-platform -#CC# /src/legacy/ui/public/documentation_links @elastic/kibana-platform -#CC# /src/plugins/legacy_export/ @elastic/kibana-platform -#CC# /src/plugins/saved_objects/ @elastic/kibana-platform -#CC# /src/plugins/status_page/ @elastic/kibana-platform -#CC# /x-pack/plugins/cloud/ @elastic/kibana-platform -#CC# /x-pack/plugins/features/ @elastic/kibana-platform -#CC# /x-pack/plugins/global_search/ @elastic/kibana-platform +# Core +/src/core/ @elastic/kibana-core +/src/plugins/saved_objects_tagging_oss @elastic/kibana-core +/config/kibana.yml @elastic/kibana-core +/x-pack/plugins/features/ @elastic/kibana-core +/x-pack/plugins/licensing/ @elastic/kibana-core +/x-pack/plugins/global_search/ @elastic/kibana-core +/x-pack/plugins/cloud/ @elastic/kibana-core +/x-pack/plugins/saved_objects_tagging/ @elastic/kibana-core +/x-pack/test/saved_objects_field_count/ @elastic/kibana-core +/x-pack/test/saved_object_tagging/ @elastic/kibana-core +/packages/kbn-config-schema/ @elastic/kibana-core +/packages/kbn-std/ @elastic/kibana-core +/src/legacy/server/config/ @elastic/kibana-core +/src/legacy/server/http/ @elastic/kibana-core +/src/legacy/server/logging/ @elastic/kibana-core +/src/plugins/status_page/ @elastic/kibana-core +/src/plugins/saved_objects_management/ @elastic/kibana-core +/src/dev/run_check_published_api_changes.ts @elastic/kibana-core +#CC# /src/core/server/csp/ @elastic/kibana-core +#CC# /src/legacy/server/config/ @elastic/kibana-core +#CC# /src/legacy/server/http/ @elastic/kibana-core +#CC# /src/legacy/ui/public/documentation_links @elastic/kibana-core +#CC# /src/plugins/legacy_export/ @elastic/kibana-core +#CC# /src/plugins/saved_objects/ @elastic/kibana-core +#CC# /src/plugins/status_page/ @elastic/kibana-core +#CC# /x-pack/plugins/cloud/ @elastic/kibana-core +#CC# /x-pack/plugins/features/ @elastic/kibana-core +#CC# /x-pack/plugins/global_search/ @elastic/kibana-core # Security -/src/core/server/csp/ @elastic/kibana-security @elastic/kibana-platform +/src/core/server/csp/ @elastic/kibana-security @elastic/kibana-core /src/plugins/security_oss/ @elastic/kibana-security /test/security_functional/ @elastic/kibana-security /x-pack/plugins/spaces/ @elastic/kibana-security From a7670518cc15e36f9e81a977c53e94764e3d2791 Mon Sep 17 00:00:00 2001 From: Aaron Caldwell Date: Wed, 18 Nov 2020 15:43:26 -0700 Subject: [PATCH 23/49] [Maps] Add 'crossed' & 'exited' events to tracking alert (#82463) --- .../geo_threshold/query_builder/index.tsx | 2 +- .../public/alert_types/geo_threshold/types.ts | 1 + .../geo_threshold/geo_threshold.ts | 56 +++++++++++-------- .../geo_threshold/tests/geo_threshold.test.ts | 53 ++++++++++++++++-- 4 files changed, 85 insertions(+), 27 deletions(-) diff --git a/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/query_builder/index.tsx b/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/query_builder/index.tsx index 623223d66ea0..c573d3b73837 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/query_builder/index.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/query_builder/index.tsx @@ -326,7 +326,7 @@ export const GeoThresholdAlertTypeExpression: React.FunctionComponent setAlertParams('trackingEvent', e.target.value)} - options={[conditionOptions[0]]} // TODO: Make all options avab. before merge + options={conditionOptions} /> diff --git a/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/types.ts b/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/types.ts index 86faa4ed2fb4..5ac9c7fd2931 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/types.ts +++ b/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/types.ts @@ -9,6 +9,7 @@ import { Query } from '../../../../../../src/plugins/data/common'; export enum TrackingEvent { entered = 'entered', exited = 'exited', + crossed = 'crossed', } export interface GeoThresholdAlertParams { diff --git a/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/geo_threshold.ts b/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/geo_threshold.ts index 8247cc787d36..5cb4156e8462 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/geo_threshold.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/geo_threshold.ts @@ -144,11 +144,14 @@ export function getMovedEntities( [] ) // Do not track entries to or exits from 'other' - .filter((entityMovementDescriptor: EntityMovementDescriptor) => - trackingEvent === 'entered' - ? entityMovementDescriptor.currLocation.shapeId !== OTHER_CATEGORY - : entityMovementDescriptor.prevLocation.shapeId !== OTHER_CATEGORY - ) + .filter((entityMovementDescriptor: EntityMovementDescriptor) => { + if (trackingEvent !== 'crossed') { + return trackingEvent === 'entered' + ? entityMovementDescriptor.currLocation.shapeId !== OTHER_CATEGORY + : entityMovementDescriptor.prevLocation.shapeId !== OTHER_CATEGORY; + } + return true; + }) ); } @@ -254,27 +257,36 @@ export const getGeoThresholdExecutor = (log: Logger) => movedEntities.forEach(({ entityName, currLocation, prevLocation }) => { const toBoundaryName = shapesIdsNamesMap[currLocation.shapeId] || currLocation.shapeId; const fromBoundaryName = shapesIdsNamesMap[prevLocation.shapeId] || prevLocation.shapeId; - services - .alertInstanceFactory(`${entityName}-${toBoundaryName || currLocation.shapeId}`) - .scheduleActions(ActionGroupId, { - entityId: entityName, - timeOfDetection: new Date(currIntervalEndTime).getTime(), - crossingLine: `LINESTRING (${prevLocation.location[0]} ${prevLocation.location[1]}, ${currLocation.location[0]} ${currLocation.location[1]})`, + let alertInstance; + if (params.trackingEvent === 'entered') { + alertInstance = `${entityName}-${toBoundaryName || currLocation.shapeId}`; + } else if (params.trackingEvent === 'exited') { + alertInstance = `${entityName}-${fromBoundaryName || prevLocation.shapeId}`; + } else { + // == 'crossed' + alertInstance = `${entityName}-${fromBoundaryName || prevLocation.shapeId}-${ + toBoundaryName || currLocation.shapeId + }`; + } + services.alertInstanceFactory(alertInstance).scheduleActions(ActionGroupId, { + entityId: entityName, + timeOfDetection: new Date(currIntervalEndTime).getTime(), + crossingLine: `LINESTRING (${prevLocation.location[0]} ${prevLocation.location[1]}, ${currLocation.location[0]} ${currLocation.location[1]})`, - toEntityLocation: `POINT (${currLocation.location[0]} ${currLocation.location[1]})`, - toEntityDateTime: currLocation.date, - toEntityDocumentId: currLocation.docId, + toEntityLocation: `POINT (${currLocation.location[0]} ${currLocation.location[1]})`, + toEntityDateTime: currLocation.date, + toEntityDocumentId: currLocation.docId, - toBoundaryId: currLocation.shapeId, - toBoundaryName, + toBoundaryId: currLocation.shapeId, + toBoundaryName, - fromEntityLocation: `POINT (${prevLocation.location[0]} ${prevLocation.location[1]})`, - fromEntityDateTime: prevLocation.date, - fromEntityDocumentId: prevLocation.docId, + fromEntityLocation: `POINT (${prevLocation.location[0]} ${prevLocation.location[1]})`, + fromEntityDateTime: prevLocation.date, + fromEntityDocumentId: prevLocation.docId, - fromBoundaryId: prevLocation.shapeId, - fromBoundaryName, - }); + fromBoundaryId: prevLocation.shapeId, + fromBoundaryName, + }); }); // Combine previous results w/ current results for state of next run diff --git a/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/tests/geo_threshold.test.ts b/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/tests/geo_threshold.test.ts index e4cee9c67771..5b5197ac62a3 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/tests/geo_threshold.test.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/tests/geo_threshold.test.ts @@ -99,7 +99,6 @@ describe('geo_threshold', () => { }); describe('getMovedEntities', () => { - const trackingEvent = 'entered'; it('should return empty array if only movements were within same shapes', async () => { const currLocationArr = [ { @@ -133,7 +132,7 @@ describe('geo_threshold', () => { shapeLocationId: 'sameShape2', }, ]; - const movedEntities = getMovedEntities(currLocationArr, prevLocationArr, trackingEvent); + const movedEntities = getMovedEntities(currLocationArr, prevLocationArr, 'entered'); expect(movedEntities).toEqual([]); }); @@ -170,7 +169,7 @@ describe('geo_threshold', () => { shapeLocationId: 'thisOneDidntMove', }, ]; - const movedEntities = getMovedEntities(currLocationArr, prevLocationArr, trackingEvent); + const movedEntities = getMovedEntities(currLocationArr, prevLocationArr, 'entered'); expect(movedEntities.length).toEqual(1); }); @@ -193,7 +192,7 @@ describe('geo_threshold', () => { shapeLocationId: 'oldShapeLocation', }, ]; - const movedEntities = getMovedEntities(currLocationArr, prevLocationArr, trackingEvent); + const movedEntities = getMovedEntities(currLocationArr, prevLocationArr, 'entered'); expect(movedEntities).toEqual([]); }); @@ -219,5 +218,51 @@ describe('geo_threshold', () => { const movedEntities = getMovedEntities(currLocationArr, prevLocationArr, 'exited'); expect(movedEntities).toEqual([]); }); + + it('should not ignore "crossed" results from "other"', async () => { + const currLocationArr = [ + { + dateInShape: '2020-09-28T18:01:41.190Z', + docId: 'N-ng1XQB6yyY-xQxnGSM', + entityName: '936', + location: [-82.8814151789993, 41.62806099653244], + shapeLocationId: 'newShapeLocation', + }, + ]; + const prevLocationArr = [ + { + dateInShape: '2020-08-28T18:01:41.190Z', + docId: 'N-ng1XQB6yyY-xQxnGSM', + entityName: '936', + location: [-82.8814151789993, 40.62806099653244], + shapeLocationId: OTHER_CATEGORY, + }, + ]; + const movedEntities = getMovedEntities(currLocationArr, prevLocationArr, 'crossed'); + expect(movedEntities.length).toEqual(1); + }); + + it('should not ignore "crossed" results to "other"', async () => { + const currLocationArr = [ + { + dateInShape: '2020-08-28T18:01:41.190Z', + docId: 'N-ng1XQB6yyY-xQxnGSM', + entityName: '936', + location: [-82.8814151789993, 40.62806099653244], + shapeLocationId: OTHER_CATEGORY, + }, + ]; + const prevLocationArr = [ + { + dateInShape: '2020-09-28T18:01:41.190Z', + docId: 'N-ng1XQB6yyY-xQxnGSM', + entityName: '936', + location: [-82.8814151789993, 41.62806099653244], + shapeLocationId: 'newShapeLocation', + }, + ]; + const movedEntities = getMovedEntities(currLocationArr, prevLocationArr, 'crossed'); + expect(movedEntities.length).toEqual(1); + }); }); }); From 640a7b9b7f65c284dd82ca4572caa189f483f450 Mon Sep 17 00:00:00 2001 From: Constance Date: Wed, 18 Nov 2020 14:49:14 -0800 Subject: [PATCH 24/49] [Enterprise Search] Rename React Router helpers (#83718) * Rename EUI React Router components - Instead of bogarting the EUI component names, use EuiLinkTo instead of EuiLink Other misc renaming - eui_link.tsx to eui_components.tsx for clearer file name - EuiReactRouterHelper to ReactRouterHelper, to make the distinction between EUI and React Router clearer (in theory you could use this helper for non-EUI components) - other misc type renaming * Update simple instances of previous EUI RR components to Eui*To * Clean up complex/renamed instances of Eui*To (hopefully much more straightforward now) - unfortunately side_nav requires an eslint disable --- .../components/engines/engines_table.test.tsx | 4 +-- .../components/engines/engines_table.tsx | 10 +++---- .../product_card/product_card.test.tsx | 8 +++--- .../components/product_card/product_card.tsx | 6 ++-- .../setup_guide/setup_guide_cta.tsx | 6 ++-- .../shared/error_state/error_state_prompt.tsx | 6 ++-- .../shared/layout/side_nav.test.tsx | 8 +++--- .../applications/shared/layout/side_nav.tsx | 19 +++++-------- .../shared/not_found/not_found.tsx | 12 ++++---- ..._link.test.tsx => eui_components.test.tsx} | 20 ++++++------- .../{eui_link.tsx => eui_components.tsx} | 28 +++++++++---------- .../shared/react_router_helpers/index.ts | 6 +--- .../shared/source_row/source_row.tsx | 10 +++---- .../groups/components/group_manager_modal.tsx | 6 ++-- .../views/groups/components/group_row.tsx | 10 +++---- .../views/groups/groups.test.tsx | 4 +-- .../workplace_search/views/groups/groups.tsx | 10 +++---- 17 files changed, 82 insertions(+), 91 deletions(-) rename x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/{eui_link.test.tsx => eui_components.test.tsx} (78%) rename x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/{eui_link.tsx => eui_components.tsx} (73%) diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_table.test.tsx index e9ac51b6a901..c8872fe43a18 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_table.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_table.test.tsx @@ -10,7 +10,7 @@ import { mockTelemetryActions, mountWithIntl } from '../../../__mocks__/'; import React from 'react'; import { EuiBasicTable, EuiPagination, EuiButtonEmpty } from '@elastic/eui'; -import { EuiLink } from '../../../shared/react_router_helpers'; +import { EuiLinkTo } from '../../../shared/react_router_helpers'; import { EnginesTable } from './engines_table'; @@ -50,7 +50,7 @@ describe('EnginesTable', () => { }); it('contains engine links which send telemetry', () => { - const engineLinks = wrapper.find(EuiLink); + const engineLinks = wrapper.find(EuiLinkTo); engineLinks.forEach((link) => { expect(link.prop('to')).toEqual('/engines/test-engine'); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_table.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_table.tsx index 9591bbda1f7c..7d69cd2b4d4d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_table.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_table.tsx @@ -11,7 +11,7 @@ import { FormattedMessage, FormattedDate, FormattedNumber } from '@kbn/i18n/reac import { i18n } from '@kbn/i18n'; import { TelemetryLogic } from '../../../shared/telemetry'; -import { EuiLink } from '../../../shared/react_router_helpers'; +import { EuiLinkTo } from '../../../shared/react_router_helpers'; import { getEngineRoute } from '../../routes'; import { ENGINES_PAGE_SIZE } from '../../../../../common/constants'; @@ -59,9 +59,9 @@ export const EnginesTable: React.FC = ({ defaultMessage: 'Name', }), render: (name: string) => ( - + {name} - + ), width: '30%', truncateText: true, @@ -122,12 +122,12 @@ export const EnginesTable: React.FC = ({ ), dataType: 'string', render: (name: string) => ( - + - + ), align: 'right', width: '100px', diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_card/product_card.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_card/product_card.test.tsx index a257ccde9f47..8ba2da11604c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_card/product_card.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_card/product_card.test.tsx @@ -12,7 +12,7 @@ import { useValues } from 'kea'; import { shallow } from 'enzyme'; import { EuiCard } from '@elastic/eui'; -import { EuiButton } from '../../../shared/react_router_helpers'; +import { EuiButtonTo } from '../../../shared/react_router_helpers'; import { APP_SEARCH_PLUGIN, WORKPLACE_SEARCH_PLUGIN } from '../../../../../common/constants'; import { ProductCard } from './'; @@ -29,7 +29,7 @@ describe('ProductCard', () => { expect(card.find('h2').text()).toEqual('Elastic App Search'); expect(card.find('.productCard__image').prop('src')).toEqual('as.jpg'); - const button = card.find(EuiButton); + const button = card.find(EuiButtonTo); expect(button.prop('to')).toEqual('/app/enterprise_search/app_search'); expect(button.prop('children')).toEqual('Launch App Search'); @@ -47,7 +47,7 @@ describe('ProductCard', () => { expect(card.find('h2').text()).toEqual('Elastic Workplace Search'); expect(card.find('.productCard__image').prop('src')).toEqual('ws.jpg'); - const button = card.find(EuiButton); + const button = card.find(EuiButtonTo); expect(button.prop('to')).toEqual('/app/enterprise_search/workplace_search'); expect(button.prop('children')).toEqual('Launch Workplace Search'); @@ -63,7 +63,7 @@ describe('ProductCard', () => { const wrapper = shallow(); const card = wrapper.find(EuiCard).dive().shallow(); - const button = card.find(EuiButton); + const button = card.find(EuiButtonTo); expect(button.prop('children')).toEqual('Setup Workplace Search'); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_card/product_card.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_card/product_card.tsx index de553acf11f7..954743f2d043 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_card/product_card.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_card/product_card.tsx @@ -10,7 +10,7 @@ import { snakeCase } from 'lodash'; import { i18n } from '@kbn/i18n'; import { EuiCard, EuiTextColor } from '@elastic/eui'; -import { EuiButton } from '../../../shared/react_router_helpers'; +import { EuiButtonTo } from '../../../shared/react_router_helpers'; import { TelemetryLogic } from '../../../shared/telemetry'; import { KibanaLogic } from '../../../shared/kibana'; @@ -63,7 +63,7 @@ export const ProductCard: React.FC = ({ product, image }) => { paddingSize="l" description={{product.CARD_DESCRIPTION}} footer={ - = ({ product, image }) => { } > {config.host ? LAUNCH_BUTTON_TEXT : SETUP_BUTTON_TEXT} - + } /> ); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/setup_guide/setup_guide_cta.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/setup_guide/setup_guide_cta.tsx index 2a0e2ffc34f3..bb5d7ab570a0 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/setup_guide/setup_guide_cta.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/setup_guide/setup_guide_cta.tsx @@ -7,13 +7,13 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { EuiFlexGroup, EuiFlexItem, EuiTitle, EuiText } from '@elastic/eui'; -import { EuiPanel } from '../../../shared/react_router_helpers'; +import { EuiPanelTo } from '../../../shared/react_router_helpers'; import CtaImage from './assets/getting_started.png'; import './setup_guide_cta.scss'; export const SetupGuideCta: React.FC = () => ( - + @@ -34,5 +34,5 @@ export const SetupGuideCta: React.FC = () => ( - + ); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/error_state/error_state_prompt.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/error_state/error_state_prompt.tsx index b92a5bbf1c64..a04357816886 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/error_state/error_state_prompt.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/error_state/error_state_prompt.tsx @@ -9,7 +9,7 @@ import { useValues } from 'kea'; import { EuiEmptyPrompt, EuiCode } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiButton } from '../react_router_helpers'; +import { EuiButtonTo } from '../react_router_helpers'; import { KibanaLogic } from '../../shared/kibana'; import './error_state_prompt.scss'; @@ -90,12 +90,12 @@ export const ErrorStatePrompt: React.FC = () => { } actions={ - + - + } /> ); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/side_nav.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/side_nav.test.tsx index 9eaa2ba4c4d6..a7cc21fa63f4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/side_nav.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/side_nav.test.tsx @@ -10,8 +10,8 @@ import React from 'react'; import { useLocation } from 'react-router-dom'; import { shallow } from 'enzyme'; -import { EuiLink as EuiLinkExternal } from '@elastic/eui'; -import { EuiLink } from '../react_router_helpers'; +import { EuiLink } from '@elastic/eui'; +import { EuiLinkTo } from '../react_router_helpers'; import { ENTERPRISE_SEARCH_PLUGIN, APP_SEARCH_PLUGIN } from '../../../../common/constants'; import { SideNav, SideNavLink, SideNavItem } from './'; @@ -42,7 +42,7 @@ describe('SideNavLink', () => { const wrapper = shallow(Link); expect(wrapper.type()).toEqual('li'); - expect(wrapper.find(EuiLink)).toHaveLength(1); + expect(wrapper.find(EuiLinkTo)).toHaveLength(1); expect(wrapper.find('.enterpriseSearchNavLinks__item')).toHaveLength(1); }); @@ -52,7 +52,7 @@ describe('SideNavLink', () => { Link ); - const externalLink = wrapper.find(EuiLinkExternal); + const externalLink = wrapper.find(EuiLink); expect(externalLink).toHaveLength(1); expect(externalLink.prop('href')).toEqual('http://website.com'); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/side_nav.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/side_nav.tsx index c75a48d5af41..8da8f4575796 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/side_nav.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/side_nav.tsx @@ -9,8 +9,8 @@ import { useLocation } from 'react-router-dom'; import classNames from 'classnames'; import { i18n } from '@kbn/i18n'; -import { EuiIcon, EuiTitle, EuiText, EuiLink as EuiLinkExternal } from '@elastic/eui'; // TODO: Remove EuiLinkExternal after full Kibana transition -import { EuiLink } from '../react_router_helpers'; +import { EuiIcon, EuiTitle, EuiText, EuiLink } from '@elastic/eui'; // TODO: Remove EuiLink after full Kibana transition +import { EuiLinkTo } from '../react_router_helpers'; import { ENTERPRISE_SEARCH_PLUGIN } from '../../../../common/constants'; import { stripTrailingSlash } from '../../../../common/strip_slashes'; @@ -96,19 +96,14 @@ export const SideNavLink: React.FC = ({ return (
  • {isExternal ? ( - + // eslint-disable-next-line @elastic/eui/href-or-on-click + {children} - + ) : ( - + {children} - + )} {subNav &&
      {subNav}
    }
  • diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.tsx index 05374cb5f027..d0140b873022 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.tsx @@ -13,7 +13,7 @@ import { EuiTitle, EuiFlexGroup, EuiFlexItem, - EuiButton as EuiButtonExternal, + EuiButton, } from '@elastic/eui'; import { @@ -22,7 +22,7 @@ import { LICENSED_SUPPORT_URL, } from '../../../../common/constants'; -import { EuiButton } from '../react_router_helpers'; +import { EuiButtonTo } from '../react_router_helpers'; import { SetAppSearchChrome, SetWorkplaceSearchChrome } from '../kibana_chrome'; import { SendAppSearchTelemetry, SendWorkplaceSearchTelemetry } from '../telemetry'; import { LicensingLogic } from '../licensing'; @@ -89,18 +89,18 @@ export const NotFound: React.FC = ({ product = {} }) => { actions={ - + {i18n.translate('xpack.enterpriseSearch.notFound.action1', { defaultMessage: 'Back to your dashboard', })} - + - + {i18n.translate('xpack.enterpriseSearch.notFound.action2', { defaultMessage: 'Contact support', })} - + } diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/eui_link.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/eui_components.test.tsx similarity index 78% rename from x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/eui_link.test.tsx rename to x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/eui_components.test.tsx index 3a4585b6d9a7..37784fbf4ffb 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/eui_link.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/eui_components.test.tsx @@ -12,7 +12,7 @@ import { EuiLink, EuiButton, EuiPanel } from '@elastic/eui'; import { mockKibanaValues, mockHistory } from '../../__mocks__'; -import { EuiReactRouterLink, EuiReactRouterButton, EuiReactRouterPanel } from './eui_link'; +import { EuiLinkTo, EuiButtonTo, EuiPanelTo } from './eui_components'; describe('EUI & React Router Component Helpers', () => { beforeEach(() => { @@ -20,26 +20,26 @@ describe('EUI & React Router Component Helpers', () => { }); it('renders an EuiLink', () => { - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper.find(EuiLink)).toHaveLength(1); }); it('renders an EuiButton', () => { - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper.find(EuiButton)).toHaveLength(1); }); it('renders an EuiPanel', () => { - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper.find(EuiPanel)).toHaveLength(1); expect(wrapper.find(EuiPanel).prop('paddingSize')).toEqual('l'); }); it('passes down all ...rest props', () => { - const wrapper = shallow(); + const wrapper = shallow(); const link = wrapper.find(EuiLink); expect(link.prop('external')).toEqual(true); @@ -47,7 +47,7 @@ describe('EUI & React Router Component Helpers', () => { }); it('renders with the correct href and onClick props', () => { - const wrapper = mount(); + const wrapper = mount(); const link = wrapper.find(EuiLink); expect(link.prop('onClick')).toBeInstanceOf(Function); @@ -56,7 +56,7 @@ describe('EUI & React Router Component Helpers', () => { }); it('renders with the correct non-basenamed href when shouldNotCreateHref is passed', () => { - const wrapper = mount(); + const wrapper = mount(); const link = wrapper.find(EuiLink); expect(link.prop('href')).toEqual('/foo/bar'); @@ -65,7 +65,7 @@ describe('EUI & React Router Component Helpers', () => { describe('onClick', () => { it('prevents default navigation and uses React Router history', () => { - const wrapper = mount(); + const wrapper = mount(); const simulatedEvent = { button: 0, @@ -79,7 +79,7 @@ describe('EUI & React Router Component Helpers', () => { }); it('does not prevent default browser behavior on new tab/window clicks', () => { - const wrapper = mount(); + const wrapper = mount(); const simulatedEvent = { shiftKey: true, @@ -92,7 +92,7 @@ describe('EUI & React Router Component Helpers', () => { it('calls inherited onClick actions in addition to default navigation', () => { const customOnClick = jest.fn(); // Can be anything from telemetry to a state reset - const wrapper = mount(); + const wrapper = mount(); wrapper.find(EuiLink).simulate('click', { shiftKey: true }); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/eui_link.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/eui_components.tsx similarity index 73% rename from x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/eui_link.tsx rename to x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/eui_components.tsx index 61f6b31d3e2e..56beed878070 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/eui_link.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/eui_components.tsx @@ -20,7 +20,7 @@ import { letBrowserHandleEvent, createHref } from './'; * https://github.com/elastic/eui/blob/master/wiki/react-router.md#react-router-51 */ -interface EuiReactRouterProps { +interface ReactRouterProps { to: string; onClick?(): void; // Used to navigate outside of the React Router plugin basename but still within Kibana, @@ -28,7 +28,7 @@ interface EuiReactRouterProps { shouldNotCreateHref?: boolean; } -export const EuiReactRouterHelper: React.FC = ({ +export const ReactRouterHelper: React.FC = ({ to, onClick, shouldNotCreateHref, @@ -59,38 +59,38 @@ export const EuiReactRouterHelper: React.FC = ({ * Component helpers */ -type EuiReactRouterLinkProps = EuiLinkAnchorProps & EuiReactRouterProps; -export const EuiReactRouterLink: React.FC = ({ +type ReactRouterEuiLinkProps = ReactRouterProps & EuiLinkAnchorProps; +export const EuiLinkTo: React.FC = ({ to, onClick, shouldNotCreateHref, ...rest }) => ( - + - + ); -type EuiReactRouterButtonProps = EuiButtonProps & EuiReactRouterProps; -export const EuiReactRouterButton: React.FC = ({ +type ReactRouterEuiButtonProps = ReactRouterProps & EuiButtonProps; +export const EuiButtonTo: React.FC = ({ to, onClick, shouldNotCreateHref, ...rest }) => ( - + - + ); -type EuiReactRouterPanelProps = EuiPanelProps & EuiReactRouterProps; -export const EuiReactRouterPanel: React.FC = ({ +type ReactRouterEuiPanelProps = ReactRouterProps & EuiPanelProps; +export const EuiPanelTo: React.FC = ({ to, onClick, shouldNotCreateHref, ...rest }) => ( - + - + ); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/index.ts b/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/index.ts index 326bfb32e41f..05a9450e4a34 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/react_router_helpers/index.ts @@ -6,8 +6,4 @@ export { letBrowserHandleEvent } from './link_events'; export { createHref, CreateHrefOptions } from './create_href'; -export { - EuiReactRouterLink as EuiLink, - EuiReactRouterButton as EuiButton, - EuiReactRouterPanel as EuiPanel, -} from './eui_link'; +export { EuiLinkTo, EuiButtonTo, EuiPanelTo } from './eui_components'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_row/source_row.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_row/source_row.tsx index 896b8f8f5b4c..818d06c55dd1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_row/source_row.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_row/source_row.tsx @@ -23,7 +23,7 @@ import { EuiToolTip, } from '@elastic/eui'; -import { EuiLink } from '../../../../shared/react_router_helpers'; +import { EuiLinkTo } from '../../../../shared/react_router_helpers'; import { SOURCE_STATUSES as statuses } from '../../../constants'; import { ContentSourceDetails } from '../../../types'; import { ADD_SOURCE_PATH, SOURCE_DETAILS_PATH, getContentSourcePath } from '../../../routes'; @@ -77,9 +77,9 @@ export const SourceRow: React.FC = ({ const imageClass = classNames('source-row__icon', { 'source-row__icon--loading': isIndexing }); const fixLink = ( - + Fix - + ); const remoteTooltip = ( @@ -159,13 +159,13 @@ export const SourceRow: React.FC = ({ {showFix && {fixLink}} {showDetails && ( - Details - + )}
    diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_manager_modal.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_manager_modal.tsx index 11c0430a8b42..c0f8bf57989c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_manager_modal.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_manager_modal.tsx @@ -26,7 +26,7 @@ import { EuiSpacer, } from '@elastic/eui'; -import { EuiButton as EuiLinkButton } from '../../../../shared/react_router_helpers'; +import { EuiButtonTo } from '../../../../shared/react_router_helpers'; import { Group } from '../../../types'; import { ORG_SOURCES_PATH } from '../../../routes'; @@ -96,9 +96,9 @@ export const GroupManagerModal: React.FC = ({ const handleSelectAll = () => selectAll(allSelected ? [] : allItems); const sourcesButton = ( - + {ADD_SOURCE_BUTTON_TEXT} - + ); const emptyState = ( diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_row.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_row.tsx index 5cebb96d00eb..9d33f810edae 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_row.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_row.tsx @@ -13,7 +13,7 @@ import { i18n } from '@kbn/i18n'; import { EuiTableRow, EuiTableRowCell, EuiIcon } from '@elastic/eui'; import { TruncatedContent } from '../../../../shared/truncate'; -import { EuiLink } from '../../../../shared/react_router_helpers'; +import { EuiLinkTo } from '../../../../shared/react_router_helpers'; import { Group } from '../../../types'; @@ -64,9 +64,9 @@ export const GroupRow: React.FC = ({ - + - +
    {GROUP_UPDATED_TEXT} @@ -93,9 +93,9 @@ export const GroupRow: React.FC = ({ )} - + - +
    diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups.test.tsx index cc50c4d0af5c..85175d156f88 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups.test.tsx @@ -27,7 +27,7 @@ import { TableFilters } from './components/table_filters'; import { DEFAULT_META } from '../../../shared/constants'; import { EuiFieldSearch, EuiLoadingSpinner } from '@elastic/eui'; -import { EuiButton as EuiLinkButton } from '../../../shared/react_router_helpers'; +import { EuiButtonTo } from '../../../shared/react_router_helpers'; const getSearchResults = jest.fn(); const openNewGroupModal = jest.fn(); @@ -138,7 +138,7 @@ describe('GroupOverview', () => { const action = shallow(); expect(action.find('[data-test-subj="InviteUsersButton"]')).toHaveLength(1); - expect(action.find(EuiLinkButton)).toHaveLength(1); + expect(action.find(EuiButtonTo)).toHaveLength(1); }); it('does not render inviteUsersButton when federated auth', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups.tsx index 4064391c0989..97647f149bc9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups.tsx @@ -10,7 +10,7 @@ import { useActions, useValues } from 'kea'; import { i18n } from '@kbn/i18n'; import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner, EuiSpacer } from '@elastic/eui'; -import { EuiButton as EuiLinkButton } from '../../../shared/react_router_helpers'; +import { EuiButtonTo } from '../../../shared/react_router_helpers'; import { AppLogic } from '../../app_logic'; @@ -61,7 +61,7 @@ export const Groups: React.FC = () => { if (newGroup && hasMessages) { messages[0].description = ( - { {i18n.translate('xpack.enterpriseSearch.workplaceSearch.groups.newGroup.action', { defaultMessage: 'Manage Group', })} - + ); } const clearFilters = hasFiltersSet && ; const inviteUsersButton = !isFederatedAuth ? ( - + {i18n.translate('xpack.enterpriseSearch.workplaceSearch.groups.inviteUsers.action', { defaultMessage: 'Invite users', })} - + ) : null; const headerAction = ( From b819287ce3489283d49def9a74dd2f82ae0b68be Mon Sep 17 00:00:00 2001 From: Scotty Bollinger Date: Wed, 18 Nov 2020 16:50:14 -0600 Subject: [PATCH 25/49] [Workplace Search] Update SourceIcon to match latest changes in ent-search (#83714) * Move source icons into subfolder * Copy over new icons * Update SourceIcon to account for full bleed images * Remove unused file * Fix broken icon path --- .../shared/assets/{ => source_icons}/box.svg | 0 .../assets/{ => source_icons}/confluence.svg | 0 .../connection_illustration.svg | 0 .../assets/{ => source_icons}/crawler.svg | 0 .../assets/{ => source_icons}/custom.svg | 0 .../assets/{ => source_icons}/drive.svg | 0 .../assets/{ => source_icons}/dropbox.svg | 0 .../assets/{ => source_icons}/github.svg | 0 .../assets/{ => source_icons}/gmail.svg | 0 .../assets/{ => source_icons}/google.svg | 0 .../{ => source_icons}/google_drive.svg | 0 .../shared/assets/{ => source_icons}/index.ts | 0 .../shared/assets/{ => source_icons}/jira.svg | 0 .../assets/{ => source_icons}/jira_server.svg | 0 .../{ => source_icons}/loading_small.svg | 0 .../assets/{ => source_icons}/office365.svg | 0 .../assets/{ => source_icons}/one_drive.svg | 0 .../assets/{ => source_icons}/outlook.svg | 0 .../assets/{ => source_icons}/people.svg | 0 .../assets/{ => source_icons}/salesforce.svg | 0 .../assets/{ => source_icons}/service_now.svg | 0 .../{ => source_icons}/share_circle.svg | 0 .../assets/{ => source_icons}/share_point.svg | 0 .../assets/{ => source_icons}/slack.svg | 0 .../assets/{ => source_icons}/zendesk.svg | 0 .../assets/sources_full_bleed/confluence.svg | 1 + .../assets/sources_full_bleed/custom.svg | 1 + .../assets/sources_full_bleed/dropbox.svg | 1 + .../assets/sources_full_bleed/github.svg | 1 + .../assets/sources_full_bleed/gmail.svg | 1 + .../sources_full_bleed/google_drive.svg | 1 + .../shared/assets/sources_full_bleed/index.ts | 42 +++++++++++++++++++ .../shared/assets/sources_full_bleed/jira.svg | 1 + .../assets/sources_full_bleed/jira_server.svg | 1 + .../assets/sources_full_bleed/onedrive.svg | 1 + .../assets/sources_full_bleed/salesforce.svg | 1 + .../assets/sources_full_bleed/servicenow.svg | 1 + .../assets/sources_full_bleed/sharepoint.svg | 1 + .../assets/sources_full_bleed/slack.svg | 1 + .../assets/sources_full_bleed/zendesk.svg | 1 + .../shared/source_icon/source_icon.tsx | 17 +++++--- .../views/overview/onboarding_steps.tsx | 2 +- 42 files changed, 69 insertions(+), 6 deletions(-) rename x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/{ => source_icons}/box.svg (100%) rename x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/{ => source_icons}/confluence.svg (100%) rename x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/{ => source_icons}/connection_illustration.svg (100%) rename x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/{ => source_icons}/crawler.svg (100%) rename x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/{ => source_icons}/custom.svg (100%) rename x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/{ => source_icons}/drive.svg (100%) rename x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/{ => source_icons}/dropbox.svg (100%) rename x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/{ => source_icons}/github.svg (100%) rename x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/{ => source_icons}/gmail.svg (100%) rename x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/{ => source_icons}/google.svg (100%) rename x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/{ => source_icons}/google_drive.svg (100%) rename x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/{ => source_icons}/index.ts (100%) rename x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/{ => source_icons}/jira.svg (100%) rename x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/{ => source_icons}/jira_server.svg (100%) rename x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/{ => source_icons}/loading_small.svg (100%) rename x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/{ => source_icons}/office365.svg (100%) rename x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/{ => source_icons}/one_drive.svg (100%) rename x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/{ => source_icons}/outlook.svg (100%) rename x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/{ => source_icons}/people.svg (100%) rename x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/{ => source_icons}/salesforce.svg (100%) rename x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/{ => source_icons}/service_now.svg (100%) rename x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/{ => source_icons}/share_circle.svg (100%) rename x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/{ => source_icons}/share_point.svg (100%) rename x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/{ => source_icons}/slack.svg (100%) rename x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/{ => source_icons}/zendesk.svg (100%) create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/confluence.svg create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/custom.svg create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/dropbox.svg create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/github.svg create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/gmail.svg create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/google_drive.svg create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/index.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/jira.svg create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/jira_server.svg create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/onedrive.svg create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/salesforce.svg create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/servicenow.svg create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/sharepoint.svg create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/slack.svg create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/zendesk.svg diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/box.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/box.svg similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/box.svg rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/box.svg diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/confluence.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/confluence.svg similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/confluence.svg rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/confluence.svg diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/connection_illustration.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/connection_illustration.svg similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/connection_illustration.svg rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/connection_illustration.svg diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/crawler.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/crawler.svg similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/crawler.svg rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/crawler.svg diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/custom.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/custom.svg similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/custom.svg rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/custom.svg diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/drive.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/drive.svg similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/drive.svg rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/drive.svg diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/dropbox.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/dropbox.svg similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/dropbox.svg rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/dropbox.svg diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/github.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/github.svg similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/github.svg rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/github.svg diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/gmail.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/gmail.svg similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/gmail.svg rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/gmail.svg diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/google.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/google.svg similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/google.svg rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/google.svg diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/google_drive.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/google_drive.svg similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/google_drive.svg rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/google_drive.svg diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/index.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/index.ts similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/index.ts rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/index.ts diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/jira.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/jira.svg similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/jira.svg rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/jira.svg diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/jira_server.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/jira_server.svg similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/jira_server.svg rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/jira_server.svg diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/loading_small.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/loading_small.svg similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/loading_small.svg rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/loading_small.svg diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/office365.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/office365.svg similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/office365.svg rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/office365.svg diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/one_drive.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/one_drive.svg similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/one_drive.svg rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/one_drive.svg diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/outlook.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/outlook.svg similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/outlook.svg rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/outlook.svg diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/people.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/people.svg similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/people.svg rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/people.svg diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/salesforce.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/salesforce.svg similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/salesforce.svg rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/salesforce.svg diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/service_now.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/service_now.svg similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/service_now.svg rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/service_now.svg diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/share_circle.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/share_circle.svg similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/share_circle.svg rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/share_circle.svg diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/share_point.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/share_point.svg similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/share_point.svg rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/share_point.svg diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/slack.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/slack.svg similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/slack.svg rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/slack.svg diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/zendesk.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/zendesk.svg similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/zendesk.svg rename to x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/source_icons/zendesk.svg diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/confluence.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/confluence.svg new file mode 100644 index 000000000000..7aac36a6fe3c --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/confluence.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/custom.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/custom.svg new file mode 100644 index 000000000000..cc07fbbc5087 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/custom.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/dropbox.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/dropbox.svg new file mode 100644 index 000000000000..01e5a7735de1 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/dropbox.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/github.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/github.svg new file mode 100644 index 000000000000..aa9c3e5b4514 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/github.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/gmail.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/gmail.svg new file mode 100644 index 000000000000..98d418244c22 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/gmail.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/google_drive.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/google_drive.svg new file mode 100644 index 000000000000..6541b3f9e753 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/google_drive.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/index.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/index.ts new file mode 100644 index 000000000000..e749fb948275 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/index.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import confluence from './confluence.svg'; +import custom from './custom.svg'; +import dropbox from './dropbox.svg'; +import github from './github.svg'; +import gmail from './gmail.svg'; +import googleDrive from './google_drive.svg'; +import jira from './jira.svg'; +import jiraServer from './jira_server.svg'; +import oneDrive from './onedrive.svg'; +import salesforce from './salesforce.svg'; +import serviceNow from './servicenow.svg'; +import sharePoint from './sharepoint.svg'; +import slack from './slack.svg'; +import zendesk from './zendesk.svg'; + +export const imagesFull = { + confluence, + confluenceCloud: confluence, + confluenceServer: confluence, + custom, + dropbox, + github, + githubEnterpriseServer: github, + gmail, + googleDrive, + jira, + jiraServer, + jiraCloud: jira, + oneDrive, + salesforce, + salesforceSandbox: salesforce, + serviceNow, + sharePoint, + slack, + zendesk, +} as { [key: string]: string }; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/jira.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/jira.svg new file mode 100644 index 000000000000..c12e55798d88 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/jira.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/jira_server.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/jira_server.svg new file mode 100644 index 000000000000..4dfd0fd91007 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/jira_server.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/onedrive.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/onedrive.svg new file mode 100644 index 000000000000..c390dff1e418 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/onedrive.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/salesforce.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/salesforce.svg new file mode 100644 index 000000000000..ef6d55294942 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/salesforce.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/servicenow.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/servicenow.svg new file mode 100644 index 000000000000..6388ec44d21d --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/servicenow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/sharepoint.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/sharepoint.svg new file mode 100644 index 000000000000..aebfd7a8e49c --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/sharepoint.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/slack.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/slack.svg new file mode 100644 index 000000000000..8f6fc0c987ea --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/slack.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/zendesk.svg b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/zendesk.svg new file mode 100644 index 000000000000..8afd143dd9a7 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/assets/sources_full_bleed/zendesk.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_icon/source_icon.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_icon/source_icon.tsx index 857b6f3aaf99..dec9e25fe244 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_icon/source_icon.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/source_icon/source_icon.tsx @@ -6,17 +6,17 @@ import React from 'react'; -// Prefer importing entire lodash library, e.g. import { get } from "lodash" -// eslint-disable-next-line no-restricted-imports -import _camelCase from 'lodash/camelCase'; +import { camelCase } from 'lodash'; -import { images } from '../assets'; +import { images } from '../assets/source_icons'; +import { imagesFull } from '../assets/sources_full_bleed'; interface SourceIconProps { serviceType: string; name: string; className?: string; wrapped?: boolean; + fullBleed?: boolean; } export const SourceIcon: React.FC = ({ @@ -24,8 +24,15 @@ export const SourceIcon: React.FC = ({ serviceType, className, wrapped, + fullBleed = false, }) => { - const icon = {name}; + const icon = ( + {name} + ); return wrapped ? (
    {icon} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_steps.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_steps.tsx index 7251461b848a..ed5136a6f7a4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_steps.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/onboarding_steps.tsx @@ -21,7 +21,7 @@ import { EuiButtonEmptyProps, EuiLinkProps, } from '@elastic/eui'; -import sharedSourcesIcon from '../../components/shared/assets/share_circle.svg'; +import sharedSourcesIcon from '../../components/shared/assets/source_icons/share_circle.svg'; import { TelemetryLogic } from '../../../shared/telemetry'; import { getWorkplaceSearchUrl } from '../../../shared/enterprise_search_url'; import { ORG_SOURCES_PATH, USERS_PATH, ORG_SETTINGS_PATH } from '../../routes'; From 71f972dc837d6186ad2b8157af8fb178d86e6a96 Mon Sep 17 00:00:00 2001 From: Constance Date: Wed, 18 Nov 2020 15:53:45 -0800 Subject: [PATCH 26/49] [App Search] Engine overview layout stub (#83504) * Set up Overview file * Finish Overview page logic, stub out empty/metric views * Stub in basic empty engine overview - Minus document creation button & API code example * Stub out EngineOverviewMetrics and unavailable empty prompt * Stub out EngineOverMetrics components (stats, charts, logs) * [Refactor] Pull out some document creation i18n strings to constants - They're repeated/reused by the DocumentCreationPopover component * PR feedback: Drop the regex * PR feedback: RecentLogs -> RecentApiLogs * PR feedback: Copy * PR feedback: Copy, sentence-casing --- .../components/analytics/constants.ts | 27 +++++ .../components/api_logs/constants.ts | 12 +++ .../document_creation/constants.tsx | 54 ++++++++++ .../app_search/components/engine/constants.ts | 4 - .../components/engine/engine_nav.tsx | 2 +- .../components/engine/engine_router.test.tsx | 3 +- .../components/engine/engine_router.tsx | 5 +- .../engine_overview/components/index.ts | 10 ++ .../components/recent_api_logs.test.tsx | 32 ++++++ .../components/recent_api_logs.tsx | 50 ++++++++++ .../components/total_charts.test.tsx | 46 +++++++++ .../components/total_charts.tsx | 99 +++++++++++++++++++ .../components/total_stats.test.tsx | 51 ++++++++++ .../components/total_stats.tsx | 37 +++++++ .../components/unavailable_prompt.test.tsx | 18 ++++ .../components/unavailable_prompt.tsx | 30 ++++++ .../components/engine_overview/constants.ts | 27 +++++ .../engine_overview/engine_overview.test.tsx | 80 +++++++++++++++ .../engine_overview/engine_overview.tsx | 44 +++++++++ .../engine_overview_empty.test.tsx | 40 ++++++++ .../engine_overview/engine_overview_empty.tsx | 98 ++++++++++++++++++ .../engine_overview_metrics.test.tsx | 34 +++++++ .../engine_overview_metrics.tsx | 44 +++++++++ .../components/engine_overview/index.ts | 2 + 24 files changed, 841 insertions(+), 8 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/constants.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/constants.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/constants.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/index.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/recent_api_logs.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/recent_api_logs.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_charts.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_charts.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_stats.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_stats.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/unavailable_prompt.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/unavailable_prompt.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/constants.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_metrics.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_metrics.tsx diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/constants.ts new file mode 100644 index 000000000000..51ae11ad2ab8 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/constants.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const TOTAL_DOCUMENTS = i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.analytics.totalDocuments', + { defaultMessage: 'Total documents' } +); + +export const TOTAL_API_OPERATIONS = i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.analytics.totalApiOperations', + { defaultMessage: 'Total API operations' } +); + +export const TOTAL_QUERIES = i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.analytics.totalQueries', + { defaultMessage: 'Total queries' } +); + +export const TOTAL_CLICKS = i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.analytics.totalClicks', + { defaultMessage: 'Total clicks' } +); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/constants.ts new file mode 100644 index 000000000000..6fd60b7a34eb --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/constants.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const RECENT_API_EVENTS = i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.apiLogs.recent', + { defaultMessage: 'Recent API events' } +); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/constants.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/constants.tsx new file mode 100644 index 000000000000..736ef09fa6cf --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/constants.tsx @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiCode, EuiLink } from '@elastic/eui'; + +import { DOCS_PREFIX } from '../../routes'; + +export const DOCUMENT_CREATION_DESCRIPTION = ( + .json, + postCode: POST, + documentsApiLink: ( + + documents API + + ), + apiStrong: Indexing by API, + }} + /> +); + +export const DOCUMENT_API_INDEXING_TITLE = i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.documentCreation.api.title', + { defaultMessage: 'Indexing by API' } +); + +export const DOCUMENT_API_INDEXING_DESCRIPTION = ( + + documents API + + ), + clientLibrariesLink: ( + + client libraries + + ), + }} + /> +); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/constants.ts index 3c963e415f33..9ce524038075 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/constants.ts @@ -9,10 +9,6 @@ import { i18n } from '@kbn/i18n'; // TODO: It's very likely that we'll move these i18n constants to their respective component // folders once those are migrated over. This is a temporary way of DRYing them out for now. -export const OVERVIEW_TITLE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.overview.title', - { defaultMessage: 'Overview' } -); export const ANALYTICS_TITLE = i18n.translate( 'xpack.enterpriseSearch.appSearch.engine.analytics.title', { defaultMessage: 'Analytics' } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx index 77aca8a71994..a7ac6f203b1f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx @@ -28,8 +28,8 @@ import { } from '../../routes'; import { getAppSearchUrl } from '../../../shared/enterprise_search_url'; import { ENGINES_TITLE } from '../engines'; +import { OVERVIEW_TITLE } from '../engine_overview'; import { - OVERVIEW_TITLE, ANALYTICS_TITLE, DOCUMENTS_TITLE, SCHEMA_TITLE, diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.test.tsx index 8f067754c48a..e8609c169855 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.test.tsx @@ -18,6 +18,7 @@ jest.mock('../../../shared/flash_messages', () => ({ import { setQueuedErrorMessage } from '../../../shared/flash_messages'; import { Loading } from '../../../shared/loading'; +import { EngineOverview } from '../engine_overview'; import { EngineRouter } from './'; @@ -71,7 +72,7 @@ describe('EngineRouter', () => { const wrapper = shallow(); expect(wrapper.find(Switch)).toHaveLength(1); - expect(wrapper.find('[data-test-subj="EngineOverviewTODO"]')).toHaveLength(1); + expect(wrapper.find(EngineOverview)).toHaveLength(1); }); it('renders an analytics view', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx index 9833305c438c..f586106924f2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx @@ -31,8 +31,8 @@ import { // ENGINE_API_LOGS_PATH, } from '../../routes'; import { ENGINES_TITLE } from '../engines'; +import { OVERVIEW_TITLE } from '../engine_overview'; import { - OVERVIEW_TITLE, ANALYTICS_TITLE, // DOCUMENTS_TITLE, // SCHEMA_TITLE, @@ -46,6 +46,7 @@ import { } from './constants'; import { Loading } from '../../../shared/loading'; +import { EngineOverview } from '../engine_overview'; import { EngineLogic } from './'; @@ -100,7 +101,7 @@ export const EngineRouter: React.FC = () => { )} -
    Overview
    +
    ); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/index.ts new file mode 100644 index 000000000000..11e7b2a5fba9 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { UnavailablePrompt } from './unavailable_prompt'; +export { TotalStats } from './total_stats'; +export { TotalCharts } from './total_charts'; +export { RecentApiLogs } from './recent_api_logs'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/recent_api_logs.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/recent_api_logs.test.tsx new file mode 100644 index 000000000000..725c89894b80 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/recent_api_logs.test.tsx @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { setMockValues } from '../../../../__mocks__/kea.mock'; + +import React from 'react'; +import { shallow, ShallowWrapper } from 'enzyme'; + +import { EuiButton } from '../../../../shared/react_router_helpers'; + +import { RecentApiLogs } from './recent_api_logs'; + +describe('RecentApiLogs', () => { + let wrapper: ShallowWrapper; + + beforeAll(() => { + jest.clearAllMocks(); + setMockValues({ + engineName: 'some-engine', + }); + wrapper = shallow(); + }); + + it('renders the recent API logs table', () => { + expect(wrapper.find('h2').text()).toEqual('Recent API events'); + expect(wrapper.find(EuiButton).prop('to')).toEqual('/engines/some-engine/api-logs'); + // TODO: expect(wrapper.find(ApiLogsTable)).toHaveLength(1) + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/recent_api_logs.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/recent_api_logs.tsx new file mode 100644 index 000000000000..05eb73111688 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/recent_api_logs.tsx @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { useValues } from 'kea'; + +import { + EuiPageContent, + EuiPageContentHeader, + EuiPageContentHeaderSection, + EuiPageContentBody, + EuiTitle, +} from '@elastic/eui'; + +import { EuiButton } from '../../../../shared/react_router_helpers'; + +import { ENGINE_API_LOGS_PATH, getEngineRoute } from '../../../routes'; +import { RECENT_API_EVENTS } from '../../api_logs/constants'; +import { VIEW_API_LOGS } from '../constants'; + +import { EngineLogic } from '../../engine'; + +export const RecentApiLogs: React.FC = () => { + const { engineName } = useValues(EngineLogic); + const engineRoute = getEngineRoute(engineName); + + return ( + + + + +

    {RECENT_API_EVENTS}

    +
    +
    + + + {VIEW_API_LOGS} + + +
    + + TODO: API Logs Table + {/* */} + +
    + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_charts.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_charts.test.tsx new file mode 100644 index 000000000000..a56b41dfb050 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_charts.test.tsx @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { setMockValues } from '../../../../__mocks__/kea.mock'; + +import React from 'react'; +import { shallow, ShallowWrapper } from 'enzyme'; + +import { EuiButton } from '../../../../shared/react_router_helpers'; + +import { TotalCharts } from './total_charts'; + +describe('TotalCharts', () => { + let wrapper: ShallowWrapper; + + beforeAll(() => { + jest.clearAllMocks(); + setMockValues({ + engineName: 'some-engine', + startDate: '1970-01-01', + endDate: '1970-01-08', + queriesPerDay: [0, 1, 2, 3, 5, 10, 50], + operationsPerDay: [0, 0, 0, 0, 0, 0, 0], + }); + wrapper = shallow(); + }); + + it('renders the total queries chart', () => { + const chart = wrapper.find('[data-test-subj="TotalQueriesChart"]'); + + expect(chart.find('h2').text()).toEqual('Total queries'); + expect(chart.find(EuiButton).prop('to')).toEqual('/engines/some-engine/analytics'); + // TODO: find chart component + }); + + it('renders the total API operations chart', () => { + const chart = wrapper.find('[data-test-subj="TotalApiOperationsChart"]'); + + expect(chart.find('h2').text()).toEqual('Total API operations'); + expect(chart.find(EuiButton).prop('to')).toEqual('/engines/some-engine/api-logs'); + // TODO: find chart component + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_charts.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_charts.tsx new file mode 100644 index 000000000000..e27fe3cdfc79 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_charts.tsx @@ -0,0 +1,99 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { useValues } from 'kea'; + +import { + EuiFlexGroup, + EuiFlexItem, + EuiPageContent, + EuiPageContentHeader, + EuiPageContentHeaderSection, + EuiPageContentBody, + EuiTitle, + EuiText, +} from '@elastic/eui'; + +import { EuiButton } from '../../../../shared/react_router_helpers'; + +import { ENGINE_ANALYTICS_PATH, ENGINE_API_LOGS_PATH, getEngineRoute } from '../../../routes'; +import { TOTAL_QUERIES, TOTAL_API_OPERATIONS } from '../../analytics/constants'; +import { VIEW_ANALYTICS, VIEW_API_LOGS, LAST_7_DAYS } from '../constants'; + +import { EngineLogic } from '../../engine'; +import { EngineOverviewLogic } from '../'; + +export const TotalCharts: React.FC = () => { + const { engineName } = useValues(EngineLogic); + const engineRoute = getEngineRoute(engineName); + + const { + // startDate, + // endDate, + // queriesPerDay, + // operationsPerDay, + } = useValues(EngineOverviewLogic); + + return ( + + + + + + +

    {TOTAL_QUERIES}

    +
    + + {LAST_7_DAYS} + +
    + + + {VIEW_ANALYTICS} + + +
    + + TODO: Analytics chart + {/* */} + +
    +
    + + + + + +

    {TOTAL_API_OPERATIONS}

    +
    + + {LAST_7_DAYS} + +
    + + + {VIEW_API_LOGS} + + +
    + + TODO: API Logs chart + {/* */} + +
    +
    +
    + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_stats.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_stats.test.tsx new file mode 100644 index 000000000000..6cb47e8b419f --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_stats.test.tsx @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { setMockValues } from '../../../../__mocks__/kea.mock'; + +import React from 'react'; +import { shallow, ShallowWrapper } from 'enzyme'; +import { EuiStat } from '@elastic/eui'; + +import { TotalStats } from './total_stats'; + +describe('TotalStats', () => { + let wrapper: ShallowWrapper; + + beforeAll(() => { + jest.clearAllMocks(); + setMockValues({ + totalQueries: 11, + documentCount: 22, + totalClicks: 33, + }); + wrapper = shallow(); + }); + + it('renders the total queries stat', () => { + expect(wrapper.find('[data-test-subj="TotalQueriesCard"]')).toHaveLength(1); + + const card = wrapper.find(EuiStat).at(0); + expect(card.prop('title')).toEqual(11); + expect(card.prop('description')).toEqual('Total queries'); + }); + + it('renders the total documents stat', () => { + expect(wrapper.find('[data-test-subj="TotalDocumentsCard"]')).toHaveLength(1); + + const card = wrapper.find(EuiStat).at(1); + expect(card.prop('title')).toEqual(22); + expect(card.prop('description')).toEqual('Total documents'); + }); + + it('renders the total clicks stat', () => { + expect(wrapper.find('[data-test-subj="TotalClicksCard"]')).toHaveLength(1); + + const card = wrapper.find(EuiStat).at(2); + expect(card.prop('title')).toEqual(33); + expect(card.prop('description')).toEqual('Total clicks'); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_stats.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_stats.tsx new file mode 100644 index 000000000000..a27142938f55 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_stats.tsx @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { useValues } from 'kea'; +import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiStat } from '@elastic/eui'; + +import { TOTAL_QUERIES, TOTAL_DOCUMENTS, TOTAL_CLICKS } from '../../analytics/constants'; + +import { EngineOverviewLogic } from '../'; + +export const TotalStats: React.FC = () => { + const { totalQueries, documentCount, totalClicks } = useValues(EngineOverviewLogic); + + return ( + + + + + + + + + + + + + + + + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/unavailable_prompt.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/unavailable_prompt.test.tsx new file mode 100644 index 000000000000..3ddfd14b0eb0 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/unavailable_prompt.test.tsx @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; +import { EuiEmptyPrompt } from '@elastic/eui'; + +import { UnavailablePrompt } from './unavailable_prompt'; + +describe('UnavailablePrompt', () => { + it('renders', () => { + const wrapper = shallow(); + expect(wrapper.find(EuiEmptyPrompt)).toHaveLength(1); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/unavailable_prompt.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/unavailable_prompt.tsx new file mode 100644 index 000000000000..e9cc6e2f05bf --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/unavailable_prompt.tsx @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +import { i18n } from '@kbn/i18n'; +import { EuiEmptyPrompt } from '@elastic/eui'; + +export const UnavailablePrompt: React.FC = () => ( + + {i18n.translate('xpack.enterpriseSearch.appSearch.engine.overview.unavailableTitle', { + defaultMessage: 'Dashboard metrics are currently unavailable', + })} + + } + body={ +

    + {i18n.translate('xpack.enterpriseSearch.appSearch.engine.overview.unavailableBody', { + defaultMessage: 'Please try again in a few minutes.', + })} +

    + } + /> +); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/constants.ts new file mode 100644 index 000000000000..797811ec6cde --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/constants.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const OVERVIEW_TITLE = i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.overview.title', + { defaultMessage: 'Overview' } +); + +export const VIEW_ANALYTICS = i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.overview.analyticsLink', + { defaultMessage: 'View analytics' } +); + +export const VIEW_API_LOGS = i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.overview.apiLogsLink', + { defaultMessage: 'View API logs' } +); + +export const LAST_7_DAYS = i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.overview.chartDuration', + { defaultMessage: 'Last 7 days' } +); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.test.tsx new file mode 100644 index 000000000000..196fb2ca2bf1 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.test.tsx @@ -0,0 +1,80 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import '../../../__mocks__/shallow_useeffect.mock'; +import { setMockValues, setMockActions } from '../../../__mocks__/kea.mock'; + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { Loading } from '../../../shared/loading'; +import { EmptyEngineOverview } from './engine_overview_empty'; +import { EngineOverviewMetrics } from './engine_overview_metrics'; +import { EngineOverview } from './'; + +describe('EngineOverview', () => { + const values = { + dataLoading: false, + documentCount: 0, + myRole: {}, + isMetaEngine: false, + }; + const actions = { + pollForOverviewMetrics: jest.fn(), + }; + + beforeEach(() => { + jest.clearAllMocks(); + setMockValues(values); + setMockActions(actions); + }); + + it('renders', () => { + const wrapper = shallow(); + expect(wrapper.find('[data-test-subj="EngineOverview"]')).toHaveLength(1); + }); + + it('initializes data on mount', () => { + shallow(); + expect(actions.pollForOverviewMetrics).toHaveBeenCalledTimes(1); + }); + + it('renders a loading component if async data is still loading', () => { + setMockValues({ ...values, dataLoading: true }); + const wrapper = shallow(); + expect(wrapper.find(Loading)).toHaveLength(1); + }); + + describe('EmptyEngineOverview', () => { + it('renders when the engine has no documents & the user can add documents', () => { + const myRole = { canManageEngineDocuments: true, canViewEngineCredentials: true }; + setMockValues({ ...values, myRole, documentCount: 0 }); + const wrapper = shallow(); + expect(wrapper.find(EmptyEngineOverview)).toHaveLength(1); + }); + }); + + describe('EngineOverviewMetrics', () => { + it('renders when the engine has documents', () => { + setMockValues({ ...values, documentCount: 1 }); + const wrapper = shallow(); + expect(wrapper.find(EngineOverviewMetrics)).toHaveLength(1); + }); + + it('renders when the user does not have the ability to add documents', () => { + const myRole = { canManageEngineDocuments: false, canViewEngineCredentials: false }; + setMockValues({ ...values, myRole }); + const wrapper = shallow(); + expect(wrapper.find(EngineOverviewMetrics)).toHaveLength(1); + }); + + it('always renders for meta engines', () => { + setMockValues({ ...values, isMetaEngine: true }); + const wrapper = shallow(); + expect(wrapper.find(EngineOverviewMetrics)).toHaveLength(1); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.tsx new file mode 100644 index 000000000000..dd43bc67b3e8 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.tsx @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useEffect } from 'react'; +import { useActions, useValues } from 'kea'; + +import { AppLogic } from '../../app_logic'; +import { EngineLogic } from '../engine'; +import { Loading } from '../../../shared/loading'; + +import { EngineOverviewLogic } from './'; +import { EmptyEngineOverview } from './engine_overview_empty'; +import { EngineOverviewMetrics } from './engine_overview_metrics'; + +export const EngineOverview: React.FC = () => { + const { + myRole: { canManageEngineDocuments, canViewEngineCredentials }, + } = useValues(AppLogic); + const { isMetaEngine } = useValues(EngineLogic); + + const { pollForOverviewMetrics } = useActions(EngineOverviewLogic); + const { dataLoading, documentCount } = useValues(EngineOverviewLogic); + + useEffect(() => { + pollForOverviewMetrics(); + }, []); + + if (dataLoading) { + return ; + } + + const engineHasDocuments = documentCount > 0; + const canAddDocuments = canManageEngineDocuments && canViewEngineCredentials; + const showEngineOverview = engineHasDocuments || !canAddDocuments || isMetaEngine; + + return ( +
    + {showEngineOverview ? : } +
    + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.test.tsx new file mode 100644 index 000000000000..8ebe09820a67 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.test.tsx @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import '../../../__mocks__/enterprise_search_url.mock'; +import { setMockValues } from '../../../__mocks__/kea.mock'; + +import React from 'react'; +import { shallow, ShallowWrapper } from 'enzyme'; +import { EuiButton } from '@elastic/eui'; + +import { CURRENT_MAJOR_VERSION } from '../../../../../common/version'; + +import { EmptyEngineOverview } from './engine_overview_empty'; + +describe('EmptyEngineOverview', () => { + let wrapper: ShallowWrapper; + + beforeAll(() => { + jest.clearAllMocks(); + setMockValues({ + engineName: 'empty-engine', + }); + wrapper = shallow(); + }); + + it('renders', () => { + expect(wrapper.find('h1').text()).toEqual('Engine setup'); + expect(wrapper.find('h2').text()).toEqual('Setting up the “empty-engine” engine'); + expect(wrapper.find('h3').text()).toEqual('Indexing by API'); + }); + + it('renders correctly versioned documentation URLs', () => { + expect(wrapper.find(EuiButton).prop('href')).toEqual( + `https://www.elastic.co/guide/en/app-search/${CURRENT_MAJOR_VERSION}/index.html` + ); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.tsx new file mode 100644 index 000000000000..f2bf5a54f810 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.tsx @@ -0,0 +1,98 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { useValues } from 'kea'; + +import { i18n } from '@kbn/i18n'; +import { + EuiPageHeader, + EuiPageHeaderSection, + EuiPageContent, + EuiPageContentHeader, + EuiPageContentBody, + EuiTitle, + EuiText, + EuiButton, + EuiSpacer, +} from '@elastic/eui'; + +import { EngineLogic } from '../engine'; + +import { DOCS_PREFIX } from '../../routes'; +import { + DOCUMENT_CREATION_DESCRIPTION, + DOCUMENT_API_INDEXING_TITLE, + DOCUMENT_API_INDEXING_DESCRIPTION, +} from '../document_creation/constants'; +// TODO +// import { DocumentCreationButtons, CodeExample } from '../document_creation' + +export const EmptyEngineOverview: React.FC = () => { + const { engineName } = useValues(EngineLogic); + + return ( + <> + + + +

    + {i18n.translate('xpack.enterpriseSearch.appSearch.engine.overview.empty.heading', { + defaultMessage: 'Engine setup', + })} +

    +
    +
    + + + {i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.overview.empty.headingAction', + { defaultMessage: 'View documentation' } + )} + + +
    + + + +

    + {i18n.translate('xpack.enterpriseSearch.appSearch.engine.overview.empty.subheading', { + defaultMessage: 'Setting up the “{engineName}” engine', + values: { engineName }, + })} +

    +
    +
    + + +

    {DOCUMENT_CREATION_DESCRIPTION}

    +
    + + {/* TODO: */} +
    + + + +

    {DOCUMENT_API_INDEXING_TITLE}

    +
    +
    + + +

    {DOCUMENT_API_INDEXING_DESCRIPTION}

    +

    + {i18n.translate('xpack.enterpriseSearch.appSearch.engine.overview.empty.apiExample', { + defaultMessage: + 'To see the API in action, you can experiment with the example request below using a command line or a client library.', + })} +

    +
    + + {/* */} +
    +
    + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_metrics.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_metrics.test.tsx new file mode 100644 index 000000000000..8250446e231b --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_metrics.test.tsx @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { setMockValues } from '../../../__mocks__/kea.mock'; + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { UnavailablePrompt, TotalStats, TotalCharts, RecentApiLogs } from './components'; +import { EngineOverviewMetrics } from './engine_overview_metrics'; + +describe('EngineOverviewMetrics', () => { + it('renders', () => { + const wrapper = shallow(); + expect(wrapper.find('h1').text()).toEqual('Engine overview'); + }); + + it('renders an unavailable prompt if engine data is still indexing', () => { + setMockValues({ apiLogsUnavailable: true }); + const wrapper = shallow(); + expect(wrapper.find(UnavailablePrompt)).toHaveLength(1); + }); + + it('renders total stats, charts, and recent logs when metrics are available', () => { + setMockValues({ apiLogsUnavailable: false }); + const wrapper = shallow(); + expect(wrapper.find(TotalStats)).toHaveLength(1); + expect(wrapper.find(TotalCharts)).toHaveLength(1); + expect(wrapper.find(RecentApiLogs)).toHaveLength(1); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_metrics.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_metrics.tsx new file mode 100644 index 000000000000..9630f6fa2f81 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_metrics.tsx @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { useValues } from 'kea'; + +import { i18n } from '@kbn/i18n'; +import { EuiPageHeader, EuiTitle, EuiSpacer } from '@elastic/eui'; + +import { EngineOverviewLogic } from './'; + +import { UnavailablePrompt, TotalStats, TotalCharts, RecentApiLogs } from './components'; + +export const EngineOverviewMetrics: React.FC = () => { + const { apiLogsUnavailable } = useValues(EngineOverviewLogic); + + return ( + <> + + +

    + {i18n.translate('xpack.enterpriseSearch.appSearch.engine.overview.heading', { + defaultMessage: 'Engine overview', + })} +

    +
    +
    + {apiLogsUnavailable ? ( + + ) : ( + <> + + + + + + + )} + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/index.ts index fcd92ba6a338..82c5d7dc8e60 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/index.ts @@ -5,3 +5,5 @@ */ export { EngineOverviewLogic } from './engine_overview_logic'; +export { EngineOverview } from './engine_overview'; +export { OVERVIEW_TITLE } from './constants'; From f2d97a9fe2064eb47c4e871a41cd07e7cbae9258 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Thu, 19 Nov 2020 00:08:50 +0000 Subject: [PATCH 27/49] chore(NA): update lmdb store to v0.8.15 (#83726) * chore(NA): upgrade lmdb-store to v0.8.15 * chore(NA): remove unused ts-error statements --- package.json | 2 +- packages/kbn-optimizer/src/node/cache.ts | 4 ---- yarn.lock | 25 ++++++++++-------------- 3 files changed, 11 insertions(+), 20 deletions(-) diff --git a/package.json b/package.json index 87e51abe49be..23f7a0b43065 100644 --- a/package.json +++ b/package.json @@ -723,7 +723,7 @@ "less": "npm:@elastic/less@2.7.3-kibana", "license-checker": "^16.0.0", "listr": "^0.14.1", - "lmdb-store": "^0.8.11", + "lmdb-store": "^0.8.15", "load-grunt-config": "^3.0.1", "loader-utils": "^1.2.3", "log-symbols": "^2.2.0", diff --git a/packages/kbn-optimizer/src/node/cache.ts b/packages/kbn-optimizer/src/node/cache.ts index dc96bf47fafc..a73dba5b1646 100644 --- a/packages/kbn-optimizer/src/node/cache.ts +++ b/packages/kbn-optimizer/src/node/cache.ts @@ -49,23 +49,19 @@ export class Cache { this.codes = LmdbStore.open({ name: 'codes', path: CACHE_DIR, - // @ts-expect-error See https://github.com/DoctorEvidence/lmdb-store/pull/18 maxReaders: 500, }); - // @ts-expect-error See https://github.com/DoctorEvidence/lmdb-store/pull/18 this.atimes = this.codes.openDB({ name: 'atimes', encoding: 'string', }); - // @ts-expect-error See https://github.com/DoctorEvidence/lmdb-store/pull/18 this.mtimes = this.codes.openDB({ name: 'mtimes', encoding: 'string', }); - // @ts-expect-error See https://github.com/DoctorEvidence/lmdb-store/pull/18 this.sourceMaps = this.codes.openDB({ name: 'sourceMaps', encoding: 'msgpack', diff --git a/yarn.lock b/yarn.lock index 2a82e7024a89..9be39ea18e3d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18798,24 +18798,24 @@ livereload-js@^2.3.0: resolved "https://registry.yarnpkg.com/livereload-js/-/livereload-js-2.4.0.tgz#447c31cf1ea9ab52fc20db615c5ddf678f78009c" integrity sha512-XPQH8Z2GDP/Hwz2PCDrh2mth4yFejwA1OZ/81Ti3LgKyhDcEjsSsqFWZojHG0va/duGd+WyosY7eXLDoOyqcPw== -lmdb-store-0.9@0.7.2: - version "0.7.2" - resolved "https://registry.yarnpkg.com/lmdb-store-0.9/-/lmdb-store-0.9-0.7.2.tgz#45b907a46d0a676fee955629bd2f70f06efb26bb" - integrity sha512-/MO8G6p4l7ku1ltCCdE/2ZOtSQBSM0B02vIemMHjoKgjE/fooanJYXIFwtZYM5r/hBDxmO+L3q5ASAXbfHQ6pQ== +lmdb-store-0.9@0.7.3: + version "0.7.3" + resolved "https://registry.yarnpkg.com/lmdb-store-0.9/-/lmdb-store-0.9-0.7.3.tgz#c2cb27dfa916ab966cceed692c67e4236813104a" + integrity sha512-t8iCnN6T3NZPFelPmjYIjCg+nhGbOdc0xcHCG40v01AWRTN49OINSt2k/u+16/2/HrI+b6Ssb8WByXUhbyHz6w== dependencies: fs-extra "^9.0.1" msgpackr "^0.5.3" nan "^2.14.1" node-gyp-build "^4.2.3" - weak-lru-cache "^0.2.0" + weak-lru-cache "^0.3.9" -lmdb-store@^0.8.11: - version "0.8.11" - resolved "https://registry.yarnpkg.com/lmdb-store/-/lmdb-store-0.8.11.tgz#7f7c756a115ceab32c51c0948444bfd5d1716ab3" - integrity sha512-CFgxh2/TL1NXyJ8FQPXY50O/gADxih7Gx0RjKRScGlyxihqSLd/fGzMkbvDdeAOAS8bsnYpLojAMTSeNiwN8dQ== +lmdb-store@^0.8.15: + version "0.8.15" + resolved "https://registry.yarnpkg.com/lmdb-store/-/lmdb-store-0.8.15.tgz#4efb0341c2df505dd6f3a7f26f834f0a142a80a2" + integrity sha512-4Q0WZh2FmcJC6esZRUWMfkCmNiz0WU9cOgrxt97ZMTnVfHyOdZhtrt0oOF5EQPfetxxJf/BorKY28aX92R6G6g== dependencies: fs-extra "^9.0.1" - lmdb-store-0.9 "0.7.2" + lmdb-store-0.9 "0.7.3" msgpackr "^0.5.4" nan "^2.14.1" node-gyp-build "^4.2.3" @@ -29141,11 +29141,6 @@ wcwidth@^1.0.1: dependencies: defaults "^1.0.3" -weak-lru-cache@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/weak-lru-cache/-/weak-lru-cache-0.2.0.tgz#447379ccff6dfda1b7a9566c9ef168260be859d1" - integrity sha512-M1l5CzKvM7maa7tCbtL0NW6sOnp8gqup853+9Aq7GL0XNWKNnFOkeE3v3Z5X2IeMzedPwQyPbi4RlFvD6rxs7A== - weak-lru-cache@^0.3.9: version "0.3.9" resolved "https://registry.yarnpkg.com/weak-lru-cache/-/weak-lru-cache-0.3.9.tgz#9e56920d4115e8542625d8ef8cc278cbd97f7624" From a04cb37f2b510de522e9284ff71cfeab00f7c06e Mon Sep 17 00:00:00 2001 From: Chris Cowan Date: Wed, 18 Nov 2020 18:14:22 -0700 Subject: [PATCH 28/49] [Metrics UI] Optimizations for Snapshot and Inventory Metadata (#83596) * [Metrics UI] Add time range to inventory metadata request * Adding optimizations for snapshot request * Adding sorting to dataset request * Only query inventory metadata for AWS * moving check inside getCloudMetadata * removing unused deps --- .../common/http_api/inventory_meta_api.ts | 1 + .../inventory_view/components/layout.tsx | 2 +- .../components/toolbars/toolbar.tsx | 5 ++-- .../hooks/use_inventory_meta.ts | 7 +++++- .../inventory_view/hooks/use_snaphot.ts | 2 +- .../server/routes/inventory_metadata/index.ts | 6 +++-- .../lib/get_cloud_metadata.ts | 24 +++++++++++++++++-- .../lib/find_interval_for_metrics.ts | 3 ++- .../lib/get_dataset_for_field.ts | 21 ++++++++++++++-- .../lib/create_timerange_with_interval.ts | 5 +++- .../server/routes/snapshot/lib/get_nodes.ts | 13 +++++----- 11 files changed, 69 insertions(+), 20 deletions(-) diff --git a/x-pack/plugins/infra/common/http_api/inventory_meta_api.ts b/x-pack/plugins/infra/common/http_api/inventory_meta_api.ts index 77de515c9cc4..43f3b2037e38 100644 --- a/x-pack/plugins/infra/common/http_api/inventory_meta_api.ts +++ b/x-pack/plugins/infra/common/http_api/inventory_meta_api.ts @@ -21,6 +21,7 @@ export const InventoryMetaResponseRT = rt.type({ export const InventoryMetaRequestRT = rt.type({ sourceId: rt.string, nodeType: ItemTypeRT, + currentTime: rt.number, }); export type InventoryMetaRequest = rt.TypeOf; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx index 92aa015113b2..2e5ddab77d37 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx @@ -124,7 +124,7 @@ export const Layout = () => { <> - + diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/toolbars/toolbar.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/toolbars/toolbar.tsx index e9ffc56d8c47..7bcb1270c30a 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/toolbars/toolbar.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/toolbars/toolbar.tsx @@ -54,11 +54,12 @@ const wrapToolbarItems = ( interface Props { nodeType: InventoryItemType; + currentTime: number; } -export const Toolbar = ({ nodeType }: Props) => { +export const Toolbar = ({ nodeType, currentTime }: Props) => { const { sourceId } = useSourceContext(); - const { accounts, regions } = useInventoryMeta(sourceId, nodeType); + const { accounts, regions } = useInventoryMeta(sourceId, nodeType, currentTime); const ToolbarItems = findToolbar(nodeType); return wrapToolbarItems(ToolbarItems, accounts, regions); }; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_inventory_meta.ts b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_inventory_meta.ts index b038491690a1..01811eb21a11 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_inventory_meta.ts +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_inventory_meta.ts @@ -15,7 +15,11 @@ import { } from '../../../../../common/http_api/inventory_meta_api'; import { InventoryItemType } from '../../../../../common/inventory_models/types'; -export function useInventoryMeta(sourceId: string, nodeType: InventoryItemType) { +export function useInventoryMeta( + sourceId: string, + nodeType: InventoryItemType, + currentTime: number +) { const decodeResponse = (response: any) => { return pipe( InventoryMetaResponseRT.decode(response), @@ -29,6 +33,7 @@ export function useInventoryMeta(sourceId: string, nodeType: InventoryItemType) JSON.stringify({ sourceId, nodeType, + currentTime, }), decodeResponse ); diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_snaphot.ts b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_snaphot.ts index 702213516c12..eec46b048628 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_snaphot.ts +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/hooks/use_snaphot.ts @@ -44,7 +44,7 @@ export function useSnapshot( interval: '1m', to: currentTime, from: currentTime - 1200 * 1000, - lookbackSize: 20, + lookbackSize: 5, }; const { error, loading, response, makeRequest } = useHTTPRequest( diff --git a/x-pack/plugins/infra/server/routes/inventory_metadata/index.ts b/x-pack/plugins/infra/server/routes/inventory_metadata/index.ts index 8b5271cb960c..c784aa0f7d20 100644 --- a/x-pack/plugins/infra/server/routes/inventory_metadata/index.ts +++ b/x-pack/plugins/infra/server/routes/inventory_metadata/index.ts @@ -33,7 +33,7 @@ export const initInventoryMetaRoute = (libs: InfraBackendLibs) => { }, async (requestContext, request, response) => { try { - const { sourceId, nodeType } = pipe( + const { sourceId, nodeType, currentTime } = pipe( InventoryMetaRequestRT.decode(request.body), fold(throwErrors(Boom.badRequest), identity) ); @@ -42,11 +42,13 @@ export const initInventoryMetaRoute = (libs: InfraBackendLibs) => { requestContext.core.savedObjects.client, sourceId ); + const awsMetadata = await getCloudMetadata( framework, requestContext, configuration, - nodeType + nodeType, + currentTime ); return response.ok({ diff --git a/x-pack/plugins/infra/server/routes/inventory_metadata/lib/get_cloud_metadata.ts b/x-pack/plugins/infra/server/routes/inventory_metadata/lib/get_cloud_metadata.ts index b4288dae0c22..af9e9c5f57c5 100644 --- a/x-pack/plugins/infra/server/routes/inventory_metadata/lib/get_cloud_metadata.ts +++ b/x-pack/plugins/infra/server/routes/inventory_metadata/lib/get_cloud_metadata.ts @@ -25,9 +25,18 @@ export const getCloudMetadata = async ( framework: KibanaFramework, req: RequestHandlerContext, sourceConfiguration: InfraSourceConfiguration, - nodeType: InventoryItemType + nodeType: InventoryItemType, + currentTime: number ): Promise => { const model = findInventoryModel(nodeType); + // Only run this for AWS modules, eventually we might have more. + if (model.requiredModule !== 'aws') { + return { + accounts: [], + projects: [], + regions: [], + }; + } const metricQuery = { allowNoIndices: true, @@ -36,7 +45,18 @@ export const getCloudMetadata = async ( body: { query: { bool: { - must: [{ match: { 'event.module': model.requiredModule } }], + must: [ + { + range: { + [sourceConfiguration.fields.timestamp]: { + gte: currentTime - 86400000, // 24 hours ago + lte: currentTime, + format: 'epoch_millis', + }, + }, + }, + { match: { 'event.module': model.requiredModule } }, + ], }, }, size: 0, diff --git a/x-pack/plugins/infra/server/routes/metrics_explorer/lib/find_interval_for_metrics.ts b/x-pack/plugins/infra/server/routes/metrics_explorer/lib/find_interval_for_metrics.ts index 8ab0f4a44c85..b3d960e30404 100644 --- a/x-pack/plugins/infra/server/routes/metrics_explorer/lib/find_interval_for_metrics.ts +++ b/x-pack/plugins/infra/server/routes/metrics_explorer/lib/find_interval_for_metrics.ts @@ -34,7 +34,8 @@ export const findIntervalForMetrics = async ( const modules = await Promise.all( fields.map( - async (field) => await getDatasetForField(client, field as string, options.indexPattern) + async (field) => + await getDatasetForField(client, field as string, options.indexPattern, options.timerange) ) ); diff --git a/x-pack/plugins/infra/server/routes/metrics_explorer/lib/get_dataset_for_field.ts b/x-pack/plugins/infra/server/routes/metrics_explorer/lib/get_dataset_for_field.ts index 85bb5b106c87..15e6f7ba86d0 100644 --- a/x-pack/plugins/infra/server/routes/metrics_explorer/lib/get_dataset_for_field.ts +++ b/x-pack/plugins/infra/server/routes/metrics_explorer/lib/get_dataset_for_field.ts @@ -17,7 +17,8 @@ interface EventDatasetHit { export const getDatasetForField = async ( client: ESSearchClient, field: string, - indexPattern: string + indexPattern: string, + timerange: { field: string; to: number; from: number } ) => { const params = { allowNoIndices: true, @@ -25,9 +26,25 @@ export const getDatasetForField = async ( terminateAfter: 1, index: indexPattern, body: { - query: { exists: { field } }, + query: { + bool: { + filter: [ + { exists: { field } }, + { + range: { + [timerange.field]: { + gte: timerange.from, + lte: timerange.to, + format: 'epoch_millis', + }, + }, + }, + ], + }, + }, size: 1, _source: ['event.dataset'], + sort: [{ [timerange.field]: { order: 'desc' } }], }, }; diff --git a/x-pack/plugins/infra/server/routes/snapshot/lib/create_timerange_with_interval.ts b/x-pack/plugins/infra/server/routes/snapshot/lib/create_timerange_with_interval.ts index 827e0901c1c0..833b5349f4b5 100644 --- a/x-pack/plugins/infra/server/routes/snapshot/lib/create_timerange_with_interval.ts +++ b/x-pack/plugins/infra/server/routes/snapshot/lib/create_timerange_with_interval.ts @@ -75,7 +75,10 @@ const aggregationsToModules = async ( const fields = await Promise.all( uniqueFields.map( async (field) => - await getDatasetForField(client, field as string, options.sourceConfiguration.metricAlias) + await getDatasetForField(client, field as string, options.sourceConfiguration.metricAlias, { + ...options.timerange, + field: options.sourceConfiguration.fields.timestamp, + }) ) ); return fields.filter((f) => f) as string[]; diff --git a/x-pack/plugins/infra/server/routes/snapshot/lib/get_nodes.ts b/x-pack/plugins/infra/server/routes/snapshot/lib/get_nodes.ts index 9332d5aee1f5..7a2985188dcc 100644 --- a/x-pack/plugins/infra/server/routes/snapshot/lib/get_nodes.ts +++ b/x-pack/plugins/infra/server/routes/snapshot/lib/get_nodes.ts @@ -23,12 +23,11 @@ export const getNodes = async ( snapshotRequest ); const metricsApiResponse = await queryAllData(client, metricsApiRequest); - return copyMissingMetrics( - transformMetricsApiResponseToSnapshotResponse( - metricsApiRequest, - snapshotRequest, - source, - metricsApiResponse - ) + const snapshotResponse = transformMetricsApiResponseToSnapshotResponse( + metricsApiRequest, + snapshotRequest, + source, + metricsApiResponse ); + return copyMissingMetrics(snapshotResponse); }; From 5375ea41356a1e47f7f8267b6b78b48af908b67f Mon Sep 17 00:00:00 2001 From: Bill McConaghy Date: Wed, 18 Nov 2020 20:19:13 -0500 Subject: [PATCH 29/49] Adding documentation for global action configuration options (#83557) * Adding documentation for global action configuration options * Update docs/user/alerting/defining-alerts.asciidoc Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * incorporating PR feedback Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> --- docs/user/alerting/defining-alerts.asciidoc | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/user/alerting/defining-alerts.asciidoc b/docs/user/alerting/defining-alerts.asciidoc index 89a487ca8fb3..05d022d039b2 100644 --- a/docs/user/alerting/defining-alerts.asciidoc +++ b/docs/user/alerting/defining-alerts.asciidoc @@ -74,6 +74,21 @@ image::images/alert-flyout-add-action.png[You can add multiple actions on an ale Actions are not required on alerts. In some cases you may want to run an alert without actions first to understand its behavior, and configure actions later. ============================================== +[float] +=== Global actions configuration +Some actions configuration options apply to all actions. +If you are using an *on-prem* Elastic Stack deployment, you can set these in the kibana.yml file. +If you are using a cloud deployment, you can set these via the console. + +Here's a list of the available global configuration options and an explanation of what each one does: + +* `xpack.actions.allowedHosts`: Specifies an array of host names which actions such as email, Slack, PagerDuty, and webhook can connect to. An element of * indicates any host can be connected to. An empty array indicates no hosts can be connected to. Default: [ {asterisk} ] +* `xpack.actions.enabledActionTypes`: Specifies to an array of action types that are enabled. An {asterisk} indicates all action types registered are enabled. The action types that {kib} provides are: .server-log, .slack, .email, .index, .pagerduty, .webhook. Default: [ {asterisk} ] +* `xpack.actions.proxyUrl`: Specifies the proxy URL to use, if using a proxy for actions. +* `xpack.actions.proxyHeader`: Specifies HTTP headers for proxy, if using a proxy for actions. +* `xpack.actions.proxyRejectUnauthorizedCertificates`: Set to `false` to bypass certificate validation for proxy, if using a proxy for actions. +* `xpack.actions.rejectUnauthorized`: Set to `false` to bypass certificate validation for actions. + [float] === Managing alerts From 92acf4586ee2a0ccffab4f6c8b9fa8a5e8f693b4 Mon Sep 17 00:00:00 2001 From: spalger Date: Wed, 18 Nov 2020 18:35:36 -0700 Subject: [PATCH 30/49] Revert "[App Search] Engine overview layout stub (#83504)" This reverts commit 71f972dc837d6186ad2b8157af8fb178d86e6a96. --- .../components/analytics/constants.ts | 27 ----- .../components/api_logs/constants.ts | 12 --- .../document_creation/constants.tsx | 54 ---------- .../app_search/components/engine/constants.ts | 4 + .../components/engine/engine_nav.tsx | 2 +- .../components/engine/engine_router.test.tsx | 3 +- .../components/engine/engine_router.tsx | 5 +- .../engine_overview/components/index.ts | 10 -- .../components/recent_api_logs.test.tsx | 32 ------ .../components/recent_api_logs.tsx | 50 ---------- .../components/total_charts.test.tsx | 46 --------- .../components/total_charts.tsx | 99 ------------------- .../components/total_stats.test.tsx | 51 ---------- .../components/total_stats.tsx | 37 ------- .../components/unavailable_prompt.test.tsx | 18 ---- .../components/unavailable_prompt.tsx | 30 ------ .../components/engine_overview/constants.ts | 27 ----- .../engine_overview/engine_overview.test.tsx | 80 --------------- .../engine_overview/engine_overview.tsx | 44 --------- .../engine_overview_empty.test.tsx | 40 -------- .../engine_overview/engine_overview_empty.tsx | 98 ------------------ .../engine_overview_metrics.test.tsx | 34 ------- .../engine_overview_metrics.tsx | 44 --------- .../components/engine_overview/index.ts | 2 - 24 files changed, 8 insertions(+), 841 deletions(-) delete mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/constants.ts delete mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/constants.ts delete mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/constants.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/index.ts delete mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/recent_api_logs.test.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/recent_api_logs.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_charts.test.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_charts.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_stats.test.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_stats.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/unavailable_prompt.test.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/unavailable_prompt.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/constants.ts delete mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.test.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.test.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_metrics.test.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_metrics.tsx diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/constants.ts deleted file mode 100644 index 51ae11ad2ab8..000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/constants.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { i18n } from '@kbn/i18n'; - -export const TOTAL_DOCUMENTS = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.analytics.totalDocuments', - { defaultMessage: 'Total documents' } -); - -export const TOTAL_API_OPERATIONS = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.analytics.totalApiOperations', - { defaultMessage: 'Total API operations' } -); - -export const TOTAL_QUERIES = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.analytics.totalQueries', - { defaultMessage: 'Total queries' } -); - -export const TOTAL_CLICKS = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.analytics.totalClicks', - { defaultMessage: 'Total clicks' } -); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/constants.ts deleted file mode 100644 index 6fd60b7a34eb..000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/constants.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { i18n } from '@kbn/i18n'; - -export const RECENT_API_EVENTS = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.apiLogs.recent', - { defaultMessage: 'Recent API events' } -); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/constants.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/constants.tsx deleted file mode 100644 index 736ef09fa6cf..000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/constants.tsx +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; - -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiCode, EuiLink } from '@elastic/eui'; - -import { DOCS_PREFIX } from '../../routes'; - -export const DOCUMENT_CREATION_DESCRIPTION = ( - .json, - postCode: POST, - documentsApiLink: ( - - documents API - - ), - apiStrong: Indexing by API, - }} - /> -); - -export const DOCUMENT_API_INDEXING_TITLE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.documentCreation.api.title', - { defaultMessage: 'Indexing by API' } -); - -export const DOCUMENT_API_INDEXING_DESCRIPTION = ( - - documents API - - ), - clientLibrariesLink: ( - - client libraries - - ), - }} - /> -); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/constants.ts index 9ce524038075..3c963e415f33 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/constants.ts @@ -9,6 +9,10 @@ import { i18n } from '@kbn/i18n'; // TODO: It's very likely that we'll move these i18n constants to their respective component // folders once those are migrated over. This is a temporary way of DRYing them out for now. +export const OVERVIEW_TITLE = i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.overview.title', + { defaultMessage: 'Overview' } +); export const ANALYTICS_TITLE = i18n.translate( 'xpack.enterpriseSearch.appSearch.engine.analytics.title', { defaultMessage: 'Analytics' } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx index a7ac6f203b1f..77aca8a71994 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx @@ -28,8 +28,8 @@ import { } from '../../routes'; import { getAppSearchUrl } from '../../../shared/enterprise_search_url'; import { ENGINES_TITLE } from '../engines'; -import { OVERVIEW_TITLE } from '../engine_overview'; import { + OVERVIEW_TITLE, ANALYTICS_TITLE, DOCUMENTS_TITLE, SCHEMA_TITLE, diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.test.tsx index e8609c169855..8f067754c48a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.test.tsx @@ -18,7 +18,6 @@ jest.mock('../../../shared/flash_messages', () => ({ import { setQueuedErrorMessage } from '../../../shared/flash_messages'; import { Loading } from '../../../shared/loading'; -import { EngineOverview } from '../engine_overview'; import { EngineRouter } from './'; @@ -72,7 +71,7 @@ describe('EngineRouter', () => { const wrapper = shallow(); expect(wrapper.find(Switch)).toHaveLength(1); - expect(wrapper.find(EngineOverview)).toHaveLength(1); + expect(wrapper.find('[data-test-subj="EngineOverviewTODO"]')).toHaveLength(1); }); it('renders an analytics view', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx index f586106924f2..9833305c438c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx @@ -31,8 +31,8 @@ import { // ENGINE_API_LOGS_PATH, } from '../../routes'; import { ENGINES_TITLE } from '../engines'; -import { OVERVIEW_TITLE } from '../engine_overview'; import { + OVERVIEW_TITLE, ANALYTICS_TITLE, // DOCUMENTS_TITLE, // SCHEMA_TITLE, @@ -46,7 +46,6 @@ import { } from './constants'; import { Loading } from '../../../shared/loading'; -import { EngineOverview } from '../engine_overview'; import { EngineLogic } from './'; @@ -101,7 +100,7 @@ export const EngineRouter: React.FC = () => { )} - +
    Overview
    ); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/index.ts deleted file mode 100644 index 11e7b2a5fba9..000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { UnavailablePrompt } from './unavailable_prompt'; -export { TotalStats } from './total_stats'; -export { TotalCharts } from './total_charts'; -export { RecentApiLogs } from './recent_api_logs'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/recent_api_logs.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/recent_api_logs.test.tsx deleted file mode 100644 index 725c89894b80..000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/recent_api_logs.test.tsx +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { setMockValues } from '../../../../__mocks__/kea.mock'; - -import React from 'react'; -import { shallow, ShallowWrapper } from 'enzyme'; - -import { EuiButton } from '../../../../shared/react_router_helpers'; - -import { RecentApiLogs } from './recent_api_logs'; - -describe('RecentApiLogs', () => { - let wrapper: ShallowWrapper; - - beforeAll(() => { - jest.clearAllMocks(); - setMockValues({ - engineName: 'some-engine', - }); - wrapper = shallow(); - }); - - it('renders the recent API logs table', () => { - expect(wrapper.find('h2').text()).toEqual('Recent API events'); - expect(wrapper.find(EuiButton).prop('to')).toEqual('/engines/some-engine/api-logs'); - // TODO: expect(wrapper.find(ApiLogsTable)).toHaveLength(1) - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/recent_api_logs.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/recent_api_logs.tsx deleted file mode 100644 index 05eb73111688..000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/recent_api_logs.tsx +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { useValues } from 'kea'; - -import { - EuiPageContent, - EuiPageContentHeader, - EuiPageContentHeaderSection, - EuiPageContentBody, - EuiTitle, -} from '@elastic/eui'; - -import { EuiButton } from '../../../../shared/react_router_helpers'; - -import { ENGINE_API_LOGS_PATH, getEngineRoute } from '../../../routes'; -import { RECENT_API_EVENTS } from '../../api_logs/constants'; -import { VIEW_API_LOGS } from '../constants'; - -import { EngineLogic } from '../../engine'; - -export const RecentApiLogs: React.FC = () => { - const { engineName } = useValues(EngineLogic); - const engineRoute = getEngineRoute(engineName); - - return ( - - - - -

    {RECENT_API_EVENTS}

    -
    -
    - - - {VIEW_API_LOGS} - - -
    - - TODO: API Logs Table - {/* */} - -
    - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_charts.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_charts.test.tsx deleted file mode 100644 index a56b41dfb050..000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_charts.test.tsx +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { setMockValues } from '../../../../__mocks__/kea.mock'; - -import React from 'react'; -import { shallow, ShallowWrapper } from 'enzyme'; - -import { EuiButton } from '../../../../shared/react_router_helpers'; - -import { TotalCharts } from './total_charts'; - -describe('TotalCharts', () => { - let wrapper: ShallowWrapper; - - beforeAll(() => { - jest.clearAllMocks(); - setMockValues({ - engineName: 'some-engine', - startDate: '1970-01-01', - endDate: '1970-01-08', - queriesPerDay: [0, 1, 2, 3, 5, 10, 50], - operationsPerDay: [0, 0, 0, 0, 0, 0, 0], - }); - wrapper = shallow(); - }); - - it('renders the total queries chart', () => { - const chart = wrapper.find('[data-test-subj="TotalQueriesChart"]'); - - expect(chart.find('h2').text()).toEqual('Total queries'); - expect(chart.find(EuiButton).prop('to')).toEqual('/engines/some-engine/analytics'); - // TODO: find chart component - }); - - it('renders the total API operations chart', () => { - const chart = wrapper.find('[data-test-subj="TotalApiOperationsChart"]'); - - expect(chart.find('h2').text()).toEqual('Total API operations'); - expect(chart.find(EuiButton).prop('to')).toEqual('/engines/some-engine/api-logs'); - // TODO: find chart component - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_charts.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_charts.tsx deleted file mode 100644 index e27fe3cdfc79..000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_charts.tsx +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { useValues } from 'kea'; - -import { - EuiFlexGroup, - EuiFlexItem, - EuiPageContent, - EuiPageContentHeader, - EuiPageContentHeaderSection, - EuiPageContentBody, - EuiTitle, - EuiText, -} from '@elastic/eui'; - -import { EuiButton } from '../../../../shared/react_router_helpers'; - -import { ENGINE_ANALYTICS_PATH, ENGINE_API_LOGS_PATH, getEngineRoute } from '../../../routes'; -import { TOTAL_QUERIES, TOTAL_API_OPERATIONS } from '../../analytics/constants'; -import { VIEW_ANALYTICS, VIEW_API_LOGS, LAST_7_DAYS } from '../constants'; - -import { EngineLogic } from '../../engine'; -import { EngineOverviewLogic } from '../'; - -export const TotalCharts: React.FC = () => { - const { engineName } = useValues(EngineLogic); - const engineRoute = getEngineRoute(engineName); - - const { - // startDate, - // endDate, - // queriesPerDay, - // operationsPerDay, - } = useValues(EngineOverviewLogic); - - return ( - - - - - - -

    {TOTAL_QUERIES}

    -
    - - {LAST_7_DAYS} - -
    - - - {VIEW_ANALYTICS} - - -
    - - TODO: Analytics chart - {/* */} - -
    -
    - - - - - -

    {TOTAL_API_OPERATIONS}

    -
    - - {LAST_7_DAYS} - -
    - - - {VIEW_API_LOGS} - - -
    - - TODO: API Logs chart - {/* */} - -
    -
    -
    - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_stats.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_stats.test.tsx deleted file mode 100644 index 6cb47e8b419f..000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_stats.test.tsx +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { setMockValues } from '../../../../__mocks__/kea.mock'; - -import React from 'react'; -import { shallow, ShallowWrapper } from 'enzyme'; -import { EuiStat } from '@elastic/eui'; - -import { TotalStats } from './total_stats'; - -describe('TotalStats', () => { - let wrapper: ShallowWrapper; - - beforeAll(() => { - jest.clearAllMocks(); - setMockValues({ - totalQueries: 11, - documentCount: 22, - totalClicks: 33, - }); - wrapper = shallow(); - }); - - it('renders the total queries stat', () => { - expect(wrapper.find('[data-test-subj="TotalQueriesCard"]')).toHaveLength(1); - - const card = wrapper.find(EuiStat).at(0); - expect(card.prop('title')).toEqual(11); - expect(card.prop('description')).toEqual('Total queries'); - }); - - it('renders the total documents stat', () => { - expect(wrapper.find('[data-test-subj="TotalDocumentsCard"]')).toHaveLength(1); - - const card = wrapper.find(EuiStat).at(1); - expect(card.prop('title')).toEqual(22); - expect(card.prop('description')).toEqual('Total documents'); - }); - - it('renders the total clicks stat', () => { - expect(wrapper.find('[data-test-subj="TotalClicksCard"]')).toHaveLength(1); - - const card = wrapper.find(EuiStat).at(2); - expect(card.prop('title')).toEqual(33); - expect(card.prop('description')).toEqual('Total clicks'); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_stats.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_stats.tsx deleted file mode 100644 index a27142938f55..000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/total_stats.tsx +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { useValues } from 'kea'; -import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiStat } from '@elastic/eui'; - -import { TOTAL_QUERIES, TOTAL_DOCUMENTS, TOTAL_CLICKS } from '../../analytics/constants'; - -import { EngineOverviewLogic } from '../'; - -export const TotalStats: React.FC = () => { - const { totalQueries, documentCount, totalClicks } = useValues(EngineOverviewLogic); - - return ( - - - - - - - - - - - - - - - - - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/unavailable_prompt.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/unavailable_prompt.test.tsx deleted file mode 100644 index 3ddfd14b0eb0..000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/unavailable_prompt.test.tsx +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { shallow } from 'enzyme'; -import { EuiEmptyPrompt } from '@elastic/eui'; - -import { UnavailablePrompt } from './unavailable_prompt'; - -describe('UnavailablePrompt', () => { - it('renders', () => { - const wrapper = shallow(); - expect(wrapper.find(EuiEmptyPrompt)).toHaveLength(1); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/unavailable_prompt.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/unavailable_prompt.tsx deleted file mode 100644 index e9cc6e2f05bf..000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/components/unavailable_prompt.tsx +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; - -import { i18n } from '@kbn/i18n'; -import { EuiEmptyPrompt } from '@elastic/eui'; - -export const UnavailablePrompt: React.FC = () => ( - - {i18n.translate('xpack.enterpriseSearch.appSearch.engine.overview.unavailableTitle', { - defaultMessage: 'Dashboard metrics are currently unavailable', - })} - - } - body={ -

    - {i18n.translate('xpack.enterpriseSearch.appSearch.engine.overview.unavailableBody', { - defaultMessage: 'Please try again in a few minutes.', - })} -

    - } - /> -); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/constants.ts deleted file mode 100644 index 797811ec6cde..000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/constants.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { i18n } from '@kbn/i18n'; - -export const OVERVIEW_TITLE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.overview.title', - { defaultMessage: 'Overview' } -); - -export const VIEW_ANALYTICS = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.overview.analyticsLink', - { defaultMessage: 'View analytics' } -); - -export const VIEW_API_LOGS = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.overview.apiLogsLink', - { defaultMessage: 'View API logs' } -); - -export const LAST_7_DAYS = i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.overview.chartDuration', - { defaultMessage: 'Last 7 days' } -); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.test.tsx deleted file mode 100644 index 196fb2ca2bf1..000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.test.tsx +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import '../../../__mocks__/shallow_useeffect.mock'; -import { setMockValues, setMockActions } from '../../../__mocks__/kea.mock'; - -import React from 'react'; -import { shallow } from 'enzyme'; - -import { Loading } from '../../../shared/loading'; -import { EmptyEngineOverview } from './engine_overview_empty'; -import { EngineOverviewMetrics } from './engine_overview_metrics'; -import { EngineOverview } from './'; - -describe('EngineOverview', () => { - const values = { - dataLoading: false, - documentCount: 0, - myRole: {}, - isMetaEngine: false, - }; - const actions = { - pollForOverviewMetrics: jest.fn(), - }; - - beforeEach(() => { - jest.clearAllMocks(); - setMockValues(values); - setMockActions(actions); - }); - - it('renders', () => { - const wrapper = shallow(); - expect(wrapper.find('[data-test-subj="EngineOverview"]')).toHaveLength(1); - }); - - it('initializes data on mount', () => { - shallow(); - expect(actions.pollForOverviewMetrics).toHaveBeenCalledTimes(1); - }); - - it('renders a loading component if async data is still loading', () => { - setMockValues({ ...values, dataLoading: true }); - const wrapper = shallow(); - expect(wrapper.find(Loading)).toHaveLength(1); - }); - - describe('EmptyEngineOverview', () => { - it('renders when the engine has no documents & the user can add documents', () => { - const myRole = { canManageEngineDocuments: true, canViewEngineCredentials: true }; - setMockValues({ ...values, myRole, documentCount: 0 }); - const wrapper = shallow(); - expect(wrapper.find(EmptyEngineOverview)).toHaveLength(1); - }); - }); - - describe('EngineOverviewMetrics', () => { - it('renders when the engine has documents', () => { - setMockValues({ ...values, documentCount: 1 }); - const wrapper = shallow(); - expect(wrapper.find(EngineOverviewMetrics)).toHaveLength(1); - }); - - it('renders when the user does not have the ability to add documents', () => { - const myRole = { canManageEngineDocuments: false, canViewEngineCredentials: false }; - setMockValues({ ...values, myRole }); - const wrapper = shallow(); - expect(wrapper.find(EngineOverviewMetrics)).toHaveLength(1); - }); - - it('always renders for meta engines', () => { - setMockValues({ ...values, isMetaEngine: true }); - const wrapper = shallow(); - expect(wrapper.find(EngineOverviewMetrics)).toHaveLength(1); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.tsx deleted file mode 100644 index dd43bc67b3e8..000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview.tsx +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { useEffect } from 'react'; -import { useActions, useValues } from 'kea'; - -import { AppLogic } from '../../app_logic'; -import { EngineLogic } from '../engine'; -import { Loading } from '../../../shared/loading'; - -import { EngineOverviewLogic } from './'; -import { EmptyEngineOverview } from './engine_overview_empty'; -import { EngineOverviewMetrics } from './engine_overview_metrics'; - -export const EngineOverview: React.FC = () => { - const { - myRole: { canManageEngineDocuments, canViewEngineCredentials }, - } = useValues(AppLogic); - const { isMetaEngine } = useValues(EngineLogic); - - const { pollForOverviewMetrics } = useActions(EngineOverviewLogic); - const { dataLoading, documentCount } = useValues(EngineOverviewLogic); - - useEffect(() => { - pollForOverviewMetrics(); - }, []); - - if (dataLoading) { - return ; - } - - const engineHasDocuments = documentCount > 0; - const canAddDocuments = canManageEngineDocuments && canViewEngineCredentials; - const showEngineOverview = engineHasDocuments || !canAddDocuments || isMetaEngine; - - return ( -
    - {showEngineOverview ? : } -
    - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.test.tsx deleted file mode 100644 index 8ebe09820a67..000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.test.tsx +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import '../../../__mocks__/enterprise_search_url.mock'; -import { setMockValues } from '../../../__mocks__/kea.mock'; - -import React from 'react'; -import { shallow, ShallowWrapper } from 'enzyme'; -import { EuiButton } from '@elastic/eui'; - -import { CURRENT_MAJOR_VERSION } from '../../../../../common/version'; - -import { EmptyEngineOverview } from './engine_overview_empty'; - -describe('EmptyEngineOverview', () => { - let wrapper: ShallowWrapper; - - beforeAll(() => { - jest.clearAllMocks(); - setMockValues({ - engineName: 'empty-engine', - }); - wrapper = shallow(); - }); - - it('renders', () => { - expect(wrapper.find('h1').text()).toEqual('Engine setup'); - expect(wrapper.find('h2').text()).toEqual('Setting up the “empty-engine” engine'); - expect(wrapper.find('h3').text()).toEqual('Indexing by API'); - }); - - it('renders correctly versioned documentation URLs', () => { - expect(wrapper.find(EuiButton).prop('href')).toEqual( - `https://www.elastic.co/guide/en/app-search/${CURRENT_MAJOR_VERSION}/index.html` - ); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.tsx deleted file mode 100644 index f2bf5a54f810..000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.tsx +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { useValues } from 'kea'; - -import { i18n } from '@kbn/i18n'; -import { - EuiPageHeader, - EuiPageHeaderSection, - EuiPageContent, - EuiPageContentHeader, - EuiPageContentBody, - EuiTitle, - EuiText, - EuiButton, - EuiSpacer, -} from '@elastic/eui'; - -import { EngineLogic } from '../engine'; - -import { DOCS_PREFIX } from '../../routes'; -import { - DOCUMENT_CREATION_DESCRIPTION, - DOCUMENT_API_INDEXING_TITLE, - DOCUMENT_API_INDEXING_DESCRIPTION, -} from '../document_creation/constants'; -// TODO -// import { DocumentCreationButtons, CodeExample } from '../document_creation' - -export const EmptyEngineOverview: React.FC = () => { - const { engineName } = useValues(EngineLogic); - - return ( - <> - - - -

    - {i18n.translate('xpack.enterpriseSearch.appSearch.engine.overview.empty.heading', { - defaultMessage: 'Engine setup', - })} -

    -
    -
    - - - {i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.overview.empty.headingAction', - { defaultMessage: 'View documentation' } - )} - - -
    - - - -

    - {i18n.translate('xpack.enterpriseSearch.appSearch.engine.overview.empty.subheading', { - defaultMessage: 'Setting up the “{engineName}” engine', - values: { engineName }, - })} -

    -
    -
    - - -

    {DOCUMENT_CREATION_DESCRIPTION}

    -
    - - {/* TODO: */} -
    - - - -

    {DOCUMENT_API_INDEXING_TITLE}

    -
    -
    - - -

    {DOCUMENT_API_INDEXING_DESCRIPTION}

    -

    - {i18n.translate('xpack.enterpriseSearch.appSearch.engine.overview.empty.apiExample', { - defaultMessage: - 'To see the API in action, you can experiment with the example request below using a command line or a client library.', - })} -

    -
    - - {/* */} -
    -
    - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_metrics.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_metrics.test.tsx deleted file mode 100644 index 8250446e231b..000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_metrics.test.tsx +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { setMockValues } from '../../../__mocks__/kea.mock'; - -import React from 'react'; -import { shallow } from 'enzyme'; - -import { UnavailablePrompt, TotalStats, TotalCharts, RecentApiLogs } from './components'; -import { EngineOverviewMetrics } from './engine_overview_metrics'; - -describe('EngineOverviewMetrics', () => { - it('renders', () => { - const wrapper = shallow(); - expect(wrapper.find('h1').text()).toEqual('Engine overview'); - }); - - it('renders an unavailable prompt if engine data is still indexing', () => { - setMockValues({ apiLogsUnavailable: true }); - const wrapper = shallow(); - expect(wrapper.find(UnavailablePrompt)).toHaveLength(1); - }); - - it('renders total stats, charts, and recent logs when metrics are available', () => { - setMockValues({ apiLogsUnavailable: false }); - const wrapper = shallow(); - expect(wrapper.find(TotalStats)).toHaveLength(1); - expect(wrapper.find(TotalCharts)).toHaveLength(1); - expect(wrapper.find(RecentApiLogs)).toHaveLength(1); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_metrics.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_metrics.tsx deleted file mode 100644 index 9630f6fa2f81..000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_metrics.tsx +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { useValues } from 'kea'; - -import { i18n } from '@kbn/i18n'; -import { EuiPageHeader, EuiTitle, EuiSpacer } from '@elastic/eui'; - -import { EngineOverviewLogic } from './'; - -import { UnavailablePrompt, TotalStats, TotalCharts, RecentApiLogs } from './components'; - -export const EngineOverviewMetrics: React.FC = () => { - const { apiLogsUnavailable } = useValues(EngineOverviewLogic); - - return ( - <> - - -

    - {i18n.translate('xpack.enterpriseSearch.appSearch.engine.overview.heading', { - defaultMessage: 'Engine overview', - })} -

    -
    -
    - {apiLogsUnavailable ? ( - - ) : ( - <> - - - - - - - )} - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/index.ts index 82c5d7dc8e60..fcd92ba6a338 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/index.ts @@ -5,5 +5,3 @@ */ export { EngineOverviewLogic } from './engine_overview_logic'; -export { EngineOverview } from './engine_overview'; -export { OVERVIEW_TITLE } from './constants'; From d97ddcd4dae437bf2b81251926b89a024d69f6d5 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 18 Nov 2020 18:42:37 -0700 Subject: [PATCH 31/49] [maps] convert VectorStyleEditor to TS (#83582) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../style_property_descriptor_types.ts | 24 +- .../blended_vector_layer.ts | 11 +- .../create_choropleth_layer_descriptor.ts | 2 +- .../create_region_map_layer_descriptor.ts | 4 +- .../create_tile_map_layer_descriptor.ts | 10 +- .../maps/public/classes/layers/layer.tsx | 18 +- .../observability/create_layer_descriptor.ts | 6 +- .../security/create_layer_descriptors.ts | 8 +- .../clusters_layer_wizard.tsx | 4 +- .../classes/styles/heatmap/heatmap_style.tsx | 6 +- .../maps/public/classes/styles/style.ts | 11 +- .../public/classes/styles/tile/tile_style.ts | 2 +- .../color/vector_style_color_editor.tsx | 6 +- ...ditor.js => vector_style_label_editor.tsx} | 9 +- ...editor.js => vector_style_size_editor.tsx} | 9 +- .../vector/components/style_prop_editor.tsx | 10 +- ...editor.js => vector_style_icon_editor.tsx} | 9 +- ...tyle_editor.js => vector_style_editor.tsx} | 252 ++++++++++++------ .../styles/vector/style_fields_helper.ts | 2 +- .../classes/styles/vector/vector_style.tsx | 43 ++- .../vector/vector_style_defaults.test.ts | 8 +- .../styles/vector/vector_style_defaults.ts | 36 +-- .../style_settings/style_settings.js | 4 +- 23 files changed, 297 insertions(+), 197 deletions(-) rename x-pack/plugins/maps/public/classes/styles/vector/components/label/{vector_style_label_editor.js => vector_style_label_editor.tsx} (62%) rename x-pack/plugins/maps/public/classes/styles/vector/components/size/{vector_style_size_editor.js => vector_style_size_editor.tsx} (62%) rename x-pack/plugins/maps/public/classes/styles/vector/components/symbol/{vector_style_icon_editor.js => vector_style_icon_editor.tsx} (62%) rename x-pack/plugins/maps/public/classes/styles/vector/components/{vector_style_editor.js => vector_style_editor.tsx} (64%) diff --git a/x-pack/plugins/maps/common/descriptor_types/style_property_descriptor_types.ts b/x-pack/plugins/maps/common/descriptor_types/style_property_descriptor_types.ts index 5aba9b06a6cc..d52afebcaa25 100644 --- a/x-pack/plugins/maps/common/descriptor_types/style_property_descriptor_types.ts +++ b/x-pack/plugins/maps/common/descriptor_types/style_property_descriptor_types.ts @@ -174,18 +174,18 @@ export type SizeStylePropertyDescriptor = }; export type VectorStylePropertiesDescriptor = { - [VECTOR_STYLES.SYMBOLIZE_AS]?: SymbolizeAsStylePropertyDescriptor; - [VECTOR_STYLES.FILL_COLOR]?: ColorStylePropertyDescriptor; - [VECTOR_STYLES.LINE_COLOR]?: ColorStylePropertyDescriptor; - [VECTOR_STYLES.LINE_WIDTH]?: SizeStylePropertyDescriptor; - [VECTOR_STYLES.ICON]?: IconStylePropertyDescriptor; - [VECTOR_STYLES.ICON_SIZE]?: SizeStylePropertyDescriptor; - [VECTOR_STYLES.ICON_ORIENTATION]?: OrientationStylePropertyDescriptor; - [VECTOR_STYLES.LABEL_TEXT]?: LabelStylePropertyDescriptor; - [VECTOR_STYLES.LABEL_COLOR]?: ColorStylePropertyDescriptor; - [VECTOR_STYLES.LABEL_SIZE]?: SizeStylePropertyDescriptor; - [VECTOR_STYLES.LABEL_BORDER_COLOR]?: ColorStylePropertyDescriptor; - [VECTOR_STYLES.LABEL_BORDER_SIZE]?: LabelBorderSizeStylePropertyDescriptor; + [VECTOR_STYLES.SYMBOLIZE_AS]: SymbolizeAsStylePropertyDescriptor; + [VECTOR_STYLES.FILL_COLOR]: ColorStylePropertyDescriptor; + [VECTOR_STYLES.LINE_COLOR]: ColorStylePropertyDescriptor; + [VECTOR_STYLES.LINE_WIDTH]: SizeStylePropertyDescriptor; + [VECTOR_STYLES.ICON]: IconStylePropertyDescriptor; + [VECTOR_STYLES.ICON_SIZE]: SizeStylePropertyDescriptor; + [VECTOR_STYLES.ICON_ORIENTATION]: OrientationStylePropertyDescriptor; + [VECTOR_STYLES.LABEL_TEXT]: LabelStylePropertyDescriptor; + [VECTOR_STYLES.LABEL_COLOR]: ColorStylePropertyDescriptor; + [VECTOR_STYLES.LABEL_SIZE]: SizeStylePropertyDescriptor; + [VECTOR_STYLES.LABEL_BORDER_COLOR]: ColorStylePropertyDescriptor; + [VECTOR_STYLES.LABEL_BORDER_SIZE]: LabelBorderSizeStylePropertyDescriptor; }; export type StyleDescriptor = { diff --git a/x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.ts b/x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.ts index 2ab8a70f2e4d..85391ea82cbf 100644 --- a/x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.ts +++ b/x-pack/plugins/maps/public/classes/layers/blended_vector_layer/blended_vector_layer.ts @@ -36,6 +36,7 @@ import { LayerDescriptor, VectorLayerDescriptor, VectorSourceRequestMeta, + VectorStylePropertiesDescriptor, } from '../../../../common/descriptor_types'; import { IVectorSource } from '../../sources/vector_source'; import { LICENSED_FEATURES } from '../../../licensed_features'; @@ -79,13 +80,15 @@ function getClusterStyleDescriptor( clusterSource: ESGeoGridSource ): VectorStyleDescriptor { const defaultDynamicProperties = getDefaultDynamicProperties(); - const clusterStyleDescriptor: VectorStyleDescriptor = { + const clusterStyleDescriptor: Omit & { + properties: Partial; + } = { type: LAYER_STYLE_TYPE.VECTOR, properties: { [VECTOR_STYLES.LABEL_TEXT]: { type: STYLE_TYPE.DYNAMIC, options: { - ...defaultDynamicProperties[VECTOR_STYLES.LABEL_TEXT]!.options, + ...defaultDynamicProperties[VECTOR_STYLES.LABEL_TEXT].options, field: { name: COUNT_PROP_NAME, origin: FIELD_ORIGIN.SOURCE, @@ -95,7 +98,7 @@ function getClusterStyleDescriptor( [VECTOR_STYLES.ICON_SIZE]: { type: STYLE_TYPE.DYNAMIC, options: { - ...(defaultDynamicProperties[VECTOR_STYLES.ICON_SIZE]!.options as SizeDynamicOptions), + ...(defaultDynamicProperties[VECTOR_STYLES.ICON_SIZE].options as SizeDynamicOptions), field: { name: COUNT_PROP_NAME, origin: FIELD_ORIGIN.SOURCE, @@ -157,7 +160,7 @@ function getClusterStyleDescriptor( } }); - return clusterStyleDescriptor; + return clusterStyleDescriptor as VectorStyleDescriptor; } export interface BlendedVectorLayerArguments { diff --git a/x-pack/plugins/maps/public/classes/layers/choropleth_layer_wizard/create_choropleth_layer_descriptor.ts b/x-pack/plugins/maps/public/classes/layers/choropleth_layer_wizard/create_choropleth_layer_descriptor.ts index cdfe60946f5f..fa82b9dc3b54 100644 --- a/x-pack/plugins/maps/public/classes/layers/choropleth_layer_wizard/create_choropleth_layer_descriptor.ts +++ b/x-pack/plugins/maps/public/classes/layers/choropleth_layer_wizard/create_choropleth_layer_descriptor.ts @@ -71,7 +71,7 @@ function createChoroplethLayerDescriptor({ [VECTOR_STYLES.FILL_COLOR]: { type: STYLE_TYPE.DYNAMIC, options: { - ...(defaultDynamicProperties[VECTOR_STYLES.FILL_COLOR]!.options as ColorDynamicOptions), + ...(defaultDynamicProperties[VECTOR_STYLES.FILL_COLOR].options as ColorDynamicOptions), field: { name: joinKey, origin: FIELD_ORIGIN.JOIN, diff --git a/x-pack/plugins/maps/public/classes/layers/create_region_map_layer_descriptor.ts b/x-pack/plugins/maps/public/classes/layers/create_region_map_layer_descriptor.ts index 6f9bb686459b..5fa2524b1b79 100644 --- a/x-pack/plugins/maps/public/classes/layers/create_region_map_layer_descriptor.ts +++ b/x-pack/plugins/maps/public/classes/layers/create_region_map_layer_descriptor.ts @@ -100,7 +100,7 @@ export function createRegionMapLayerDescriptor({ [VECTOR_STYLES.FILL_COLOR]: { type: STYLE_TYPE.DYNAMIC, options: { - ...(defaultDynamicProperties[VECTOR_STYLES.FILL_COLOR]!.options as ColorDynamicOptions), + ...(defaultDynamicProperties[VECTOR_STYLES.FILL_COLOR].options as ColorDynamicOptions), field: { name: joinKey, origin: FIELD_ORIGIN.JOIN, @@ -108,7 +108,7 @@ export function createRegionMapLayerDescriptor({ color: colorPallette ? colorPallette.value : 'Yellow to Red', type: COLOR_MAP_TYPE.ORDINAL, fieldMetaOptions: { - ...(defaultDynamicProperties[VECTOR_STYLES.FILL_COLOR]!.options as ColorDynamicOptions) + ...(defaultDynamicProperties[VECTOR_STYLES.FILL_COLOR].options as ColorDynamicOptions) .fieldMetaOptions, isEnabled: false, }, diff --git a/x-pack/plugins/maps/public/classes/layers/create_tile_map_layer_descriptor.ts b/x-pack/plugins/maps/public/classes/layers/create_tile_map_layer_descriptor.ts index 5b89373f2db4..05616f6916f6 100644 --- a/x-pack/plugins/maps/public/classes/layers/create_tile_map_layer_descriptor.ts +++ b/x-pack/plugins/maps/public/classes/layers/create_tile_map_layer_descriptor.ts @@ -113,16 +113,16 @@ export function createTileMapLayerDescriptor({ const colorPallette = NUMERICAL_COLOR_PALETTES.find((pallette) => { return pallette.value.toLowerCase() === colorSchema.toLowerCase(); }); - const styleProperties: VectorStylePropertiesDescriptor = { + const styleProperties: Partial = { [VECTOR_STYLES.FILL_COLOR]: { type: STYLE_TYPE.DYNAMIC, options: { - ...(defaultDynamicProperties[VECTOR_STYLES.FILL_COLOR]!.options as ColorDynamicOptions), + ...(defaultDynamicProperties[VECTOR_STYLES.FILL_COLOR].options as ColorDynamicOptions), field: metricStyleField, color: colorPallette ? colorPallette.value : 'Yellow to Red', type: COLOR_MAP_TYPE.ORDINAL, fieldMetaOptions: { - ...(defaultDynamicProperties[VECTOR_STYLES.FILL_COLOR]!.options as ColorDynamicOptions) + ...(defaultDynamicProperties[VECTOR_STYLES.FILL_COLOR].options as ColorDynamicOptions) .fieldMetaOptions, isEnabled: false, }, @@ -139,11 +139,11 @@ export function createTileMapLayerDescriptor({ styleProperties[VECTOR_STYLES.ICON_SIZE] = { type: STYLE_TYPE.DYNAMIC, options: { - ...(defaultDynamicProperties[VECTOR_STYLES.ICON_SIZE]!.options as SizeDynamicOptions), + ...(defaultDynamicProperties[VECTOR_STYLES.ICON_SIZE].options as SizeDynamicOptions), maxSize: 18, field: metricStyleField, fieldMetaOptions: { - ...(defaultDynamicProperties[VECTOR_STYLES.ICON_SIZE]!.options as SizeDynamicOptions) + ...(defaultDynamicProperties[VECTOR_STYLES.ICON_SIZE].options as SizeDynamicOptions) .fieldMetaOptions, isEnabled: false, }, diff --git a/x-pack/plugins/maps/public/classes/layers/layer.tsx b/x-pack/plugins/maps/public/classes/layers/layer.tsx index b982e6452e8c..060ff4d46fa2 100644 --- a/x-pack/plugins/maps/public/classes/layers/layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/layer.tsx @@ -76,11 +76,9 @@ export interface ILayer { getType(): string | undefined; isVisible(): boolean; cloneDescriptor(): Promise; - renderStyleEditor({ - onStyleDescriptorChange, - }: { - onStyleDescriptorChange: (styleDescriptor: StyleDescriptor) => void; - }): ReactElement | null; + renderStyleEditor( + onStyleDescriptorChange: (styleDescriptor: StyleDescriptor) => void + ): ReactElement | null; getInFlightRequestTokens(): symbol[]; getPrevRequestToken(dataId: string): symbol | undefined; destroy: () => void; @@ -437,16 +435,14 @@ export class AbstractLayer implements ILayer { return null; } - renderStyleEditor({ - onStyleDescriptorChange, - }: { - onStyleDescriptorChange: (styleDescriptor: StyleDescriptor) => void; - }): ReactElement | null { + renderStyleEditor( + onStyleDescriptorChange: (styleDescriptor: StyleDescriptor) => void + ): ReactElement | null { const style = this.getStyleForEditing(); if (!style) { return null; } - return style.renderEditor({ layer: this, onStyleDescriptorChange }); + return style.renderEditor(onStyleDescriptorChange); } getIndexPatternIds(): string[] { diff --git a/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/create_layer_descriptor.ts b/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/create_layer_descriptor.ts index dea551866f4a..7e8a216685bb 100644 --- a/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/create_layer_descriptor.ts +++ b/x-pack/plugins/maps/public/classes/layers/solution_layers/observability/create_layer_descriptor.ts @@ -50,7 +50,7 @@ function createDynamicFillColorDescriptor( return { type: STYLE_TYPE.DYNAMIC, options: { - ...(defaultDynamicProperties[VECTOR_STYLES.FILL_COLOR]!.options as ColorDynamicOptions), + ...(defaultDynamicProperties[VECTOR_STYLES.FILL_COLOR].options as ColorDynamicOptions), field, color: layer === OBSERVABILITY_LAYER_TYPE.APM_RUM_PERFORMANCE ? 'Green to Red' : 'Yellow to Red', @@ -226,12 +226,12 @@ export function createLayerDescriptor({ origin: FIELD_ORIGIN.SOURCE, }; - const styleProperties: VectorStylePropertiesDescriptor = { + const styleProperties: Partial = { [VECTOR_STYLES.FILL_COLOR]: createDynamicFillColorDescriptor(layer, metricStyleField), [VECTOR_STYLES.ICON_SIZE]: { type: STYLE_TYPE.DYNAMIC, options: { - ...(defaultDynamicProperties[VECTOR_STYLES.ICON_SIZE]!.options as SizeDynamicOptions), + ...(defaultDynamicProperties[VECTOR_STYLES.ICON_SIZE].options as SizeDynamicOptions), field: metricStyleField, }, }, diff --git a/x-pack/plugins/maps/public/classes/layers/solution_layers/security/create_layer_descriptors.ts b/x-pack/plugins/maps/public/classes/layers/solution_layers/security/create_layer_descriptors.ts index 909cd93b3df7..b52ce02acb5f 100644 --- a/x-pack/plugins/maps/public/classes/layers/solution_layers/security/create_layer_descriptors.ts +++ b/x-pack/plugins/maps/public/classes/layers/solution_layers/security/create_layer_descriptors.ts @@ -68,7 +68,7 @@ function createSourceLayerDescriptor(indexPatternId: string, indexPatternTitle: ], }); - const styleProperties: VectorStylePropertiesDescriptor = { + const styleProperties: Partial = { [VECTOR_STYLES.FILL_COLOR]: { type: STYLE_TYPE.STATIC, options: { color: euiVisColorPalette[1] }, @@ -121,7 +121,7 @@ function createDestinationLayerDescriptor(indexPatternId: string, indexPatternTi ], }); - const styleProperties: VectorStylePropertiesDescriptor = { + const styleProperties: Partial = { [VECTOR_STYLES.FILL_COLOR]: { type: STYLE_TYPE.STATIC, options: { color: euiVisColorPalette[2] }, @@ -168,7 +168,7 @@ function createLineLayerDescriptor(indexPatternId: string, indexPatternTitle: st ], }); - const styleProperties: VectorStylePropertiesDescriptor = { + const styleProperties: Partial = { [VECTOR_STYLES.LINE_COLOR]: { type: STYLE_TYPE.STATIC, options: { color: euiVisColorPalette[1] }, @@ -176,7 +176,7 @@ function createLineLayerDescriptor(indexPatternId: string, indexPatternTitle: st [VECTOR_STYLES.LINE_WIDTH]: { type: STYLE_TYPE.DYNAMIC, options: { - ...(defaultDynamicProperties[VECTOR_STYLES.LINE_WIDTH]!.options as SizeDynamicOptions), + ...(defaultDynamicProperties[VECTOR_STYLES.LINE_WIDTH].options as SizeDynamicOptions), field: { name: COUNT_PROP_NAME, origin: FIELD_ORIGIN.SOURCE, diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/clusters_layer_wizard.tsx b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/clusters_layer_wizard.tsx index 0f596c47fc9b..1fd6a9c9ecc8 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/clusters_layer_wizard.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/clusters_layer_wizard.tsx @@ -77,7 +77,7 @@ export const clustersLayerWizardConfig: LayerWizard = { [VECTOR_STYLES.ICON_SIZE]: { type: STYLE_TYPE.DYNAMIC, options: { - ...(defaultDynamicProperties[VECTOR_STYLES.ICON_SIZE]!.options as SizeDynamicOptions), + ...(defaultDynamicProperties[VECTOR_STYLES.ICON_SIZE].options as SizeDynamicOptions), field: { name: COUNT_PROP_NAME, origin: FIELD_ORIGIN.SOURCE, @@ -87,7 +87,7 @@ export const clustersLayerWizardConfig: LayerWizard = { [VECTOR_STYLES.LABEL_TEXT]: { type: STYLE_TYPE.DYNAMIC, options: { - ...defaultDynamicProperties[VECTOR_STYLES.LABEL_TEXT]!.options, + ...defaultDynamicProperties[VECTOR_STYLES.LABEL_TEXT].options, field: { name: COUNT_PROP_NAME, origin: FIELD_ORIGIN.SOURCE, diff --git a/x-pack/plugins/maps/public/classes/styles/heatmap/heatmap_style.tsx b/x-pack/plugins/maps/public/classes/styles/heatmap/heatmap_style.tsx index c75698805225..599f3b2dfbb0 100644 --- a/x-pack/plugins/maps/public/classes/styles/heatmap/heatmap_style.tsx +++ b/x-pack/plugins/maps/public/classes/styles/heatmap/heatmap_style.tsx @@ -41,11 +41,7 @@ export class HeatmapStyle implements IStyle { return LAYER_STYLE_TYPE.HEATMAP; } - renderEditor({ - onStyleDescriptorChange, - }: { - onStyleDescriptorChange: (styleDescriptor: StyleDescriptor) => void; - }) { + renderEditor(onStyleDescriptorChange: (styleDescriptor: StyleDescriptor) => void) { const onHeatmapColorChange = ({ colorRampName }: { colorRampName: string }) => { const styleDescriptor = HeatmapStyle.createDescriptor(colorRampName); onStyleDescriptorChange(styleDescriptor); diff --git a/x-pack/plugins/maps/public/classes/styles/style.ts b/x-pack/plugins/maps/public/classes/styles/style.ts index abaa6184b0ca..de14ab990fa2 100644 --- a/x-pack/plugins/maps/public/classes/styles/style.ts +++ b/x-pack/plugins/maps/public/classes/styles/style.ts @@ -6,15 +6,10 @@ import { ReactElement } from 'react'; import { StyleDescriptor } from '../../../common/descriptor_types'; -import { ILayer } from '../layers/layer'; export interface IStyle { getType(): string; - renderEditor({ - layer, - onStyleDescriptorChange, - }: { - layer: ILayer; - onStyleDescriptorChange: (styleDescriptor: StyleDescriptor) => void; - }): ReactElement | null; + renderEditor( + onStyleDescriptorChange: (styleDescriptor: StyleDescriptor) => void + ): ReactElement | null; } diff --git a/x-pack/plugins/maps/public/classes/styles/tile/tile_style.ts b/x-pack/plugins/maps/public/classes/styles/tile/tile_style.ts index cac3913d3149..dad26d4172e0 100644 --- a/x-pack/plugins/maps/public/classes/styles/tile/tile_style.ts +++ b/x-pack/plugins/maps/public/classes/styles/tile/tile_style.ts @@ -21,7 +21,7 @@ export class TileStyle implements IStyle { return LAYER_STYLE_TYPE.TILE; } - renderEditor(/* { layer, onStyleDescriptorChange } */) { + renderEditor(/* onStyleDescriptorChange */) { return null; } } diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/color/vector_style_color_editor.tsx b/x-pack/plugins/maps/public/classes/styles/vector/components/color/vector_style_color_editor.tsx index 4527f56c04d2..d45c33bbc3f5 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/components/color/vector_style_color_editor.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/color/vector_style_color_editor.tsx @@ -14,7 +14,11 @@ import { DynamicColorForm } from './dynamic_color_form'; import { StaticColorForm } from './static_color_form'; import { ColorDynamicOptions, ColorStaticOptions } from '../../../../../../common/descriptor_types'; -export function VectorStyleColorEditor(props: Props) { +type ColorEditorProps = Omit, 'children'> & { + swatches: string[]; +}; + +export function VectorStyleColorEditor(props: ColorEditorProps) { const colorForm = props.styleProperty.isDynamic() ? ( ) : ( diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/label/vector_style_label_editor.js b/x-pack/plugins/maps/public/classes/styles/vector/components/label/vector_style_label_editor.tsx similarity index 62% rename from x-pack/plugins/maps/public/classes/styles/vector/components/label/vector_style_label_editor.js rename to x-pack/plugins/maps/public/classes/styles/vector/components/label/vector_style_label_editor.tsx index aaa21ea315f3..586d4fc0576a 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/components/label/vector_style_label_editor.js +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/label/vector_style_label_editor.tsx @@ -6,11 +6,16 @@ import React from 'react'; -import { StylePropEditor } from '../style_prop_editor'; +import { Props, StylePropEditor } from '../style_prop_editor'; +// @ts-expect-error import { DynamicLabelForm } from './dynamic_label_form'; +// @ts-expect-error import { StaticLabelForm } from './static_label_form'; +import { LabelDynamicOptions, LabelStaticOptions } from '../../../../../../common/descriptor_types'; -export function VectorStyleLabelEditor(props) { +type LabelEditorProps = Omit, 'children'>; + +export function VectorStyleLabelEditor(props: LabelEditorProps) { const labelForm = props.styleProperty.isDynamic() ? ( ) : ( diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/size/vector_style_size_editor.js b/x-pack/plugins/maps/public/classes/styles/vector/components/size/vector_style_size_editor.tsx similarity index 62% rename from x-pack/plugins/maps/public/classes/styles/vector/components/size/vector_style_size_editor.js rename to x-pack/plugins/maps/public/classes/styles/vector/components/size/vector_style_size_editor.tsx index e344f72bd429..c492f24661e7 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/components/size/vector_style_size_editor.js +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/size/vector_style_size_editor.tsx @@ -6,11 +6,16 @@ import React from 'react'; -import { StylePropEditor } from '../style_prop_editor'; +import { Props, StylePropEditor } from '../style_prop_editor'; +// @ts-expect-error import { DynamicSizeForm } from './dynamic_size_form'; +// @ts-expect-error import { StaticSizeForm } from './static_size_form'; +import { SizeDynamicOptions, SizeStaticOptions } from '../../../../../../common/descriptor_types'; -export function VectorStyleSizeEditor(props) { +type SizeEditorProps = Omit, 'children'>; + +export function VectorStyleSizeEditor(props: SizeEditorProps) { const sizeForm = props.styleProperty.isDynamic() ? ( ) : ( diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/style_prop_editor.tsx b/x-pack/plugins/maps/public/classes/styles/vector/components/style_prop_editor.tsx index 43b088074a30..f3363a9443cf 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/components/style_prop_editor.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/style_prop_editor.tsx @@ -25,12 +25,12 @@ export interface Props { customStaticOptionLabel?: string; defaultStaticStyleOptions: StaticOptions; defaultDynamicStyleOptions: DynamicOptions; - disabled: boolean; + disabled?: boolean; disabledBy?: VECTOR_STYLES; fields: StyleField[]; onDynamicStyleChange: (propertyName: VECTOR_STYLES, options: DynamicOptions) => void; onStaticStyleChange: (propertyName: VECTOR_STYLES, options: StaticOptions) => void; - styleProperty: IStyleProperty; + styleProperty: IStyleProperty; } export class StylePropEditor extends Component< @@ -42,7 +42,7 @@ export class StylePropEditor extends Component< _onTypeToggle = () => { if (this.props.styleProperty.isDynamic()) { // preserve current dynmaic style - this._prevDynamicStyleOptions = this.props.styleProperty.getOptions(); + this._prevDynamicStyleOptions = this.props.styleProperty.getOptions() as DynamicOptions; // toggle to static style this.props.onStaticStyleChange( this.props.styleProperty.getStyleName(), @@ -50,7 +50,7 @@ export class StylePropEditor extends Component< ); } else { // preserve current static style - this._prevStaticStyleOptions = this.props.styleProperty.getOptions(); + this._prevStaticStyleOptions = this.props.styleProperty.getOptions() as StaticOptions; // toggle to dynamic style this.props.onDynamicStyleChange( this.props.styleProperty.getStyleName(), @@ -61,7 +61,7 @@ export class StylePropEditor extends Component< _onFieldMetaOptionsChange = (fieldMetaOptions: FieldMetaOptions) => { const options = { - ...this.props.styleProperty.getOptions(), + ...(this.props.styleProperty.getOptions() as DynamicOptions), fieldMetaOptions, }; this.props.onDynamicStyleChange(this.props.styleProperty.getStyleName(), options); diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/symbol/vector_style_icon_editor.js b/x-pack/plugins/maps/public/classes/styles/vector/components/symbol/vector_style_icon_editor.tsx similarity index 62% rename from x-pack/plugins/maps/public/classes/styles/vector/components/symbol/vector_style_icon_editor.js rename to x-pack/plugins/maps/public/classes/styles/vector/components/symbol/vector_style_icon_editor.tsx index 2a983a32f0d8..bd6cda0b57f8 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/components/symbol/vector_style_icon_editor.js +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/symbol/vector_style_icon_editor.tsx @@ -6,11 +6,16 @@ import React from 'react'; -import { StylePropEditor } from '../style_prop_editor'; +import { Props, StylePropEditor } from '../style_prop_editor'; +// @ts-expect-error import { DynamicIconForm } from './dynamic_icon_form'; +// @ts-expect-error import { StaticIconForm } from './static_icon_form'; +import { IconDynamicOptions, IconStaticOptions } from '../../../../../../common/descriptor_types'; -export function VectorStyleIconEditor(props) { +type IconEditorProps = Omit, 'children'>; + +export function VectorStyleIconEditor(props: IconEditorProps) { const iconForm = props.styleProperty.isDynamic() ? ( ) : ( diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/vector_style_editor.js b/x-pack/plugins/maps/public/classes/styles/vector/components/vector_style_editor.tsx similarity index 64% rename from x-pack/plugins/maps/public/classes/styles/vector/components/vector_style_editor.js rename to x-pack/plugins/maps/public/classes/styles/vector/components/vector_style_editor.tsx index d577912efb83..95e32f0e9969 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/components/vector_style_editor.js +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/vector_style_editor.tsx @@ -7,34 +7,95 @@ import _ from 'lodash'; import React, { Component, Fragment } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiSpacer, EuiButtonGroup, EuiFormRow, EuiSwitch, EuiSwitchEvent } from '@elastic/eui'; import { VectorStyleColorEditor } from './color/vector_style_color_editor'; import { VectorStyleSizeEditor } from './size/vector_style_size_editor'; +// @ts-expect-error import { VectorStyleSymbolizeAsEditor } from './symbol/vector_style_symbolize_as_editor'; import { VectorStyleIconEditor } from './symbol/vector_style_icon_editor'; import { VectorStyleLabelEditor } from './label/vector_style_label_editor'; +// @ts-expect-error import { VectorStyleLabelBorderSizeEditor } from './label/vector_style_label_border_size_editor'; +// @ts-expect-error import { OrientationEditor } from './orientation/orientation_editor'; import { getDefaultDynamicProperties, getDefaultStaticProperties } from '../vector_style_defaults'; import { DEFAULT_FILL_COLORS, DEFAULT_LINE_COLORS } from '../../color_palettes'; -import { i18n } from '@kbn/i18n'; -import { EuiSpacer, EuiButtonGroup, EuiFormRow, EuiSwitch } from '@elastic/eui'; import { LABEL_BORDER_SIZES, VECTOR_STYLES, STYLE_TYPE, VECTOR_SHAPE_TYPE, } from '../../../../../common/constants'; -import { createStyleFieldsHelper } from '../style_fields_helper'; - -export class VectorStyleEditor extends Component { - state = { - styleFields: [], - defaultDynamicProperties: getDefaultDynamicProperties(), - defaultStaticProperties: getDefaultStaticProperties(), - supportedFeatures: undefined, - selectedFeature: null, - }; +import { createStyleFieldsHelper, StyleField, StyleFieldsHelper } from '../style_fields_helper'; +import { + ColorDynamicOptions, + ColorStaticOptions, + DynamicStylePropertyOptions, + IconDynamicOptions, + IconStaticOptions, + LabelDynamicOptions, + LabelStaticOptions, + SizeDynamicOptions, + SizeStaticOptions, + StaticStylePropertyOptions, + StylePropertyOptions, + VectorStylePropertiesDescriptor, +} from '../../../../../common/descriptor_types'; +import { IStyleProperty } from '../properties/style_property'; +import { SymbolizeAsProperty } from '../properties/symbolize_as_property'; +import { LabelBorderSizeProperty } from '../properties/label_border_size_property'; +import { StaticTextProperty } from '../properties/static_text_property'; +import { StaticSizeProperty } from '../properties/static_size_property'; +import { IVectorLayer } from '../../../layers/vector_layer/vector_layer'; + +export interface StyleProperties { + [key: string]: IStyleProperty; +} + +interface Props { + layer: IVectorLayer; + isPointsOnly: boolean; + isLinesOnly: boolean; + onIsTimeAwareChange: (isTimeAware: boolean) => void; + handlePropertyChange: (propertyName: VECTOR_STYLES, stylePropertyDescriptor: unknown) => void; + hasBorder: boolean; + styleProperties: StyleProperties; + isTimeAware: boolean; + showIsTimeAware: boolean; +} + +interface State { + styleFields: StyleField[]; + defaultDynamicProperties: VectorStylePropertiesDescriptor; + defaultStaticProperties: VectorStylePropertiesDescriptor; + supportedFeatures: VECTOR_SHAPE_TYPE[]; + selectedFeature: VECTOR_SHAPE_TYPE; + styleFieldsHelper?: StyleFieldsHelper; +} + +export class VectorStyleEditor extends Component { + private _isMounted: boolean = false; + + constructor(props: Props) { + super(props); + + let selectedFeature = VECTOR_SHAPE_TYPE.POLYGON; + if (props.isPointsOnly) { + selectedFeature = VECTOR_SHAPE_TYPE.POINT; + } else if (props.isLinesOnly) { + selectedFeature = VECTOR_SHAPE_TYPE.LINE; + } + + this.state = { + styleFields: [], + defaultDynamicProperties: getDefaultDynamicProperties(), + defaultStaticProperties: getDefaultStaticProperties(), + supportedFeatures: [], + selectedFeature, + }; + } componentWillUnmount() { this._isMounted = false; @@ -68,36 +129,20 @@ export class VectorStyleEditor extends Component { async _loadSupportedFeatures() { const supportedFeatures = await this.props.layer.getSource().getSupportedShapeTypes(); - if (!this._isMounted) { - return; - } - - if (!_.isEqual(supportedFeatures, this.state.supportedFeatures)) { + if (this._isMounted && !_.isEqual(supportedFeatures, this.state.supportedFeatures)) { this.setState({ supportedFeatures }); } - - if (this.state.selectedFeature === null) { - let selectedFeature = VECTOR_SHAPE_TYPE.POLYGON; - if (this.props.isPointsOnly) { - selectedFeature = VECTOR_SHAPE_TYPE.POINT; - } else if (this.props.isLinesOnly) { - selectedFeature = VECTOR_SHAPE_TYPE.LINE; - } - this.setState({ - selectedFeature: selectedFeature, - }); - } } - _handleSelectedFeatureChange = (selectedFeature) => { - this.setState({ selectedFeature }); + _handleSelectedFeatureChange = (selectedFeature: string) => { + this.setState({ selectedFeature: selectedFeature as VECTOR_SHAPE_TYPE }); }; - _onIsTimeAwareChange = (event) => { + _onIsTimeAwareChange = (event: EuiSwitchEvent) => { this.props.onIsTimeAwareChange(event.target.checked); }; - _onStaticStyleChange = (propertyName, options) => { + _onStaticStyleChange = (propertyName: VECTOR_STYLES, options: StaticStylePropertyOptions) => { const styleDescriptor = { type: STYLE_TYPE.STATIC, options, @@ -105,7 +150,7 @@ export class VectorStyleEditor extends Component { this.props.handlePropertyChange(propertyName, styleDescriptor); }; - _onDynamicStyleChange = (propertyName, options) => { + _onDynamicStyleChange = (propertyName: VECTOR_STYLES, options: DynamicStylePropertyOptions) => { const styleDescriptor = { type: STYLE_TYPE.DYNAMIC, options, @@ -115,18 +160,21 @@ export class VectorStyleEditor extends Component { _hasMarkerOrIcon() { const iconSize = this.props.styleProperties[VECTOR_STYLES.ICON_SIZE]; - return iconSize.isDynamic() || iconSize.getOptions().size > 0; + return iconSize.isDynamic() || (iconSize as StaticSizeProperty).getOptions().size > 0; } _hasLabel() { const label = this.props.styleProperties[VECTOR_STYLES.LABEL_TEXT]; return label.isDynamic() ? label.isComplete() - : label.getOptions().value != null && label.getOptions().value.length; + : (label as StaticTextProperty).getOptions().value != null && + (label as StaticTextProperty).getOptions().value.length; } _hasLabelBorder() { - const labelBorderSize = this.props.styleProperties[VECTOR_STYLES.LABEL_BORDER_SIZE]; + const labelBorderSize = this.props.styleProperties[ + VECTOR_STYLES.LABEL_BORDER_SIZE + ] as LabelBorderSizeProperty; return labelBorderSize.getOptions().size !== LABEL_BORDER_SIZES.NONE; } @@ -138,13 +186,18 @@ export class VectorStyleEditor extends Component { swatches={DEFAULT_FILL_COLORS} onStaticStyleChange={this._onStaticStyleChange} onDynamicStyleChange={this._onDynamicStyleChange} - styleProperty={this.props.styleProperties[VECTOR_STYLES.FILL_COLOR]} - fields={this.state.styleFieldsHelper.getFieldsForStyle(VECTOR_STYLES.FILL_COLOR)} + styleProperty={ + this.props.styleProperties[VECTOR_STYLES.FILL_COLOR] as IStyleProperty< + ColorDynamicOptions | ColorStaticOptions + > + } + fields={this.state.styleFieldsHelper!.getFieldsForStyle(VECTOR_STYLES.FILL_COLOR)} defaultStaticStyleOptions={ - this.state.defaultStaticProperties[VECTOR_STYLES.FILL_COLOR].options + this.state.defaultStaticProperties[VECTOR_STYLES.FILL_COLOR].options as ColorStaticOptions } defaultDynamicStyleOptions={ - this.state.defaultDynamicProperties[VECTOR_STYLES.FILL_COLOR].options + this.state.defaultDynamicProperties[VECTOR_STYLES.FILL_COLOR] + .options as ColorDynamicOptions } /> ); @@ -159,13 +212,18 @@ export class VectorStyleEditor extends Component { swatches={DEFAULT_LINE_COLORS} onStaticStyleChange={this._onStaticStyleChange} onDynamicStyleChange={this._onDynamicStyleChange} - styleProperty={this.props.styleProperties[VECTOR_STYLES.LINE_COLOR]} - fields={this.state.styleFieldsHelper.getFieldsForStyle(VECTOR_STYLES.LINE_COLOR)} + styleProperty={ + this.props.styleProperties[VECTOR_STYLES.LINE_COLOR] as IStyleProperty< + ColorDynamicOptions | ColorStaticOptions + > + } + fields={this.state.styleFieldsHelper!.getFieldsForStyle(VECTOR_STYLES.LINE_COLOR)} defaultStaticStyleOptions={ - this.state.defaultStaticProperties[VECTOR_STYLES.LINE_COLOR].options + this.state.defaultStaticProperties[VECTOR_STYLES.LINE_COLOR].options as ColorStaticOptions } defaultDynamicStyleOptions={ - this.state.defaultDynamicProperties[VECTOR_STYLES.LINE_COLOR].options + this.state.defaultDynamicProperties[VECTOR_STYLES.LINE_COLOR] + .options as ColorDynamicOptions } /> ); @@ -178,13 +236,18 @@ export class VectorStyleEditor extends Component { disabledBy={VECTOR_STYLES.ICON_SIZE} onStaticStyleChange={this._onStaticStyleChange} onDynamicStyleChange={this._onDynamicStyleChange} - styleProperty={this.props.styleProperties[VECTOR_STYLES.LINE_WIDTH]} - fields={this.state.styleFieldsHelper.getFieldsForStyle(VECTOR_STYLES.LINE_WIDTH)} + styleProperty={ + this.props.styleProperties[VECTOR_STYLES.LINE_WIDTH] as IStyleProperty< + SizeDynamicOptions | SizeStaticOptions + > + } + fields={this.state.styleFieldsHelper!.getFieldsForStyle(VECTOR_STYLES.LINE_WIDTH)} defaultStaticStyleOptions={ - this.state.defaultStaticProperties[VECTOR_STYLES.LINE_WIDTH].options + this.state.defaultStaticProperties[VECTOR_STYLES.LINE_WIDTH].options as SizeStaticOptions } defaultDynamicStyleOptions={ - this.state.defaultDynamicProperties[VECTOR_STYLES.LINE_WIDTH].options + this.state.defaultDynamicProperties[VECTOR_STYLES.LINE_WIDTH] + .options as SizeDynamicOptions } /> ); @@ -198,13 +261,19 @@ export class VectorStyleEditor extends Component { + } + fields={this.state.styleFieldsHelper!.getFieldsForStyle(VECTOR_STYLES.LABEL_TEXT)} defaultStaticStyleOptions={ - this.state.defaultStaticProperties[VECTOR_STYLES.LABEL_TEXT].options + this.state.defaultStaticProperties[VECTOR_STYLES.LABEL_TEXT] + .options as LabelStaticOptions } defaultDynamicStyleOptions={ - this.state.defaultDynamicProperties[VECTOR_STYLES.LABEL_TEXT].options + this.state.defaultDynamicProperties[VECTOR_STYLES.LABEL_TEXT] + .options as LabelDynamicOptions } /> @@ -215,13 +284,19 @@ export class VectorStyleEditor extends Component { swatches={DEFAULT_LINE_COLORS} onStaticStyleChange={this._onStaticStyleChange} onDynamicStyleChange={this._onDynamicStyleChange} - styleProperty={this.props.styleProperties[VECTOR_STYLES.LABEL_COLOR]} - fields={this.state.styleFieldsHelper.getFieldsForStyle(VECTOR_STYLES.LABEL_COLOR)} + styleProperty={ + this.props.styleProperties[VECTOR_STYLES.LABEL_COLOR] as IStyleProperty< + ColorDynamicOptions | ColorStaticOptions + > + } + fields={this.state.styleFieldsHelper!.getFieldsForStyle(VECTOR_STYLES.LABEL_COLOR)} defaultStaticStyleOptions={ - this.state.defaultStaticProperties[VECTOR_STYLES.LABEL_COLOR].options + this.state.defaultStaticProperties[VECTOR_STYLES.LABEL_COLOR] + .options as ColorStaticOptions } defaultDynamicStyleOptions={ - this.state.defaultDynamicProperties[VECTOR_STYLES.LABEL_COLOR].options + this.state.defaultDynamicProperties[VECTOR_STYLES.LABEL_COLOR] + .options as ColorDynamicOptions } /> @@ -231,13 +306,19 @@ export class VectorStyleEditor extends Component { disabledBy={VECTOR_STYLES.LABEL_TEXT} onStaticStyleChange={this._onStaticStyleChange} onDynamicStyleChange={this._onDynamicStyleChange} - styleProperty={this.props.styleProperties[VECTOR_STYLES.LABEL_SIZE]} - fields={this.state.styleFieldsHelper.getFieldsForStyle(VECTOR_STYLES.LABEL_SIZE)} + styleProperty={ + this.props.styleProperties[VECTOR_STYLES.LABEL_SIZE] as IStyleProperty< + SizeDynamicOptions | SizeStaticOptions + > + } + fields={this.state.styleFieldsHelper!.getFieldsForStyle(VECTOR_STYLES.LABEL_SIZE)} defaultStaticStyleOptions={ - this.state.defaultStaticProperties[VECTOR_STYLES.LABEL_SIZE].options + this.state.defaultStaticProperties[VECTOR_STYLES.LABEL_SIZE] + .options as SizeStaticOptions } defaultDynamicStyleOptions={ - this.state.defaultDynamicProperties[VECTOR_STYLES.LABEL_SIZE].options + this.state.defaultDynamicProperties[VECTOR_STYLES.LABEL_SIZE] + .options as SizeDynamicOptions } /> @@ -248,13 +329,19 @@ export class VectorStyleEditor extends Component { swatches={DEFAULT_LINE_COLORS} onStaticStyleChange={this._onStaticStyleChange} onDynamicStyleChange={this._onDynamicStyleChange} - styleProperty={this.props.styleProperties[VECTOR_STYLES.LABEL_BORDER_COLOR]} - fields={this.state.styleFieldsHelper.getFieldsForStyle(VECTOR_STYLES.LABEL_BORDER_COLOR)} + styleProperty={ + this.props.styleProperties[VECTOR_STYLES.LABEL_BORDER_COLOR] as IStyleProperty< + ColorDynamicOptions | ColorStaticOptions + > + } + fields={this.state.styleFieldsHelper!.getFieldsForStyle(VECTOR_STYLES.LABEL_BORDER_COLOR)} defaultStaticStyleOptions={ - this.state.defaultStaticProperties[VECTOR_STYLES.LABEL_BORDER_COLOR].options + this.state.defaultStaticProperties[VECTOR_STYLES.LABEL_BORDER_COLOR] + .options as ColorStaticOptions } defaultDynamicStyleOptions={ - this.state.defaultDynamicProperties[VECTOR_STYLES.LABEL_BORDER_COLOR].options + this.state.defaultDynamicProperties[VECTOR_STYLES.LABEL_BORDER_COLOR] + .options as ColorDynamicOptions } /> @@ -274,7 +361,11 @@ export class VectorStyleEditor extends Component { const hasMarkerOrIcon = this._hasMarkerOrIcon(); let iconOrientationEditor; let iconEditor; - if (this.props.styleProperties[VECTOR_STYLES.SYMBOLIZE_AS].isSymbolizedAsIcon()) { + if ( + (this.props.styleProperties[ + VECTOR_STYLES.SYMBOLIZE_AS + ] as SymbolizeAsProperty).isSymbolizedAsIcon() + ) { iconOrientationEditor = ( + } + fields={this.state.styleFieldsHelper!.getFieldsForStyle(VECTOR_STYLES.ICON)} defaultStaticStyleOptions={ - this.state.defaultStaticProperties[VECTOR_STYLES.ICON].options + this.state.defaultStaticProperties[VECTOR_STYLES.ICON].options as IconStaticOptions } defaultDynamicStyleOptions={ - this.state.defaultDynamicProperties[VECTOR_STYLES.ICON].options + this.state.defaultDynamicProperties[VECTOR_STYLES.ICON].options as IconDynamicOptions } /> @@ -341,13 +436,18 @@ export class VectorStyleEditor extends Component { + } + fields={this.state.styleFieldsHelper!.getFieldsForStyle(VECTOR_STYLES.ICON_SIZE)} defaultStaticStyleOptions={ - this.state.defaultStaticProperties[VECTOR_STYLES.ICON_SIZE].options + this.state.defaultStaticProperties[VECTOR_STYLES.ICON_SIZE].options as SizeStaticOptions } defaultDynamicStyleOptions={ - this.state.defaultDynamicProperties[VECTOR_STYLES.ICON_SIZE].options + this.state.defaultDynamicProperties[VECTOR_STYLES.ICON_SIZE] + .options as SizeDynamicOptions } /> @@ -385,7 +485,7 @@ export class VectorStyleEditor extends Component { _renderProperties() { const { supportedFeatures, selectedFeature, styleFieldsHelper } = this.state; - if (!supportedFeatures || !styleFieldsHelper) { + if (supportedFeatures.length === 0 || !styleFieldsHelper) { return null; } diff --git a/x-pack/plugins/maps/public/classes/styles/vector/style_fields_helper.ts b/x-pack/plugins/maps/public/classes/styles/vector/style_fields_helper.ts index 8613f9e1e946..fbe643a40148 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/style_fields_helper.ts +++ b/x-pack/plugins/maps/public/classes/styles/vector/style_fields_helper.ts @@ -34,7 +34,7 @@ export async function createStyleFieldsHelper(fields: IField[]): Promise = {}, + isTimeAware = true + ) { return { type: LAYER_STYLE_TYPE.VECTOR, - properties: { ...getDefaultProperties(), ...properties }, + properties: { ...getDefaultStaticProperties(), ...properties }, isTimeAware, }; } static createDefaultStyleProperties(mapColors: string[]) { - return getDefaultProperties(mapColors); + return getDefaultStaticProperties(mapColors); } constructor( @@ -192,7 +188,7 @@ export class VectorStyle implements IVectorStyle { this._styleMeta = new StyleMeta(this._descriptor.__styleMeta); this._symbolizeAsStyleProperty = new SymbolizeAsProperty( - this._descriptor.properties[VECTOR_STYLES.SYMBOLIZE_AS]!.options, + this._descriptor.properties[VECTOR_STYLES.SYMBOLIZE_AS].options, VECTOR_STYLES.SYMBOLIZE_AS ); this._lineColorStyleProperty = this._makeColorProperty( @@ -237,7 +233,7 @@ export class VectorStyle implements IVectorStyle { VECTOR_STYLES.LABEL_BORDER_COLOR ); this._labelBorderSizeStyleProperty = new LabelBorderSizeProperty( - this._descriptor.properties[VECTOR_STYLES.LABEL_BORDER_SIZE]!.options, + this._descriptor.properties[VECTOR_STYLES.LABEL_BORDER_SIZE].options, VECTOR_STYLES.LABEL_BORDER_SIZE, this._labelSizeStyleProperty ); @@ -270,16 +266,10 @@ export class VectorStyle implements IVectorStyle { : (this._lineWidthStyleProperty as StaticSizeProperty).getOptions().size !== 0; } - renderEditor({ - layer, - onStyleDescriptorChange, - }: { - layer: ILayer; - onStyleDescriptorChange: (styleDescriptor: StyleDescriptor) => void; - }) { + renderEditor(onStyleDescriptorChange: (styleDescriptor: StyleDescriptor) => void) { const rawProperties = this.getRawProperties(); - const handlePropertyChange = (propertyName: VECTOR_STYLES, settings: any) => { - rawProperties[propertyName] = settings; // override single property, but preserve the rest + const handlePropertyChange = (propertyName: VECTOR_STYLES, stylePropertyDescriptor: any) => { + rawProperties[propertyName] = stylePropertyDescriptor; // override single property, but preserve the rest const vectorStyleDescriptor = VectorStyle.createDescriptor(rawProperties, this.isTimeAware()); onStyleDescriptorChange(vectorStyleDescriptor); }; @@ -293,9 +283,8 @@ export class VectorStyle implements IVectorStyle { return dynamicStyleProp.isFieldMetaEnabled(); }); - const styleProperties: VectorStylePropertiesDescriptor = {}; + const styleProperties: StyleProperties = {}; this.getAllStyleProperties().forEach((styleProperty) => { - // @ts-expect-error styleProperties[styleProperty.getStyleName()] = styleProperty; }); @@ -303,7 +292,7 @@ export class VectorStyle implements IVectorStyle { { test('Should use first color in DEFAULT_*_COLORS when no colors are used on the map', () => { const styleProperties = getDefaultStaticProperties([]); - expect(styleProperties[VECTOR_STYLES.FILL_COLOR]!.options.color).toBe('#54B399'); - expect(styleProperties[VECTOR_STYLES.LINE_COLOR]!.options.color).toBe('#41937c'); + expect(styleProperties[VECTOR_STYLES.FILL_COLOR].options.color).toBe('#54B399'); + expect(styleProperties[VECTOR_STYLES.LINE_COLOR].options.color).toBe('#41937c'); }); test('Should next color in DEFAULT_*_COLORS when colors are used on the map', () => { const styleProperties = getDefaultStaticProperties(['#54B399']); - expect(styleProperties[VECTOR_STYLES.FILL_COLOR]!.options.color).toBe('#6092C0'); - expect(styleProperties[VECTOR_STYLES.LINE_COLOR]!.options.color).toBe('#4379aa'); + expect(styleProperties[VECTOR_STYLES.FILL_COLOR].options.color).toBe('#6092C0'); + expect(styleProperties[VECTOR_STYLES.LINE_COLOR].options.color).toBe('#4379aa'); }); }); diff --git a/x-pack/plugins/maps/public/classes/styles/vector/vector_style_defaults.ts b/x-pack/plugins/maps/public/classes/styles/vector/vector_style_defaults.ts index 50321510c2ba..fc152b9e5a97 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/vector_style_defaults.ts +++ b/x-pack/plugins/maps/public/classes/styles/vector/vector_style_defaults.ts @@ -37,22 +37,6 @@ export const POLYGON_STYLES = [ VECTOR_STYLES.LINE_WIDTH, ]; -export function getDefaultProperties(mapColors: string[] = []): VectorStylePropertiesDescriptor { - return { - ...getDefaultStaticProperties(mapColors), - [VECTOR_STYLES.SYMBOLIZE_AS]: { - options: { - value: SYMBOLIZE_AS_TYPES.CIRCLE, - }, - }, - [VECTOR_STYLES.LABEL_BORDER_SIZE]: { - options: { - size: LABEL_BORDER_SIZES.SMALL, - }, - }, - }; -} - export function getDefaultStaticProperties( mapColors: string[] = [] ): VectorStylePropertiesDescriptor { @@ -129,6 +113,16 @@ export function getDefaultStaticProperties( color: isDarkMode ? '#000000' : '#FFFFFF', }, }, + [VECTOR_STYLES.SYMBOLIZE_AS]: { + options: { + value: SYMBOLIZE_AS_TYPES.CIRCLE, + }, + }, + [VECTOR_STYLES.LABEL_BORDER_SIZE]: { + options: { + size: LABEL_BORDER_SIZES.SMALL, + }, + }, }; } @@ -244,5 +238,15 @@ export function getDefaultDynamicProperties(): VectorStylePropertiesDescriptor { }, }, }, + [VECTOR_STYLES.SYMBOLIZE_AS]: { + options: { + value: SYMBOLIZE_AS_TYPES.CIRCLE, + }, + }, + [VECTOR_STYLES.LABEL_BORDER_SIZE]: { + options: { + size: LABEL_BORDER_SIZES.SMALL, + }, + }, }; } diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/style_settings/style_settings.js b/x-pack/plugins/maps/public/connected_components/layer_panel/style_settings/style_settings.js index 69cf51fb29c0..e460d7728a31 100644 --- a/x-pack/plugins/maps/public/connected_components/layer_panel/style_settings/style_settings.js +++ b/x-pack/plugins/maps/public/connected_components/layer_panel/style_settings/style_settings.js @@ -11,9 +11,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiTitle, EuiPanel, EuiSpacer } from '@elast import { FormattedMessage } from '@kbn/i18n/react'; export function StyleSettings({ layer, updateStyleDescriptor }) { - const settingsEditor = layer.renderStyleEditor({ - onStyleDescriptorChange: updateStyleDescriptor, - }); + const settingsEditor = layer.renderStyleEditor(updateStyleDescriptor); if (!settingsEditor) { return null; From 9b30de41b6cf682b97e5a0518a18bb142fdb96bd Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Wed, 18 Nov 2020 22:04:26 -0700 Subject: [PATCH 32/49] [data.search] Server-side background session service (#81099) * [Search] Add request context and asScoped pattern * Update docs * Unify interface for getting search client * [WIP] [data.search] Server-side background session service * Update examples/search_examples/server/my_strategy.ts Co-authored-by: Anton Dosov * Review feedback * Fix checks * Add tapFirst and additional props for session * Fix CI * Fix security search * Fix test * Fix test for reals * Add restore method * Add code to search examples * Add restore and search using restored ID * Fix handling of preference and order of params * Trim & cleanup * Fix types * Review feedback * Add tests and remove handling of username * Update docs * Move utils to server * Review feedback * More review feedback * Regenerate docs * Review feedback * Doc changes Co-authored-by: Anton Dosov Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- ...ns-data-public.isearchoptions.isrestore.md | 13 + ...ins-data-public.isearchoptions.isstored.md | 13 + ...ugin-plugins-data-public.isearchoptions.md | 2 + ...gins-data-public.isessionservice.delete.md | 13 + ...lugins-data-public.isessionservice.find.md | 13 + ...plugins-data-public.isessionservice.get.md | 13 + ...s-data-public.isessionservice.isrestore.md | 13 + ...ns-data-public.isessionservice.isstored.md | 13 + ...gin-plugins-data-public.isessionservice.md | 9 +- ...ins-data-public.isessionservice.restore.md | 2 +- ...lugins-data-public.isessionservice.save.md | 13 + ...gins-data-public.isessionservice.update.md | 13 + ...ns-data-server.isearchoptions.isrestore.md | 13 + ...ins-data-server.isearchoptions.isstored.md | 13 + ...ugin-plugins-data-server.isearchoptions.md | 2 + examples/search_examples/kibana.json | 2 +- .../data/common/search/session/index.ts | 1 + .../data/common/search/session/mocks.ts | 7 + .../data/common/search/session/status.ts | 26 ++ .../data/common/search/session/types.ts | 62 ++++- src/plugins/data/common/search/types.ts | 11 + src/plugins/data/common/utils/index.ts | 1 + .../data/common/utils/tap_first.test.ts | 30 +++ src/plugins/data/common/utils/tap_first.ts | 31 +++ src/plugins/data/public/public.api.md | 25 +- .../data/public/search/search_interceptor.ts | 19 +- .../data/public/search/session_service.ts | 69 +++++- .../saved_objects/background_session.ts | 56 +++++ .../data/server/saved_objects/index.ts | 1 + src/plugins/data/server/search/mocks.ts | 21 ++ .../data/server/search/routes/search.ts | 14 +- .../data/server/search/routes/session.test.ts | 119 +++++++++ .../data/server/search/routes/session.ts | 201 +++++++++++++++ .../data/server/search/search_service.ts | 44 +++- .../data/server/search/session/index.ts | 20 ++ .../search/session/session_service.test.ts | 233 ++++++++++++++++++ .../server/search/session/session_service.ts | 204 +++++++++++++++ .../data/server/search/session/utils.test.ts | 37 +++ .../data/server/search/session/utils.ts | 30 +++ src/plugins/data/server/server.api.md | 2 + src/plugins/embeddable/public/public.api.md | 4 +- .../public/search/search_interceptor.ts | 8 +- .../server/search/es_search_strategy.ts | 1 + 43 files changed, 1407 insertions(+), 30 deletions(-) create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.isrestore.md create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.isstored.md create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.delete.md create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.find.md create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.get.md create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.isrestore.md create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.isstored.md create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.save.md create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.update.md create mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.isrestore.md create mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.isstored.md create mode 100644 src/plugins/data/common/search/session/status.ts create mode 100644 src/plugins/data/common/utils/tap_first.test.ts create mode 100644 src/plugins/data/common/utils/tap_first.ts create mode 100644 src/plugins/data/server/saved_objects/background_session.ts create mode 100644 src/plugins/data/server/search/routes/session.test.ts create mode 100644 src/plugins/data/server/search/routes/session.ts create mode 100644 src/plugins/data/server/search/session/index.ts create mode 100644 src/plugins/data/server/search/session/session_service.test.ts create mode 100644 src/plugins/data/server/search/session/session_service.ts create mode 100644 src/plugins/data/server/search/session/utils.test.ts create mode 100644 src/plugins/data/server/search/session/utils.ts diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.isrestore.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.isrestore.md new file mode 100644 index 000000000000..672d77719962 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.isrestore.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISearchOptions](./kibana-plugin-plugins-data-public.isearchoptions.md) > [isRestore](./kibana-plugin-plugins-data-public.isearchoptions.isrestore.md) + +## ISearchOptions.isRestore property + +Whether the session is restored (i.e. search requests should re-use the stored search IDs, rather than starting from scratch) + +Signature: + +```typescript +isRestore?: boolean; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.isstored.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.isstored.md new file mode 100644 index 000000000000..0d2c173f351c --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.isstored.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISearchOptions](./kibana-plugin-plugins-data-public.isearchoptions.md) > [isStored](./kibana-plugin-plugins-data-public.isearchoptions.isstored.md) + +## ISearchOptions.isStored property + +Whether the session is already saved (i.e. sent to background) + +Signature: + +```typescript +isStored?: boolean; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.md index 76d091417344..5acd837495da 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.md @@ -15,6 +15,8 @@ export interface ISearchOptions | Property | Type | Description | | --- | --- | --- | | [abortSignal](./kibana-plugin-plugins-data-public.isearchoptions.abortsignal.md) | AbortSignal | An AbortSignal that allows the caller of search to abort a search request. | +| [isRestore](./kibana-plugin-plugins-data-public.isearchoptions.isrestore.md) | boolean | Whether the session is restored (i.e. search requests should re-use the stored search IDs, rather than starting from scratch) | +| [isStored](./kibana-plugin-plugins-data-public.isearchoptions.isstored.md) | boolean | Whether the session is already saved (i.e. sent to background) | | [sessionId](./kibana-plugin-plugins-data-public.isearchoptions.sessionid.md) | string | A session ID, grouping multiple search requests into a single session. | | [strategy](./kibana-plugin-plugins-data-public.isearchoptions.strategy.md) | string | Use this option to force using a specific server side search strategy. Leave empty to use the default strategy. | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.delete.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.delete.md new file mode 100644 index 000000000000..eabb966160c4 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.delete.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) > [delete](./kibana-plugin-plugins-data-public.isessionservice.delete.md) + +## ISessionService.delete property + +Deletes a session + +Signature: + +```typescript +delete: (sessionId: string) => Promise; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.find.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.find.md new file mode 100644 index 000000000000..58e2fea0e6fe --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.find.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) > [find](./kibana-plugin-plugins-data-public.isessionservice.find.md) + +## ISessionService.find property + +Gets a list of saved sessions + +Signature: + +```typescript +find: (options: SearchSessionFindOptions) => Promise>; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.get.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.get.md new file mode 100644 index 000000000000..a2dff2f18253 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.get.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) > [get](./kibana-plugin-plugins-data-public.isessionservice.get.md) + +## ISessionService.get property + +Gets a saved session + +Signature: + +```typescript +get: (sessionId: string) => Promise>; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.isrestore.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.isrestore.md new file mode 100644 index 000000000000..8d8cd1f31bb9 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.isrestore.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) > [isRestore](./kibana-plugin-plugins-data-public.isessionservice.isrestore.md) + +## ISessionService.isRestore property + +Whether the active session is restored (i.e. reusing previous search IDs) + +Signature: + +```typescript +isRestore: () => boolean; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.isstored.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.isstored.md new file mode 100644 index 000000000000..db737880bb84 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.isstored.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) > [isStored](./kibana-plugin-plugins-data-public.isessionservice.isstored.md) + +## ISessionService.isStored property + +Whether the active session is already saved (i.e. sent to background) + +Signature: + +```typescript +isStored: () => boolean; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.md index 174f9dbe66bf..02c0a821e552 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.md @@ -15,8 +15,15 @@ export interface ISessionService | Property | Type | Description | | --- | --- | --- | | [clear](./kibana-plugin-plugins-data-public.isessionservice.clear.md) | () => void | Clears the active session. | +| [delete](./kibana-plugin-plugins-data-public.isessionservice.delete.md) | (sessionId: string) => Promise<void> | Deletes a session | +| [find](./kibana-plugin-plugins-data-public.isessionservice.find.md) | (options: SearchSessionFindOptions) => Promise<SavedObjectsFindResponse<BackgroundSessionSavedObjectAttributes>> | Gets a list of saved sessions | +| [get](./kibana-plugin-plugins-data-public.isessionservice.get.md) | (sessionId: string) => Promise<SavedObject<BackgroundSessionSavedObjectAttributes>> | Gets a saved session | | [getSession$](./kibana-plugin-plugins-data-public.isessionservice.getsession_.md) | () => Observable<string | undefined> | Returns the observable that emits an update every time the session ID changes | | [getSessionId](./kibana-plugin-plugins-data-public.isessionservice.getsessionid.md) | () => string | undefined | Returns the active session ID | -| [restore](./kibana-plugin-plugins-data-public.isessionservice.restore.md) | (sessionId: string) => void | Restores existing session | +| [isRestore](./kibana-plugin-plugins-data-public.isessionservice.isrestore.md) | () => boolean | Whether the active session is restored (i.e. reusing previous search IDs) | +| [isStored](./kibana-plugin-plugins-data-public.isessionservice.isstored.md) | () => boolean | Whether the active session is already saved (i.e. sent to background) | +| [restore](./kibana-plugin-plugins-data-public.isessionservice.restore.md) | (sessionId: string) => Promise<SavedObject<BackgroundSessionSavedObjectAttributes>> | Restores existing session | +| [save](./kibana-plugin-plugins-data-public.isessionservice.save.md) | (name: string, url: string) => Promise<SavedObject<BackgroundSessionSavedObjectAttributes>> | Saves a session | | [start](./kibana-plugin-plugins-data-public.isessionservice.start.md) | () => string | Starts a new session | +| [update](./kibana-plugin-plugins-data-public.isessionservice.update.md) | (sessionId: string, attributes: Partial<BackgroundSessionSavedObjectAttributes>) => Promise<any> | Updates a session | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.restore.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.restore.md index 857e85bbd30e..96106a6ef7e2 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.restore.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.restore.md @@ -9,5 +9,5 @@ Restores existing session Signature: ```typescript -restore: (sessionId: string) => void; +restore: (sessionId: string) => Promise>; ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.save.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.save.md new file mode 100644 index 000000000000..4ac4a9661446 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.save.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) > [save](./kibana-plugin-plugins-data-public.isessionservice.save.md) + +## ISessionService.save property + +Saves a session + +Signature: + +```typescript +save: (name: string, url: string) => Promise>; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.update.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.update.md new file mode 100644 index 000000000000..5e2ff53d58ab --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isessionservice.update.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) > [update](./kibana-plugin-plugins-data-public.isessionservice.update.md) + +## ISessionService.update property + +Updates a session + +Signature: + +```typescript +update: (sessionId: string, attributes: Partial) => Promise; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.isrestore.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.isrestore.md new file mode 100644 index 000000000000..ae518e5a052f --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.isrestore.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [ISearchOptions](./kibana-plugin-plugins-data-server.isearchoptions.md) > [isRestore](./kibana-plugin-plugins-data-server.isearchoptions.isrestore.md) + +## ISearchOptions.isRestore property + +Whether the session is restored (i.e. search requests should re-use the stored search IDs, rather than starting from scratch) + +Signature: + +```typescript +isRestore?: boolean; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.isstored.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.isstored.md new file mode 100644 index 000000000000..aceee7fd6df6 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.isstored.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [ISearchOptions](./kibana-plugin-plugins-data-server.isearchoptions.md) > [isStored](./kibana-plugin-plugins-data-server.isearchoptions.isstored.md) + +## ISearchOptions.isStored property + +Whether the session is already saved (i.e. sent to background) + +Signature: + +```typescript +isStored?: boolean; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.md index af96e1413ba0..85847e1c61d2 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.md @@ -15,6 +15,8 @@ export interface ISearchOptions | Property | Type | Description | | --- | --- | --- | | [abortSignal](./kibana-plugin-plugins-data-server.isearchoptions.abortsignal.md) | AbortSignal | An AbortSignal that allows the caller of search to abort a search request. | +| [isRestore](./kibana-plugin-plugins-data-server.isearchoptions.isrestore.md) | boolean | Whether the session is restored (i.e. search requests should re-use the stored search IDs, rather than starting from scratch) | +| [isStored](./kibana-plugin-plugins-data-server.isearchoptions.isstored.md) | boolean | Whether the session is already saved (i.e. sent to background) | | [sessionId](./kibana-plugin-plugins-data-server.isearchoptions.sessionid.md) | string | A session ID, grouping multiple search requests into a single session. | | [strategy](./kibana-plugin-plugins-data-server.isearchoptions.strategy.md) | string | Use this option to force using a specific server side search strategy. Leave empty to use the default strategy. | diff --git a/examples/search_examples/kibana.json b/examples/search_examples/kibana.json index 9577ec353a4c..07bb6a0b750e 100644 --- a/examples/search_examples/kibana.json +++ b/examples/search_examples/kibana.json @@ -4,7 +4,7 @@ "kibanaVersion": "kibana", "server": true, "ui": true, - "requiredPlugins": ["navigation", "data", "developerExamples"], + "requiredPlugins": ["navigation", "data", "developerExamples", "kibanaUtils"], "optionalPlugins": [], "requiredBundles": [] } diff --git a/src/plugins/data/common/search/session/index.ts b/src/plugins/data/common/search/session/index.ts index d8f7b5091eb8..0feb43f8f1d4 100644 --- a/src/plugins/data/common/search/session/index.ts +++ b/src/plugins/data/common/search/session/index.ts @@ -17,4 +17,5 @@ * under the License. */ +export * from './status'; export * from './types'; diff --git a/src/plugins/data/common/search/session/mocks.ts b/src/plugins/data/common/search/session/mocks.ts index 370faaa640c5..4604e15e4e93 100644 --- a/src/plugins/data/common/search/session/mocks.ts +++ b/src/plugins/data/common/search/session/mocks.ts @@ -27,5 +27,12 @@ export function getSessionServiceMock(): jest.Mocked { restore: jest.fn(), getSessionId: jest.fn(), getSession$: jest.fn(() => new BehaviorSubject(undefined).asObservable()), + isStored: jest.fn(), + isRestore: jest.fn(), + save: jest.fn(), + get: jest.fn(), + find: jest.fn(), + update: jest.fn(), + delete: jest.fn(), }; } diff --git a/src/plugins/data/common/search/session/status.ts b/src/plugins/data/common/search/session/status.ts new file mode 100644 index 000000000000..1f6b6eb3084b --- /dev/null +++ b/src/plugins/data/common/search/session/status.ts @@ -0,0 +1,26 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export enum BackgroundSessionStatus { + IN_PROGRESS = 'in_progress', + ERROR = 'error', + COMPLETE = 'complete', + CANCELLED = 'cancelled', + EXPIRED = 'expired', +} diff --git a/src/plugins/data/common/search/session/types.ts b/src/plugins/data/common/search/session/types.ts index 6660b8395547..d1ab22057695 100644 --- a/src/plugins/data/common/search/session/types.ts +++ b/src/plugins/data/common/search/session/types.ts @@ -18,6 +18,7 @@ */ import { Observable } from 'rxjs'; +import type { SavedObject, SavedObjectsFindResponse } from 'kibana/server'; export interface ISessionService { /** @@ -30,6 +31,17 @@ export interface ISessionService { * @returns `Observable` */ getSession$: () => Observable; + + /** + * Whether the active session is already saved (i.e. sent to background) + */ + isStored: () => boolean; + + /** + * Whether the active session is restored (i.e. reusing previous search IDs) + */ + isRestore: () => boolean; + /** * Starts a new session */ @@ -38,10 +50,58 @@ export interface ISessionService { /** * Restores existing session */ - restore: (sessionId: string) => void; + restore: (sessionId: string) => Promise>; /** * Clears the active session. */ clear: () => void; + + /** + * Saves a session + */ + save: (name: string, url: string) => Promise>; + + /** + * Gets a saved session + */ + get: (sessionId: string) => Promise>; + + /** + * Gets a list of saved sessions + */ + find: ( + options: SearchSessionFindOptions + ) => Promise>; + + /** + * Updates a session + */ + update: ( + sessionId: string, + attributes: Partial + ) => Promise; + + /** + * Deletes a session + */ + delete: (sessionId: string) => Promise; +} + +export interface BackgroundSessionSavedObjectAttributes { + name: string; + created: string; + expires: string; + status: string; + initialState: Record; + restoreState: Record; + idMapping: Record; +} + +export interface SearchSessionFindOptions { + page?: number; + perPage?: number; + sortField?: string; + sortOrder?: string; + filter?: string; } diff --git a/src/plugins/data/common/search/types.ts b/src/plugins/data/common/search/types.ts index 7451edf5e2fa..695ee34d3b46 100644 --- a/src/plugins/data/common/search/types.ts +++ b/src/plugins/data/common/search/types.ts @@ -92,4 +92,15 @@ export interface ISearchOptions { * A session ID, grouping multiple search requests into a single session. */ sessionId?: string; + + /** + * Whether the session is already saved (i.e. sent to background) + */ + isStored?: boolean; + + /** + * Whether the session is restored (i.e. search requests should re-use the stored search IDs, + * rather than starting from scratch) + */ + isRestore?: boolean; } diff --git a/src/plugins/data/common/utils/index.ts b/src/plugins/data/common/utils/index.ts index 8b8686c51b9c..4b602cb963a8 100644 --- a/src/plugins/data/common/utils/index.ts +++ b/src/plugins/data/common/utils/index.ts @@ -19,3 +19,4 @@ /** @internal */ export { shortenDottedString } from './shorten_dotted_string'; +export { tapFirst } from './tap_first'; diff --git a/src/plugins/data/common/utils/tap_first.test.ts b/src/plugins/data/common/utils/tap_first.test.ts new file mode 100644 index 000000000000..033ae59f8c71 --- /dev/null +++ b/src/plugins/data/common/utils/tap_first.test.ts @@ -0,0 +1,30 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { of } from 'rxjs'; +import { tapFirst } from './tap_first'; + +describe('tapFirst', () => { + it('should tap the first and only the first', () => { + const fn = jest.fn(); + of(1, 2, 3).pipe(tapFirst(fn)).subscribe(); + expect(fn).toBeCalledTimes(1); + expect(fn).lastCalledWith(1); + }); +}); diff --git a/src/plugins/data/common/utils/tap_first.ts b/src/plugins/data/common/utils/tap_first.ts new file mode 100644 index 000000000000..2c783a3ef87f --- /dev/null +++ b/src/plugins/data/common/utils/tap_first.ts @@ -0,0 +1,31 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { pipe } from 'rxjs'; +import { tap } from 'rxjs/operators'; + +export function tapFirst(next: (x: T) => void) { + let isFirst = true; + return pipe( + tap((x: T) => { + if (isFirst) next(x); + isFirst = false; + }) + ); +} diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 165e11517311..6c4609e5506c 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -70,10 +70,12 @@ import { RequestAdapter } from 'src/plugins/inspector/common'; import { RequestStatistics as RequestStatistics_2 } from 'src/plugins/inspector/common'; import { Required } from '@kbn/utility-types'; import * as Rx from 'rxjs'; -import { SavedObject } from 'src/core/server'; -import { SavedObject as SavedObject_2 } from 'src/core/public'; +import { SavedObject } from 'kibana/server'; +import { SavedObject as SavedObject_2 } from 'src/core/server'; +import { SavedObject as SavedObject_3 } from 'src/core/public'; import { SavedObjectReference } from 'src/core/types'; import { SavedObjectsClientContract } from 'src/core/public'; +import { SavedObjectsFindResponse } from 'kibana/server'; import { Search } from '@elastic/elasticsearch/api/requestParams'; import { SearchResponse } from 'elasticsearch'; import { SerializedFieldFormat as SerializedFieldFormat_2 } from 'src/plugins/expressions/common'; @@ -1389,7 +1391,7 @@ export class IndexPatternsService { // Warning: (ae-forgotten-export) The symbol "IndexPatternSavedObjectAttrs" needs to be exported by the entry point index.d.ts // // (undocumented) - getCache: () => Promise[] | null | undefined>; + getCache: () => Promise[] | null | undefined>; getDefault: () => Promise; getFieldsForIndexPattern: (indexPattern: IndexPattern | IndexPatternSpec, options?: GetFieldsOptions | undefined) => Promise; // Warning: (ae-forgotten-export) The symbol "GetFieldsOptions" needs to be exported by the entry point index.d.ts @@ -1401,7 +1403,7 @@ export class IndexPatternsService { }>>; getTitles: (refresh?: boolean) => Promise; refreshFields: (indexPattern: IndexPattern) => Promise; - savedObjectToSpec: (savedObject: SavedObject) => IndexPatternSpec; + savedObjectToSpec: (savedObject: SavedObject_2) => IndexPatternSpec; setDefault: (id: string, force?: boolean) => Promise; updateSavedObject(indexPattern: IndexPattern, saveAttempts?: number, ignoreErrors?: boolean): Promise; } @@ -1446,6 +1448,8 @@ export type ISearchGeneric = | undefined // @public (undocumented) export interface ISessionService { clear: () => void; + delete: (sessionId: string) => Promise; + // Warning: (ae-forgotten-export) The symbol "SearchSessionFindOptions" needs to be exported by the entry point index.d.ts + find: (options: SearchSessionFindOptions) => Promise>; + get: (sessionId: string) => Promise>; getSession$: () => Observable; getSessionId: () => string | undefined; - restore: (sessionId: string) => void; + isRestore: () => boolean; + isStored: () => boolean; + // Warning: (ae-forgotten-export) The symbol "BackgroundSessionSavedObjectAttributes" needs to be exported by the entry point index.d.ts + restore: (sessionId: string) => Promise>; + save: (name: string, url: string) => Promise>; start: () => string; + update: (sessionId: string, attributes: Partial) => Promise; } // Warning: (ae-missing-release-tag) "isFilter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -2069,7 +2082,7 @@ export class SearchInterceptor { // @internal protected pendingCount$: BehaviorSubject; // @internal (undocumented) - protected runSearch(request: IKibanaSearchRequest, signal: AbortSignal, strategy?: string): Promise; + protected runSearch(request: IKibanaSearchRequest, options?: ISearchOptions): Promise; search(request: IKibanaSearchRequest, options?: ISearchOptions): Observable; // @internal (undocumented) protected setupAbortSignal({ abortSignal, timeout, }: { diff --git a/src/plugins/data/public/search/search_interceptor.ts b/src/plugins/data/public/search/search_interceptor.ts index 78e65802bcf9..3fadb723b27c 100644 --- a/src/plugins/data/public/search/search_interceptor.ts +++ b/src/plugins/data/public/search/search_interceptor.ts @@ -126,18 +126,25 @@ export class SearchInterceptor { */ protected runSearch( request: IKibanaSearchRequest, - signal: AbortSignal, - strategy?: string + options?: ISearchOptions ): Promise { const { id, ...searchRequest } = request; - const path = trimEnd(`/internal/search/${strategy || ES_SEARCH_STRATEGY}/${id || ''}`, '/'); - const body = JSON.stringify(searchRequest); + const path = trimEnd( + `/internal/search/${options?.strategy ?? ES_SEARCH_STRATEGY}/${id ?? ''}`, + '/' + ); + const body = JSON.stringify({ + sessionId: options?.sessionId, + isStored: options?.isStored, + isRestore: options?.isRestore, + ...searchRequest, + }); return this.deps.http.fetch({ method: 'POST', path, body, - signal, + signal: options?.abortSignal, }); } @@ -235,7 +242,7 @@ export class SearchInterceptor { abortSignal: options?.abortSignal, }); this.pendingCount$.next(this.pendingCount$.getValue() + 1); - return from(this.runSearch(request, combinedSignal, options?.strategy)).pipe( + return from(this.runSearch(request, { ...options, abortSignal: combinedSignal })).pipe( catchError((e: Error) => { return throwError(this.handleSearchError(e, request, timeoutSignal, options)); }), diff --git a/src/plugins/data/public/search/session_service.ts b/src/plugins/data/public/search/session_service.ts index a17273881293..0141cff258a9 100644 --- a/src/plugins/data/public/search/session_service.ts +++ b/src/plugins/data/public/search/session_service.ts @@ -19,9 +19,13 @@ import uuid from 'uuid'; import { BehaviorSubject, Subscription } from 'rxjs'; -import { PluginInitializerContext, StartServicesAccessor } from 'kibana/public'; +import { HttpStart, PluginInitializerContext, StartServicesAccessor } from 'kibana/public'; import { ConfigSchema } from '../../config'; -import { ISessionService } from '../../common/search'; +import { + ISessionService, + BackgroundSessionSavedObjectAttributes, + SearchSessionFindOptions, +} from '../../common'; export class SessionService implements ISessionService { private session$ = new BehaviorSubject(undefined); @@ -30,6 +34,18 @@ export class SessionService implements ISessionService { } private appChangeSubscription$?: Subscription; private curApp?: string; + private http!: HttpStart; + + /** + * Has the session already been stored (i.e. "sent to background")? + */ + private _isStored: boolean = false; + + /** + * Is this session a restored session (have these requests already been made, and we're just + * looking to re-use the previous search IDs)? + */ + private _isRestore: boolean = false; constructor( initializerContext: PluginInitializerContext, @@ -39,6 +55,8 @@ export class SessionService implements ISessionService { Make sure that apps don't leave sessions open. */ getStartServices().then(([coreStart]) => { + this.http = coreStart.http; + this.appChangeSubscription$ = coreStart.application.currentAppId$.subscribe((appName) => { if (this.sessionId) { const message = `Application '${this.curApp}' had an open session while navigating`; @@ -69,16 +87,63 @@ export class SessionService implements ISessionService { return this.session$.asObservable(); } + public isStored() { + return this._isStored; + } + + public isRestore() { + return this._isRestore; + } + public start() { + this._isStored = false; + this._isRestore = false; this.session$.next(uuid.v4()); return this.sessionId!; } public restore(sessionId: string) { + this._isStored = true; + this._isRestore = true; this.session$.next(sessionId); + return this.http.get(`/internal/session/${encodeURIComponent(sessionId)}`); } public clear() { + this._isStored = false; + this._isRestore = false; this.session$.next(undefined); } + + public async save(name: string, url: string) { + const response = await this.http.post(`/internal/session`, { + body: JSON.stringify({ + name, + url, + sessionId: this.sessionId, + }), + }); + this._isStored = true; + return response; + } + + public get(sessionId: string) { + return this.http.get(`/internal/session/${encodeURIComponent(sessionId)}`); + } + + public find(options: SearchSessionFindOptions) { + return this.http.post(`/internal/session`, { + body: JSON.stringify(options), + }); + } + + public update(sessionId: string, attributes: Partial) { + return this.http.put(`/internal/session/${encodeURIComponent(sessionId)}`, { + body: JSON.stringify(attributes), + }); + } + + public delete(sessionId: string) { + return this.http.delete(`/internal/session/${encodeURIComponent(sessionId)}`); + } } diff --git a/src/plugins/data/server/saved_objects/background_session.ts b/src/plugins/data/server/saved_objects/background_session.ts new file mode 100644 index 000000000000..74b03c4d867e --- /dev/null +++ b/src/plugins/data/server/saved_objects/background_session.ts @@ -0,0 +1,56 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { SavedObjectsType } from 'kibana/server'; + +export const BACKGROUND_SESSION_TYPE = 'background-session'; + +export const backgroundSessionMapping: SavedObjectsType = { + name: BACKGROUND_SESSION_TYPE, + namespaceType: 'single', + hidden: true, + mappings: { + properties: { + name: { + type: 'keyword', + }, + created: { + type: 'date', + }, + expires: { + type: 'date', + }, + status: { + type: 'keyword', + }, + initialState: { + type: 'object', + enabled: false, + }, + restoreState: { + type: 'object', + enabled: false, + }, + idMapping: { + type: 'object', + enabled: false, + }, + }, + }, +}; diff --git a/src/plugins/data/server/saved_objects/index.ts b/src/plugins/data/server/saved_objects/index.ts index 077f9380823d..7cd4d319e641 100644 --- a/src/plugins/data/server/saved_objects/index.ts +++ b/src/plugins/data/server/saved_objects/index.ts @@ -20,3 +20,4 @@ export { querySavedObjectType } from './query'; export { indexPatternSavedObjectType } from './index_patterns'; export { kqlTelemetry } from './kql_telemetry'; export { searchTelemetry } from './search_telemetry'; +export { BACKGROUND_SESSION_TYPE, backgroundSessionMapping } from './background_session'; diff --git a/src/plugins/data/server/search/mocks.ts b/src/plugins/data/server/search/mocks.ts index 4914726c85ef..290e94ee7cf9 100644 --- a/src/plugins/data/server/search/mocks.ts +++ b/src/plugins/data/server/search/mocks.ts @@ -17,6 +17,8 @@ * under the License. */ +import type { RequestHandlerContext } from 'src/core/server'; +import { coreMock } from '../../../../core/server/mocks'; import { ISearchSetup, ISearchStart } from './types'; import { searchAggsSetupMock, searchAggsStartMock } from './aggs/mocks'; import { searchSourceMock } from './search_source/mocks'; @@ -40,3 +42,22 @@ export function createSearchStartMock(): jest.Mocked { searchSource: searchSourceMock.createStartContract(), }; } + +export function createSearchRequestHandlerContext(): jest.Mocked { + return { + core: coreMock.createRequestHandlerContext(), + search: { + search: jest.fn(), + cancel: jest.fn(), + session: { + save: jest.fn(), + get: jest.fn(), + find: jest.fn(), + update: jest.fn(), + delete: jest.fn(), + trackId: jest.fn(), + getId: jest.fn(), + }, + }, + }; +} diff --git a/src/plugins/data/server/search/routes/search.ts b/src/plugins/data/server/search/routes/search.ts index a4161fe47b38..ed519164c8e4 100644 --- a/src/plugins/data/server/search/routes/search.ts +++ b/src/plugins/data/server/search/routes/search.ts @@ -35,11 +35,18 @@ export function registerSearchRoute(router: IRouter): void { query: schema.object({}, { unknowns: 'allow' }), - body: schema.object({}, { unknowns: 'allow' }), + body: schema.object( + { + sessionId: schema.maybe(schema.string()), + isStored: schema.maybe(schema.boolean()), + isRestore: schema.maybe(schema.boolean()), + }, + { unknowns: 'allow' } + ), }, }, async (context, request, res) => { - const searchRequest = request.body; + const { sessionId, isStored, isRestore, ...searchRequest } = request.body; const { strategy, id } = request.params; const abortSignal = getRequestAbortedSignal(request.events.aborted$); @@ -50,6 +57,9 @@ export function registerSearchRoute(router: IRouter): void { { abortSignal, strategy, + sessionId, + isStored, + isRestore, } ) .pipe(first()) diff --git a/src/plugins/data/server/search/routes/session.test.ts b/src/plugins/data/server/search/routes/session.test.ts new file mode 100644 index 000000000000..f697f6d5a5c2 --- /dev/null +++ b/src/plugins/data/server/search/routes/session.test.ts @@ -0,0 +1,119 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import type { MockedKeys } from '@kbn/utility-types/jest'; +import type { CoreSetup, RequestHandlerContext } from 'kibana/server'; +import type { DataPluginStart } from '../../plugin'; +import { coreMock, httpServerMock } from '../../../../../../src/core/server/mocks'; +import { createSearchRequestHandlerContext } from '../mocks'; +import { registerSessionRoutes } from './session'; + +describe('registerSessionRoutes', () => { + let mockCoreSetup: MockedKeys>; + let mockContext: jest.Mocked; + + beforeEach(() => { + mockCoreSetup = coreMock.createSetup(); + mockContext = createSearchRequestHandlerContext(); + registerSessionRoutes(mockCoreSetup.http.createRouter()); + }); + + it('save calls session.save with sessionId and attributes', async () => { + const sessionId = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; + const name = 'my saved background search session'; + const body = { sessionId, name }; + + const mockRequest = httpServerMock.createKibanaRequest({ body }); + const mockResponse = httpServerMock.createResponseFactory(); + + const mockRouter = mockCoreSetup.http.createRouter.mock.results[0].value; + const [[, saveHandler]] = mockRouter.post.mock.calls; + + saveHandler(mockContext, mockRequest, mockResponse); + + expect(mockContext.search!.session.save).toHaveBeenCalledWith(sessionId, { name }); + }); + + it('get calls session.get with sessionId', async () => { + const id = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; + const params = { id }; + + const mockRequest = httpServerMock.createKibanaRequest({ params }); + const mockResponse = httpServerMock.createResponseFactory(); + + const mockRouter = mockCoreSetup.http.createRouter.mock.results[0].value; + const [[, getHandler]] = mockRouter.get.mock.calls; + + getHandler(mockContext, mockRequest, mockResponse); + + expect(mockContext.search!.session.get).toHaveBeenCalledWith(id); + }); + + it('find calls session.find with options', async () => { + const page = 1; + const perPage = 5; + const sortField = 'my_field'; + const sortOrder = 'desc'; + const filter = 'foo: bar'; + const body = { page, perPage, sortField, sortOrder, filter }; + + const mockRequest = httpServerMock.createKibanaRequest({ body }); + const mockResponse = httpServerMock.createResponseFactory(); + + const mockRouter = mockCoreSetup.http.createRouter.mock.results[0].value; + const [, [, findHandler]] = mockRouter.post.mock.calls; + + findHandler(mockContext, mockRequest, mockResponse); + + expect(mockContext.search!.session.find).toHaveBeenCalledWith(body); + }); + + it('update calls session.update with id and attributes', async () => { + const id = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; + const name = 'my saved background search session'; + const expires = new Date().toISOString(); + const params = { id }; + const body = { name, expires }; + + const mockRequest = httpServerMock.createKibanaRequest({ params, body }); + const mockResponse = httpServerMock.createResponseFactory(); + + const mockRouter = mockCoreSetup.http.createRouter.mock.results[0].value; + const [[, updateHandler]] = mockRouter.put.mock.calls; + + updateHandler(mockContext, mockRequest, mockResponse); + + expect(mockContext.search!.session.update).toHaveBeenCalledWith(id, body); + }); + + it('delete calls session.delete with id', async () => { + const id = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; + const params = { id }; + + const mockRequest = httpServerMock.createKibanaRequest({ params }); + const mockResponse = httpServerMock.createResponseFactory(); + + const mockRouter = mockCoreSetup.http.createRouter.mock.results[0].value; + const [[, deleteHandler]] = mockRouter.delete.mock.calls; + + deleteHandler(mockContext, mockRequest, mockResponse); + + expect(mockContext.search!.session.delete).toHaveBeenCalledWith(id); + }); +}); diff --git a/src/plugins/data/server/search/routes/session.ts b/src/plugins/data/server/search/routes/session.ts new file mode 100644 index 000000000000..93f07ecfb92f --- /dev/null +++ b/src/plugins/data/server/search/routes/session.ts @@ -0,0 +1,201 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { schema } from '@kbn/config-schema'; +import { IRouter } from 'src/core/server'; + +export function registerSessionRoutes(router: IRouter): void { + router.post( + { + path: '/internal/session', + validate: { + body: schema.object({ + sessionId: schema.string(), + name: schema.string(), + expires: schema.maybe(schema.string()), + initialState: schema.maybe(schema.object({}, { unknowns: 'allow' })), + restoreState: schema.maybe(schema.object({}, { unknowns: 'allow' })), + }), + }, + }, + async (context, request, res) => { + const { sessionId, name, expires, initialState, restoreState } = request.body; + + try { + const response = await context.search!.session.save(sessionId, { + name, + expires, + initialState, + restoreState, + }); + + return res.ok({ + body: response, + }); + } catch (err) { + return res.customError({ + statusCode: err.statusCode || 500, + body: { + message: err.message, + attributes: { + error: err.body?.error || err.message, + }, + }, + }); + } + } + ); + + router.get( + { + path: '/internal/session/{id}', + validate: { + params: schema.object({ + id: schema.string(), + }), + }, + }, + async (context, request, res) => { + const { id } = request.params; + try { + const response = await context.search!.session.get(id); + + return res.ok({ + body: response, + }); + } catch (err) { + return res.customError({ + statusCode: err.statusCode || 500, + body: { + message: err.message, + attributes: { + error: err.body?.error || err.message, + }, + }, + }); + } + } + ); + + router.post( + { + path: '/internal/session/_find', + validate: { + body: schema.object({ + page: schema.maybe(schema.number()), + perPage: schema.maybe(schema.number()), + sortField: schema.maybe(schema.string()), + sortOrder: schema.maybe(schema.string()), + filter: schema.maybe(schema.string()), + }), + }, + }, + async (context, request, res) => { + const { page, perPage, sortField, sortOrder, filter } = request.body; + try { + const response = await context.search!.session.find({ + page, + perPage, + sortField, + sortOrder, + filter, + }); + + return res.ok({ + body: response, + }); + } catch (err) { + return res.customError({ + statusCode: err.statusCode || 500, + body: { + message: err.message, + attributes: { + error: err.body?.error || err.message, + }, + }, + }); + } + } + ); + + router.delete( + { + path: '/internal/session/{id}', + validate: { + params: schema.object({ + id: schema.string(), + }), + }, + }, + async (context, request, res) => { + const { id } = request.params; + try { + await context.search!.session.delete(id); + + return res.ok(); + } catch (err) { + return res.customError({ + statusCode: err.statusCode || 500, + body: { + message: err.message, + attributes: { + error: err.body?.error || err.message, + }, + }, + }); + } + } + ); + + router.put( + { + path: '/internal/session/{id}', + validate: { + params: schema.object({ + id: schema.string(), + }), + body: schema.object({ + name: schema.maybe(schema.string()), + expires: schema.maybe(schema.string()), + }), + }, + }, + async (context, request, res) => { + const { id } = request.params; + const { name, expires } = request.body; + try { + const response = await context.search!.session.update(id, { name, expires }); + + return res.ok({ + body: response, + }); + } catch (err) { + return res.customError({ + statusCode: err.statusCode || 500, + body: { + message: err.message, + attributes: { + error: err.body?.error || err.message, + }, + }, + }); + } + } + ); +} diff --git a/src/plugins/data/server/search/search_service.ts b/src/plugins/data/server/search/search_service.ts index d8aa588719e3..b44980164d09 100644 --- a/src/plugins/data/server/search/search_service.ts +++ b/src/plugins/data/server/search/search_service.ts @@ -17,7 +17,7 @@ * under the License. */ -import { BehaviorSubject, Observable } from 'rxjs'; +import { BehaviorSubject, from, Observable } from 'rxjs'; import { pick } from 'lodash'; import { CoreSetup, @@ -29,7 +29,7 @@ import { SharedGlobalConfig, StartServicesAccessor, } from 'src/core/server'; -import { first } from 'rxjs/operators'; +import { first, switchMap } from 'rxjs/operators'; import { ExpressionsServerSetup } from 'src/plugins/expressions/server'; import { ISearchSetup, @@ -49,7 +49,7 @@ import { DataPluginStart } from '../plugin'; import { UsageCollectionSetup } from '../../../usage_collection/server'; import { registerUsageCollector } from './collectors/register'; import { usageProvider } from './collectors/usage'; -import { searchTelemetry } from '../saved_objects'; +import { BACKGROUND_SESSION_TYPE, searchTelemetry } from '../saved_objects'; import { IEsSearchRequest, IEsSearchResponse, @@ -70,10 +70,14 @@ import { } from '../../common/search/aggs/buckets/shard_delay'; import { aggShardDelay } from '../../common/search/aggs/buckets/shard_delay_fn'; import { ConfigSchema } from '../../config'; +import { BackgroundSessionService, ISearchSessionClient } from './session'; +import { registerSessionRoutes } from './routes/session'; +import { backgroundSessionMapping } from '../saved_objects'; +import { tapFirst } from '../../common/utils'; declare module 'src/core/server' { interface RequestHandlerContext { - search?: ISearchClient; + search?: ISearchClient & { session: ISearchSessionClient }; } } @@ -102,6 +106,7 @@ export class SearchService implements Plugin { private readonly searchSourceService = new SearchSourceService(); private defaultSearchStrategyName: string = ES_SEARCH_STRATEGY; private searchStrategies: StrategyMap = {}; + private sessionService: BackgroundSessionService = new BackgroundSessionService(); constructor( private initializerContext: PluginInitializerContext, @@ -121,12 +126,17 @@ export class SearchService implements Plugin { }; registerSearchRoute(router); registerMsearchRoute(router, routeDependencies); + registerSessionRoutes(router); core.http.registerRouteHandlerContext('search', async (context, request) => { const [coreStart] = await core.getStartServices(); - return this.asScopedProvider(coreStart)(request); + const search = this.asScopedProvider(coreStart)(request); + const session = this.sessionService.asScopedProvider(coreStart)(request); + return { ...search, session }; }); + core.savedObjects.registerType(backgroundSessionMapping); + this.registerSearchStrategy( ES_SEARCH_STRATEGY, esSearchStrategyProvider( @@ -223,6 +233,7 @@ export class SearchService implements Plugin { public stop() { this.aggsService.stop(); + this.sessionService.stop(); } private registerSearchStrategy = < @@ -248,7 +259,24 @@ export class SearchService implements Plugin { options.strategy ); - return strategy.search(searchRequest, options, deps); + // If this is a restored background search session, look up the ID using the provided sessionId + const getSearchRequest = async () => + !options.isRestore || searchRequest.id + ? searchRequest + : { + ...searchRequest, + id: await this.sessionService.getId(searchRequest, options, deps), + }; + + return from(getSearchRequest()).pipe( + switchMap((request) => strategy.search(request, options, deps)), + tapFirst((response) => { + if (searchRequest.id || !options.sessionId || !response.id || options.isRestore) return; + this.sessionService.trackId(searchRequest, response.id, options, { + savedObjectsClient: deps.savedObjectsClient, + }); + }) + ); }; private cancel = (id: string, options: ISearchOptions, deps: SearchStrategyDependencies) => { @@ -273,7 +301,9 @@ export class SearchService implements Plugin { private asScopedProvider = ({ elasticsearch, savedObjects, uiSettings }: CoreStart) => { return (request: KibanaRequest): ISearchClient => { - const savedObjectsClient = savedObjects.getScopedClient(request); + const savedObjectsClient = savedObjects.getScopedClient(request, { + includedHiddenTypes: [BACKGROUND_SESSION_TYPE], + }); const deps = { savedObjectsClient, esClient: elasticsearch.client.asScoped(request), diff --git a/src/plugins/data/server/search/session/index.ts b/src/plugins/data/server/search/session/index.ts new file mode 100644 index 000000000000..11b5b16a02b5 --- /dev/null +++ b/src/plugins/data/server/search/session/index.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { BackgroundSessionService, ISearchSessionClient } from './session_service'; diff --git a/src/plugins/data/server/search/session/session_service.test.ts b/src/plugins/data/server/search/session/session_service.test.ts new file mode 100644 index 000000000000..1ceebae967d4 --- /dev/null +++ b/src/plugins/data/server/search/session/session_service.test.ts @@ -0,0 +1,233 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import type { SavedObject, SavedObjectsClientContract } from 'kibana/server'; +import { savedObjectsClientMock } from '../../../../../core/server/mocks'; +import { BackgroundSessionStatus } from '../../../common'; +import { BACKGROUND_SESSION_TYPE } from '../../saved_objects'; +import { BackgroundSessionService } from './session_service'; +import { createRequestHash } from './utils'; + +describe('BackgroundSessionService', () => { + let savedObjectsClient: jest.Mocked; + let service: BackgroundSessionService; + + const mockSavedObject: SavedObject = { + id: 'd7170a35-7e2c-48d6-8dec-9a056721b489', + type: BACKGROUND_SESSION_TYPE, + attributes: { + name: 'my_name', + idMapping: {}, + }, + references: [], + }; + + beforeEach(() => { + savedObjectsClient = savedObjectsClientMock.create(); + service = new BackgroundSessionService(); + }); + + it('save throws if `name` is not provided', () => { + const sessionId = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; + + expect(() => service.save(sessionId, {}, { savedObjectsClient })).rejects.toMatchInlineSnapshot( + `[Error: Name is required]` + ); + }); + + it('get calls saved objects client', async () => { + savedObjectsClient.get.mockResolvedValue(mockSavedObject); + + const sessionId = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; + const response = await service.get(sessionId, { savedObjectsClient }); + + expect(response).toBe(mockSavedObject); + expect(savedObjectsClient.get).toHaveBeenCalledWith(BACKGROUND_SESSION_TYPE, sessionId); + }); + + it('find calls saved objects client', async () => { + const mockFindSavedObject = { + ...mockSavedObject, + score: 1, + }; + const mockResponse = { + saved_objects: [mockFindSavedObject], + total: 1, + per_page: 1, + page: 0, + }; + savedObjectsClient.find.mockResolvedValue(mockResponse); + + const options = { page: 0, perPage: 5 }; + const response = await service.find(options, { savedObjectsClient }); + + expect(response).toBe(mockResponse); + expect(savedObjectsClient.find).toHaveBeenCalledWith({ + ...options, + type: BACKGROUND_SESSION_TYPE, + }); + }); + + it('update calls saved objects client', async () => { + const mockUpdateSavedObject = { + ...mockSavedObject, + attributes: {}, + }; + savedObjectsClient.update.mockResolvedValue(mockUpdateSavedObject); + + const sessionId = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; + const attributes = { name: 'new_name' }; + const response = await service.update(sessionId, attributes, { savedObjectsClient }); + + expect(response).toBe(mockUpdateSavedObject); + expect(savedObjectsClient.update).toHaveBeenCalledWith( + BACKGROUND_SESSION_TYPE, + sessionId, + attributes + ); + }); + + it('delete calls saved objects client', async () => { + savedObjectsClient.delete.mockResolvedValue({}); + + const sessionId = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; + const response = await service.delete(sessionId, { savedObjectsClient }); + + expect(response).toEqual({}); + expect(savedObjectsClient.delete).toHaveBeenCalledWith(BACKGROUND_SESSION_TYPE, sessionId); + }); + + describe('trackId', () => { + it('stores hash in memory when `isStored` is `false` for when `save` is called', async () => { + const searchRequest = { params: {} }; + const requestHash = createRequestHash(searchRequest.params); + const searchId = 'FnpFYlBpeXdCUTMyZXhCLTc1TWFKX0EbdDFDTzJzTE1Sck9PVTBIcW1iU05CZzo4MDA0'; + const sessionId = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; + const isStored = false; + const name = 'my saved background search session'; + const created = new Date().toISOString(); + const expires = new Date().toISOString(); + + await service.trackId( + searchRequest, + searchId, + { sessionId, isStored }, + { savedObjectsClient } + ); + + expect(savedObjectsClient.update).not.toHaveBeenCalled(); + + await service.save(sessionId, { name, created, expires }, { savedObjectsClient }); + + expect(savedObjectsClient.create).toHaveBeenCalledWith( + BACKGROUND_SESSION_TYPE, + { + name, + created, + expires, + initialState: {}, + restoreState: {}, + status: BackgroundSessionStatus.IN_PROGRESS, + idMapping: { [requestHash]: searchId }, + }, + { id: sessionId } + ); + }); + + it('updates saved object when `isStored` is `true`', async () => { + const searchRequest = { params: {} }; + const requestHash = createRequestHash(searchRequest.params); + const searchId = 'FnpFYlBpeXdCUTMyZXhCLTc1TWFKX0EbdDFDTzJzTE1Sck9PVTBIcW1iU05CZzo4MDA0'; + const sessionId = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; + const isStored = true; + + await service.trackId( + searchRequest, + searchId, + { sessionId, isStored }, + { savedObjectsClient } + ); + + expect(savedObjectsClient.update).toHaveBeenCalledWith(BACKGROUND_SESSION_TYPE, sessionId, { + idMapping: { [requestHash]: searchId }, + }); + }); + }); + + describe('getId', () => { + it('throws if `sessionId` is not provided', () => { + const searchRequest = { params: {} }; + + expect(() => + service.getId(searchRequest, {}, { savedObjectsClient }) + ).rejects.toMatchInlineSnapshot(`[Error: Session ID is required]`); + }); + + it('throws if there is not a saved object', () => { + const searchRequest = { params: {} }; + const sessionId = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; + + expect(() => + service.getId(searchRequest, { sessionId, isStored: false }, { savedObjectsClient }) + ).rejects.toMatchInlineSnapshot( + `[Error: Cannot get search ID from a session that is not stored]` + ); + }); + + it('throws if not restoring a saved session', () => { + const searchRequest = { params: {} }; + const sessionId = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; + + expect(() => + service.getId( + searchRequest, + { sessionId, isStored: true, isRestore: false }, + { savedObjectsClient } + ) + ).rejects.toMatchInlineSnapshot( + `[Error: Get search ID is only supported when restoring a session]` + ); + }); + + it('returns the search ID from the saved object ID mapping', async () => { + const searchRequest = { params: {} }; + const requestHash = createRequestHash(searchRequest.params); + const searchId = 'FnpFYlBpeXdCUTMyZXhCLTc1TWFKX0EbdDFDTzJzTE1Sck9PVTBIcW1iU05CZzo4MDA0'; + const sessionId = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; + const mockSession = { + id: 'd7170a35-7e2c-48d6-8dec-9a056721b489', + type: BACKGROUND_SESSION_TYPE, + attributes: { + name: 'my_name', + idMapping: { [requestHash]: searchId }, + }, + references: [], + }; + savedObjectsClient.get.mockResolvedValue(mockSession); + + const id = await service.getId( + searchRequest, + { sessionId, isStored: true, isRestore: true }, + { savedObjectsClient } + ); + + expect(id).toBe(searchId); + }); + }); +}); diff --git a/src/plugins/data/server/search/session/session_service.ts b/src/plugins/data/server/search/session/session_service.ts new file mode 100644 index 000000000000..eca5f428b855 --- /dev/null +++ b/src/plugins/data/server/search/session/session_service.ts @@ -0,0 +1,204 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { CoreStart, KibanaRequest, SavedObjectsClientContract } from 'kibana/server'; +import { + BackgroundSessionSavedObjectAttributes, + IKibanaSearchRequest, + ISearchOptions, + SearchSessionFindOptions, + BackgroundSessionStatus, +} from '../../../common'; +import { BACKGROUND_SESSION_TYPE } from '../../saved_objects'; +import { createRequestHash } from './utils'; + +const DEFAULT_EXPIRATION = 7 * 24 * 60 * 60 * 1000; + +export interface BackgroundSessionDependencies { + savedObjectsClient: SavedObjectsClientContract; +} + +export type ISearchSessionClient = ReturnType< + ReturnType +>; + +export class BackgroundSessionService { + /** + * Map of sessionId to { [requestHash]: searchId } + * @private + */ + private sessionSearchMap = new Map>(); + + constructor() {} + + public setup = () => {}; + + public start = (core: CoreStart) => { + return { + asScoped: this.asScopedProvider(core), + }; + }; + + public stop = () => { + this.sessionSearchMap.clear(); + }; + + // TODO: Generate the `userId` from the realm type/realm name/username + public save = async ( + sessionId: string, + { + name, + created = new Date().toISOString(), + expires = new Date(Date.now() + DEFAULT_EXPIRATION).toISOString(), + status = BackgroundSessionStatus.IN_PROGRESS, + initialState = {}, + restoreState = {}, + }: Partial, + { savedObjectsClient }: BackgroundSessionDependencies + ) => { + if (!name) throw new Error('Name is required'); + + // Get the mapping of request hash/search ID for this session + const searchMap = this.sessionSearchMap.get(sessionId) ?? new Map(); + const idMapping = Object.fromEntries(searchMap.entries()); + const attributes = { name, created, expires, status, initialState, restoreState, idMapping }; + const session = await savedObjectsClient.create( + BACKGROUND_SESSION_TYPE, + attributes, + { id: sessionId } + ); + + // Clear out the entries for this session ID so they don't get saved next time + this.sessionSearchMap.delete(sessionId); + + return session; + }; + + // TODO: Throw an error if this session doesn't belong to this user + public get = (sessionId: string, { savedObjectsClient }: BackgroundSessionDependencies) => { + return savedObjectsClient.get( + BACKGROUND_SESSION_TYPE, + sessionId + ); + }; + + // TODO: Throw an error if this session doesn't belong to this user + public find = ( + options: SearchSessionFindOptions, + { savedObjectsClient }: BackgroundSessionDependencies + ) => { + return savedObjectsClient.find({ + ...options, + type: BACKGROUND_SESSION_TYPE, + }); + }; + + // TODO: Throw an error if this session doesn't belong to this user + public update = ( + sessionId: string, + attributes: Partial, + { savedObjectsClient }: BackgroundSessionDependencies + ) => { + return savedObjectsClient.update( + BACKGROUND_SESSION_TYPE, + sessionId, + attributes + ); + }; + + // TODO: Throw an error if this session doesn't belong to this user + public delete = (sessionId: string, { savedObjectsClient }: BackgroundSessionDependencies) => { + return savedObjectsClient.delete(BACKGROUND_SESSION_TYPE, sessionId); + }; + + /** + * Tracks the given search request/search ID in the saved session (if it exists). Otherwise, just + * store it in memory until a saved session exists. + * @internal + */ + public trackId = async ( + searchRequest: IKibanaSearchRequest, + searchId: string, + { sessionId, isStored }: ISearchOptions, + deps: BackgroundSessionDependencies + ) => { + if (!sessionId || !searchId) return; + const requestHash = createRequestHash(searchRequest.params); + + // If there is already a saved object for this session, update it to include this request/ID. + // Otherwise, just update the in-memory mapping for this session for when the session is saved. + if (isStored) { + const attributes = { idMapping: { [requestHash]: searchId } }; + await this.update(sessionId, attributes, deps); + } else { + const map = this.sessionSearchMap.get(sessionId) ?? new Map(); + map.set(requestHash, searchId); + this.sessionSearchMap.set(sessionId, map); + } + }; + + /** + * Look up an existing search ID that matches the given request in the given session so that the + * request can continue rather than restart. + * @internal + */ + public getId = async ( + searchRequest: IKibanaSearchRequest, + { sessionId, isStored, isRestore }: ISearchOptions, + deps: BackgroundSessionDependencies + ) => { + if (!sessionId) { + throw new Error('Session ID is required'); + } else if (!isStored) { + throw new Error('Cannot get search ID from a session that is not stored'); + } else if (!isRestore) { + throw new Error('Get search ID is only supported when restoring a session'); + } + + const session = await this.get(sessionId, deps); + const requestHash = createRequestHash(searchRequest.params); + if (!session.attributes.idMapping.hasOwnProperty(requestHash)) { + throw new Error('No search ID in this session matching the given search request'); + } + + return session.attributes.idMapping[requestHash]; + }; + + public asScopedProvider = ({ savedObjects }: CoreStart) => { + return (request: KibanaRequest) => { + const savedObjectsClient = savedObjects.getScopedClient(request, { + includedHiddenTypes: [BACKGROUND_SESSION_TYPE], + }); + const deps = { savedObjectsClient }; + return { + save: (sessionId: string, attributes: Partial) => + this.save(sessionId, attributes, deps), + get: (sessionId: string) => this.get(sessionId, deps), + find: (options: SearchSessionFindOptions) => this.find(options, deps), + update: (sessionId: string, attributes: Partial) => + this.update(sessionId, attributes, deps), + delete: (sessionId: string) => this.delete(sessionId, deps), + trackId: (searchRequest: IKibanaSearchRequest, searchId: string, options: ISearchOptions) => + this.trackId(searchRequest, searchId, options, deps), + getId: (searchRequest: IKibanaSearchRequest, options: ISearchOptions) => + this.getId(searchRequest, options, deps), + }; + }; + }; +} diff --git a/src/plugins/data/server/search/session/utils.test.ts b/src/plugins/data/server/search/session/utils.test.ts new file mode 100644 index 000000000000..d190f892a7f8 --- /dev/null +++ b/src/plugins/data/server/search/session/utils.test.ts @@ -0,0 +1,37 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { createRequestHash } from './utils'; + +describe('data/search/session utils', () => { + describe('createRequestHash', () => { + it('ignores `preference`', () => { + const request = { + foo: 'bar', + }; + + const withPreference = { + ...request, + preference: 1234, + }; + + expect(createRequestHash(request)).toEqual(createRequestHash(withPreference)); + }); + }); +}); diff --git a/src/plugins/data/server/search/session/utils.ts b/src/plugins/data/server/search/session/utils.ts new file mode 100644 index 000000000000..c3332f80b6e3 --- /dev/null +++ b/src/plugins/data/server/search/session/utils.ts @@ -0,0 +1,30 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { createHash } from 'crypto'; + +/** + * Generate the hash for this request so that, in the future, this hash can be used to look up + * existing search IDs for this request. Ignores the `preference` parameter since it generally won't + * match from one request to another identical request. + */ +export function createRequestHash(keys: Record) { + const { preference, ...params } = keys; + return createHash(`sha256`).update(JSON.stringify(params)).digest('hex'); +} diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index ce66610edf88..8d1699c4ad5e 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -753,6 +753,8 @@ export class IndexPatternsService implements Plugin_3( - () => this.runSearch(request, combinedSignal, strategy), - (requestId) => this.runSearch({ ...request, id: requestId }, combinedSignal, strategy), + () => this.runSearch(request, { ...options, strategy, abortSignal: combinedSignal }), + (requestId) => + this.runSearch( + { ...request, id: requestId }, + { ...options, strategy, abortSignal: combinedSignal } + ), (r) => !r.isRunning, (response) => response.id, id, diff --git a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts index 53bcac02cb01..2070610ceb20 100644 --- a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts +++ b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts @@ -57,6 +57,7 @@ export const enhancedEsSearchStrategyProvider = ( utils.toSnakeCase({ ...(await getDefaultSearchParams(uiSettingsClient)), batchedReduceSize: 64, + keepOnCompletion: !!options.sessionId, // Always return an ID, even if the request completes quickly ...asyncOptions, ...request.params, }) From f2ad337fefc434cdd420e098c4d94a3a944f38b3 Mon Sep 17 00:00:00 2001 From: Robert Oskamp Date: Thu, 19 Nov 2020 07:05:10 +0100 Subject: [PATCH 33/49] Increase bulk request timeout during esArchiver load (#83657) This PR fixes some timeouts during esArchive load by increasing the request timeout. --- .../kbn-es-archiver/src/lib/docs/index_doc_records_stream.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kbn-es-archiver/src/lib/docs/index_doc_records_stream.ts b/packages/kbn-es-archiver/src/lib/docs/index_doc_records_stream.ts index 28790176af73..46c46ad5d1b6 100644 --- a/packages/kbn-es-archiver/src/lib/docs/index_doc_records_stream.ts +++ b/packages/kbn-es-archiver/src/lib/docs/index_doc_records_stream.ts @@ -44,7 +44,7 @@ export function createIndexDocRecordsStream( ); }); - const resp = await client.bulk({ body }); + const resp = await client.bulk({ requestTimeout: 2 * 60 * 1000, body }); if (resp.errors) { throw new Error(`Failed to index all documents: ${JSON.stringify(resp, null, 2)}`); } From 8d9e383980ddb9ff19338d6d748ed9dc38562e06 Mon Sep 17 00:00:00 2001 From: Tyler Smalley Date: Wed, 18 Nov 2020 22:29:11 -0800 Subject: [PATCH 34/49] Skip failing cypress test Signed-off-by: Tyler Smalley --- .../cypress/integration/alerts_detection_rules_custom.spec.ts | 3 ++- .../cypress/integration/alerts_detection_rules_export.spec.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts index fb1f2920aace..d14e09d9384a 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts @@ -114,7 +114,8 @@ const expectedEditedtags = editedRule.tags.join(''); const expectedEditedIndexPatterns = editedRule.index && editedRule.index.length ? editedRule.index : indexPatterns; -describe('Custom detection rules creation', () => { +// SKIP: https://github.com/elastic/kibana/issues/83769 +describe.skip('Custom detection rules creation', () => { before(() => { esArchiverLoad('timeline'); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_export.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_export.spec.ts index eb8448233c62..6f995045dfc6 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_export.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_export.spec.ts @@ -17,7 +17,8 @@ import { DETECTIONS_URL } from '../urls/navigation'; const EXPECTED_EXPORTED_RULE_FILE_PATH = 'cypress/test_files/expected_rules_export.ndjson'; -describe('Export rules', () => { +// SKIP: https://github.com/elastic/kibana/issues/83769 +describe.skip('Export rules', () => { before(() => { esArchiverLoad('export_rule'); cy.server(); From 3a8ea2993f6c7bfdcbf52617ffc0601f49d69879 Mon Sep 17 00:00:00 2001 From: Dario Gieselaar Date: Thu, 19 Nov 2020 08:38:51 +0100 Subject: [PATCH 35/49] Make expectSnapshot available in all functional test runs (#82932) Co-authored-by: spalger --- .../src/functional_test_runner/cli.ts | 16 ++- .../fake_mocha_types.d.ts | 7 + .../lib/config/schema.ts | 2 +- .../lib/mocha/load_test_files.js | 13 +- .../lib/mocha/setup_mocha.js | 1 + .../snapshots/decorate_snapshot_ui.test.ts | 133 ++++++++++++++++++ .../lib/snapshots/decorate_snapshot_ui.ts | 99 +++++++------ .../run_tests/__snapshots__/args.test.js.snap | 23 +++ .../run_tests/__snapshots__/cli.test.js.snap | 2 + .../functional_tests/cli/run_tests/args.js | 6 + .../cli/run_tests/args.test.js | 5 + .../cli/run_tests/cli.test.js | 16 +++ .../__snapshots__/cli.test.js.snap | 9 +- .../cli/start_servers/cli.test.js | 9 ++ .../src/functional_tests/lib/run_ftr.js | 3 +- packages/kbn-test/tsconfig.json | 3 + .../kbn-test/types/ftr_globals/mocha.d.ts | 18 +-- .../kbn-test/types/ftr_globals/snapshots.d.ts | 25 ++++ test/tsconfig.json | 2 +- .../api_integration/apis/uptime/rest/index.ts | 3 - .../uptime/rest/monitor_states_real_data.ts | 1 - .../basic/tests/correlations/ranges.ts | 1 - .../tests/correlations/slow_durations.ts | 1 - .../apm_api_integration/basic/tests/index.ts | 3 - .../tests/metrics_charts/metrics_charts.ts | 1 - .../observability_overview.ts | 1 - .../basic/tests/service_maps/service_maps.ts | 1 - .../tests/service_overview/error_groups.ts | 1 - .../basic/tests/services/top_services.ts | 1 - .../basic/tests/services/transaction_types.ts | 1 - .../tests/settings/agent_configuration.ts | 1 - .../settings/anomaly_detection/read_user.ts | 1 - .../settings/anomaly_detection/write_user.ts | 1 - .../basic/tests/settings/custom_link.ts | 1 - .../basic/tests/traces/top_traces.ts | 1 - .../tests/transaction_groups/breakdown.ts | 1 - .../tests/transaction_groups/distribution.ts | 1 - .../tests/transaction_groups/error_rate.ts | 1 - .../top_transaction_groups.ts | 1 - .../transaction_groups/transaction_charts.ts | 1 - .../trial/tests/csm/csm_services.ts | 1 - .../trial/tests/csm/has_rum_data.ts | 1 - .../trial/tests/csm/js_errors.ts | 1 - .../trial/tests/csm/long_task_metrics.ts | 1 - .../trial/tests/csm/page_views.ts | 1 - .../trial/tests/csm/url_search.ts | 1 - .../trial/tests/csm/web_core_vitals.ts | 1 - .../apm_api_integration/trial/tests/index.ts | 3 - .../trial/tests/service_maps/service_maps.ts | 1 - .../trial/tests/services/top_services.ts | 1 - .../services/transaction_groups_charts.ts | 1 - x-pack/test/mocha_decorations.d.ts | 17 --- x-pack/test/tsconfig.json | 2 +- 53 files changed, 329 insertions(+), 120 deletions(-) create mode 100644 packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.test.ts rename x-pack/test/apm_api_integration/common/match_snapshot.ts => packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.ts (66%) rename test/mocha_decorations.d.ts => packages/kbn-test/types/ftr_globals/mocha.d.ts (73%) create mode 100644 packages/kbn-test/types/ftr_globals/snapshots.d.ts delete mode 100644 x-pack/test/mocha_decorations.d.ts diff --git a/packages/kbn-test/src/functional_test_runner/cli.ts b/packages/kbn-test/src/functional_test_runner/cli.ts index d744be946731..8f53d6f7cf58 100644 --- a/packages/kbn-test/src/functional_test_runner/cli.ts +++ b/packages/kbn-test/src/functional_test_runner/cli.ts @@ -60,7 +60,8 @@ export function runFtrCli() { include: toArray(flags['include-tag'] as string | string[]), exclude: toArray(flags['exclude-tag'] as string | string[]), }, - updateBaselines: flags.updateBaselines, + updateBaselines: flags.updateBaselines || flags.u, + updateSnapshots: flags.updateSnapshots || flags.u, } ); @@ -126,7 +127,16 @@ export function runFtrCli() { 'exclude-tag', 'kibana-install-dir', ], - boolean: ['bail', 'invert', 'test-stats', 'updateBaselines', 'throttle', 'headless'], + boolean: [ + 'bail', + 'invert', + 'test-stats', + 'updateBaselines', + 'updateSnapshots', + 'u', + 'throttle', + 'headless', + ], default: { config: 'test/functional/config.js', }, @@ -141,6 +151,8 @@ export function runFtrCli() { --exclude-tag=tag a tag to be excluded, pass multiple times for multiple tags --test-stats print the number of tests (included and excluded) to STDERR --updateBaselines replace baseline screenshots with whatever is generated from the test + --updateSnapshots replace inline and file snapshots with whatever is generated from the test + -u replace both baseline screenshots and snapshots --kibana-install-dir directory where the Kibana install being tested resides --throttle enable network throttling in Chrome browser --headless run browser in headless mode diff --git a/packages/kbn-test/src/functional_test_runner/fake_mocha_types.d.ts b/packages/kbn-test/src/functional_test_runner/fake_mocha_types.d.ts index 12390a95a496..35b4b85e4d22 100644 --- a/packages/kbn-test/src/functional_test_runner/fake_mocha_types.d.ts +++ b/packages/kbn-test/src/functional_test_runner/fake_mocha_types.d.ts @@ -28,10 +28,17 @@ import EventEmitter from 'events'; export interface Suite { suites: Suite[]; tests: Test[]; + title: string; + file?: string; + parent?: Suite; } export interface Test { fullTitle(): string; + title: string; + file?: string; + parent?: Suite; + isPassed: () => boolean; } export interface Runner extends EventEmitter { diff --git a/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts b/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts index 6ed114d62e24..6f1519c079be 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts @@ -138,7 +138,7 @@ export const schema = Joi.object() .default(), updateBaselines: Joi.boolean().default(false), - + updateSnapshots: Joi.boolean().default(false), browser: Joi.object() .keys({ type: Joi.string().valid('chrome', 'firefox', 'msedge').default('chrome'), diff --git a/packages/kbn-test/src/functional_test_runner/lib/mocha/load_test_files.js b/packages/kbn-test/src/functional_test_runner/lib/mocha/load_test_files.js index 5c23be636186..0f5f3df6bd41 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/mocha/load_test_files.js +++ b/packages/kbn-test/src/functional_test_runner/lib/mocha/load_test_files.js @@ -21,6 +21,7 @@ import { isAbsolute } from 'path'; import { loadTracer } from '../load_tracer'; import { decorateMochaUi } from './decorate_mocha_ui'; +import { decorateSnapshotUi } from '../snapshots/decorate_snapshot_ui'; /** * Load an array of test files into a mocha instance @@ -31,7 +32,17 @@ import { decorateMochaUi } from './decorate_mocha_ui'; * @param {String} path * @return {undefined} - mutates mocha, no return value */ -export const loadTestFiles = ({ mocha, log, lifecycle, providers, paths, updateBaselines }) => { +export const loadTestFiles = ({ + mocha, + log, + lifecycle, + providers, + paths, + updateBaselines, + updateSnapshots, +}) => { + decorateSnapshotUi(lifecycle, updateSnapshots); + const innerLoadTestFile = (path) => { if (typeof path !== 'string' || !isAbsolute(path)) { throw new TypeError('loadTestFile() only accepts absolute paths'); diff --git a/packages/kbn-test/src/functional_test_runner/lib/mocha/setup_mocha.js b/packages/kbn-test/src/functional_test_runner/lib/mocha/setup_mocha.js index 39eb69a15191..66b93ec001ac 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/mocha/setup_mocha.js +++ b/packages/kbn-test/src/functional_test_runner/lib/mocha/setup_mocha.js @@ -53,6 +53,7 @@ export async function setupMocha(lifecycle, log, config, providers) { providers, paths: config.get('testFiles'), updateBaselines: config.get('updateBaselines'), + updateSnapshots: config.get('updateSnapshots'), }); // Each suite has a tag that is the path relative to the root of the repo diff --git a/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.test.ts b/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.test.ts new file mode 100644 index 000000000000..abfbd8acea78 --- /dev/null +++ b/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.test.ts @@ -0,0 +1,133 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Test } from '../../fake_mocha_types'; +import { Lifecycle } from '../lifecycle'; +import { decorateSnapshotUi, expectSnapshot } from './decorate_snapshot_ui'; +import path from 'path'; +import fs from 'fs'; + +describe('decorateSnapshotUi', () => { + describe('when running a test', () => { + let lifecycle: Lifecycle; + beforeEach(() => { + lifecycle = new Lifecycle(); + decorateSnapshotUi(lifecycle, false); + }); + + it('passes when the snapshot matches the actual value', async () => { + const test: Test = { + title: 'Test', + file: 'foo.ts', + parent: { + file: 'foo.ts', + tests: [], + suites: [], + }, + } as any; + + await lifecycle.beforeEachTest.trigger(test); + + expect(() => { + expectSnapshot('foo').toMatchInline(`"foo"`); + }).not.toThrow(); + }); + + it('throws when the snapshot does not match the actual value', async () => { + const test: Test = { + title: 'Test', + file: 'foo.ts', + parent: { + file: 'foo.ts', + tests: [], + suites: [], + }, + } as any; + + await lifecycle.beforeEachTest.trigger(test); + + expect(() => { + expectSnapshot('foo').toMatchInline(`"bar"`); + }).toThrow(); + }); + + it('writes a snapshot to an external file if it does not exist', async () => { + const test: Test = { + title: 'Test', + file: __filename, + isPassed: () => true, + } as any; + + // @ts-expect-error + test.parent = { + file: __filename, + tests: [test], + suites: [], + }; + + await lifecycle.beforeEachTest.trigger(test); + + const snapshotFile = path.resolve( + __dirname, + '__snapshots__', + 'decorate_snapshot_ui.test.snap' + ); + + expect(fs.existsSync(snapshotFile)).toBe(false); + + expect(() => { + expectSnapshot('foo').toMatch(); + }).not.toThrow(); + + await lifecycle.afterTestSuite.trigger(test.parent); + + expect(fs.existsSync(snapshotFile)).toBe(true); + + fs.unlinkSync(snapshotFile); + + fs.rmdirSync(path.resolve(__dirname, '__snapshots__')); + }); + }); + + describe('when updating snapshots', () => { + let lifecycle: Lifecycle; + beforeEach(() => { + lifecycle = new Lifecycle(); + decorateSnapshotUi(lifecycle, true); + }); + + it("doesn't throw if the value does not match", async () => { + const test: Test = { + title: 'Test', + file: 'foo.ts', + parent: { + file: 'foo.ts', + tests: [], + suites: [], + }, + } as any; + + await lifecycle.beforeEachTest.trigger(test); + + expect(() => { + expectSnapshot('bar').toMatchInline(`"foo"`); + }).not.toThrow(); + }); + }); +}); diff --git a/x-pack/test/apm_api_integration/common/match_snapshot.ts b/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.ts similarity index 66% rename from x-pack/test/apm_api_integration/common/match_snapshot.ts rename to packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.ts index 567a1ced360f..45550b55e73c 100644 --- a/x-pack/test/apm_api_integration/common/match_snapshot.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.ts @@ -1,7 +1,20 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ import { @@ -14,8 +27,9 @@ import path from 'path'; import expect from '@kbn/expect'; import prettier from 'prettier'; import babelTraverse from '@babel/traverse'; -import { Suite, Test } from 'mocha'; -import { flatten } from 'lodash'; +import { flatten, once } from 'lodash'; +import { Lifecycle } from '../lifecycle'; +import { Test, Suite } from '../../fake_mocha_types'; type ISnapshotState = InstanceType; @@ -59,12 +73,38 @@ function getSnapshotMeta(currentTest: Test) { }; } -export function registerMochaHooksForSnapshots() { +const modifyStackTracePrepareOnce = once(() => { + const originalPrepareStackTrace = Error.prepareStackTrace; + + // jest-snapshot uses a stack trace to determine which file/line/column + // an inline snapshot should be written to. We filter out match_snapshot + // from the stack trace to prevent it from wanting to write to this file. + + Error.prepareStackTrace = (error, structuredStackTrace) => { + let filteredStrackTrace: NodeJS.CallSite[] = structuredStackTrace; + if (registered) { + filteredStrackTrace = filteredStrackTrace.filter((callSite) => { + // check for both compiled and uncompiled files + return !callSite.getFileName()?.match(/decorate_snapshot_ui\.(js|ts)/); + }); + } + + if (originalPrepareStackTrace) { + return originalPrepareStackTrace(error, filteredStrackTrace); + } + }; +}); + +export function decorateSnapshotUi(lifecycle: Lifecycle, updateSnapshots: boolean) { let snapshotStatesByFilePath: Record< string, { snapshotState: ISnapshotState; testsInFile: Test[] } > = {}; + registered = true; + + modifyStackTracePrepareOnce(); + addSerializer({ serialize: (num: number) => { return String(parseFloat(num.toPrecision(15))); @@ -74,15 +114,14 @@ export function registerMochaHooksForSnapshots() { }, }); - registered = true; - - beforeEach(function () { - const currentTest = this.currentTest!; + // @ts-expect-error + global.expectSnapshot = expectSnapshot; + lifecycle.beforeEachTest.add((currentTest: Test) => { const { file, snapshotTitle } = getSnapshotMeta(currentTest); if (!snapshotStatesByFilePath[file]) { - snapshotStatesByFilePath[file] = getSnapshotState(file, currentTest); + snapshotStatesByFilePath[file] = getSnapshotState(file, currentTest, updateSnapshots); } testContext = { @@ -95,17 +134,14 @@ export function registerMochaHooksForSnapshots() { }; }); - afterEach(function () { - testContext = null; - }); - - after(function () { - // save snapshot after tests complete + lifecycle.afterTestSuite.add(function (testSuite) { + // save snapshot & check unused after top-level test suite completes + if (testSuite.parent?.parent) { + return; + } const unused: string[] = []; - const isUpdatingSnapshots = process.env.UPDATE_SNAPSHOTS; - Object.keys(snapshotStatesByFilePath).forEach((file) => { const { snapshotState, testsInFile } = snapshotStatesByFilePath[file]; @@ -118,7 +154,7 @@ export function registerMochaHooksForSnapshots() { } }); - if (!isUpdatingSnapshots) { + if (!updateSnapshots) { unused.push(...snapshotState.getUncheckedKeys()); } else { snapshotState.removeUncheckedKeys(); @@ -131,36 +167,19 @@ export function registerMochaHooksForSnapshots() { throw new Error( `${unused.length} obsolete snapshot(s) found:\n${unused.join( '\n\t' - )}.\n\nRun tests again with \`UPDATE_SNAPSHOTS=1\` to remove them.` + )}.\n\nRun tests again with \`--updateSnapshots\` to remove them.` ); } snapshotStatesByFilePath = {}; - - registered = false; }); } -const originalPrepareStackTrace = Error.prepareStackTrace; - -// jest-snapshot uses a stack trace to determine which file/line/column -// an inline snapshot should be written to. We filter out match_snapshot -// from the stack trace to prevent it from wanting to write to this file. - -Error.prepareStackTrace = (error, structuredStackTrace) => { - const filteredStrackTrace = structuredStackTrace.filter((callSite) => { - return !callSite.getFileName()?.endsWith('match_snapshot.ts'); - }); - if (originalPrepareStackTrace) { - return originalPrepareStackTrace(error, filteredStrackTrace); - } -}; - function recursivelyGetTestsFromSuite(suite: Suite): Test[] { return suite.tests.concat(flatten(suite.suites.map((s) => recursivelyGetTestsFromSuite(s)))); } -function getSnapshotState(file: string, test: Test) { +function getSnapshotState(file: string, test: Test, updateSnapshots: boolean) { const dirname = path.dirname(file); const filename = path.basename(file); @@ -177,7 +196,7 @@ function getSnapshotState(file: string, test: Test) { const snapshotState = new SnapshotState( path.join(dirname + `/__snapshots__/` + filename.replace(path.extname(filename), '.snap')), { - updateSnapshot: process.env.UPDATE_SNAPSHOTS ? 'all' : 'new', + updateSnapshot: updateSnapshots ? 'all' : 'new', getPrettier: () => prettier, getBabelTraverse: () => babelTraverse, } diff --git a/packages/kbn-test/src/functional_tests/cli/run_tests/__snapshots__/args.test.js.snap b/packages/kbn-test/src/functional_tests/cli/run_tests/__snapshots__/args.test.js.snap index 434c374d5d23..ad2f82de87b8 100644 --- a/packages/kbn-test/src/functional_tests/cli/run_tests/__snapshots__/args.test.js.snap +++ b/packages/kbn-test/src/functional_tests/cli/run_tests/__snapshots__/args.test.js.snap @@ -16,6 +16,8 @@ Options: --bail Stop the test run at the first failure. --grep Pattern to select which tests to run. --updateBaselines Replace baseline screenshots with whatever is generated from the test. + --updateSnapshots Replace inline and file snapshots with whatever is generated from the test. + --u Replace both baseline screenshots and snapshots --include Files that must included to be run, can be included multiple times. --exclude Files that must NOT be included to be run, can be included multiple times. --include-tag Tags that suites must include to be run, can be included multiple times. @@ -48,6 +50,27 @@ Object { } `; +exports[`process options for run tests CLI accepts boolean value for updateSnapshots 1`] = ` +Object { + "assertNoneExcluded": false, + "configs": Array [ + /foo, + ], + "createLogger": [Function], + "esFrom": "snapshot", + "extraKbnOpts": undefined, + "suiteFiles": Object { + "exclude": Array [], + "include": Array [], + }, + "suiteTags": Object { + "exclude": Array [], + "include": Array [], + }, + "updateSnapshots": true, +} +`; + exports[`process options for run tests CLI accepts debug option 1`] = ` Object { "assertNoneExcluded": false, diff --git a/packages/kbn-test/src/functional_tests/cli/run_tests/__snapshots__/cli.test.js.snap b/packages/kbn-test/src/functional_tests/cli/run_tests/__snapshots__/cli.test.js.snap index 6ede71a6c394..02d11b0033d5 100644 --- a/packages/kbn-test/src/functional_tests/cli/run_tests/__snapshots__/cli.test.js.snap +++ b/packages/kbn-test/src/functional_tests/cli/run_tests/__snapshots__/cli.test.js.snap @@ -16,6 +16,8 @@ Options: --bail Stop the test run at the first failure. --grep Pattern to select which tests to run. --updateBaselines Replace baseline screenshots with whatever is generated from the test. + --updateSnapshots Replace inline and file snapshots with whatever is generated from the test. + --u Replace both baseline screenshots and snapshots --include Files that must included to be run, can be included multiple times. --exclude Files that must NOT be included to be run, can be included multiple times. --include-tag Tags that suites must include to be run, can be included multiple times. diff --git a/packages/kbn-test/src/functional_tests/cli/run_tests/args.js b/packages/kbn-test/src/functional_tests/cli/run_tests/args.js index 94d510915d8e..5af0fe7d7b61 100644 --- a/packages/kbn-test/src/functional_tests/cli/run_tests/args.js +++ b/packages/kbn-test/src/functional_tests/cli/run_tests/args.js @@ -46,6 +46,12 @@ const options = { updateBaselines: { desc: 'Replace baseline screenshots with whatever is generated from the test.', }, + updateSnapshots: { + desc: 'Replace inline and file snapshots with whatever is generated from the test.', + }, + u: { + desc: 'Replace both baseline screenshots and snapshots', + }, include: { arg: '', desc: 'Files that must included to be run, can be included multiple times.', diff --git a/packages/kbn-test/src/functional_tests/cli/run_tests/args.test.js b/packages/kbn-test/src/functional_tests/cli/run_tests/args.test.js index 35e4cef5b3a6..34a2d19c22a3 100644 --- a/packages/kbn-test/src/functional_tests/cli/run_tests/args.test.js +++ b/packages/kbn-test/src/functional_tests/cli/run_tests/args.test.js @@ -76,6 +76,11 @@ describe('process options for run tests CLI', () => { expect(options).toMatchSnapshot(); }); + it('accepts boolean value for updateSnapshots', () => { + const options = processOptions({ updateSnapshots: true }, ['foo']); + expect(options).toMatchSnapshot(); + }); + it('accepts source value for esFrom', () => { const options = processOptions({ esFrom: 'source' }, ['foo']); expect(options).toMatchSnapshot(); diff --git a/packages/kbn-test/src/functional_tests/cli/run_tests/cli.test.js b/packages/kbn-test/src/functional_tests/cli/run_tests/cli.test.js index 9f9a8f59fde9..438e3585fc06 100644 --- a/packages/kbn-test/src/functional_tests/cli/run_tests/cli.test.js +++ b/packages/kbn-test/src/functional_tests/cli/run_tests/cli.test.js @@ -125,6 +125,22 @@ describe('run tests CLI', () => { expect(exitMock).not.toHaveBeenCalledWith(); }); + it('accepts boolean value for updateSnapshots', async () => { + global.process.argv.push('--updateSnapshots'); + + await runTestsCli(['foo']); + + expect(exitMock).not.toHaveBeenCalledWith(); + }); + + it('accepts boolean value for -u', async () => { + global.process.argv.push('-u'); + + await runTestsCli(['foo']); + + expect(exitMock).not.toHaveBeenCalledWith(); + }); + it('accepts source value for esFrom', async () => { global.process.argv.push('--esFrom', 'source'); diff --git a/packages/kbn-test/src/functional_tests/cli/start_servers/__snapshots__/cli.test.js.snap b/packages/kbn-test/src/functional_tests/cli/start_servers/__snapshots__/cli.test.js.snap index b54bf5dc84dd..ba085b086821 100644 --- a/packages/kbn-test/src/functional_tests/cli/start_servers/__snapshots__/cli.test.js.snap +++ b/packages/kbn-test/src/functional_tests/cli/start_servers/__snapshots__/cli.test.js.snap @@ -7,6 +7,13 @@ exports[`start servers CLI options accepts boolean value for updateBaselines 1`] " `; +exports[`start servers CLI options accepts boolean value for updateSnapshots 1`] = ` +" +functional_tests_server: invalid option [updateSnapshots] + ...stack trace... +" +`; + exports[`start servers CLI options rejects bail 1`] = ` " functional_tests_server: invalid option [bail] @@ -40,4 +47,4 @@ exports[`start servers CLI options rejects invalid options even if valid options functional_tests_server: invalid option [grep] ...stack trace... " -`; \ No newline at end of file +`; diff --git a/packages/kbn-test/src/functional_tests/cli/start_servers/cli.test.js b/packages/kbn-test/src/functional_tests/cli/start_servers/cli.test.js index 3ceecb280662..d63a8df2491a 100644 --- a/packages/kbn-test/src/functional_tests/cli/start_servers/cli.test.js +++ b/packages/kbn-test/src/functional_tests/cli/start_servers/cli.test.js @@ -126,6 +126,15 @@ describe('start servers CLI', () => { checkMockConsoleLogSnapshot(logMock); }); + it('accepts boolean value for updateSnapshots', async () => { + global.process.argv.push('--updateSnapshots'); + + await startServersCli('foo'); + + expect(exitMock).toHaveBeenCalledWith(1); + checkMockConsoleLogSnapshot(logMock); + }); + it('accepts source value for esFrom', async () => { global.process.argv.push('--esFrom', 'source'); diff --git a/packages/kbn-test/src/functional_tests/lib/run_ftr.js b/packages/kbn-test/src/functional_tests/lib/run_ftr.js index 14883ac977c4..d9389c8cbc15 100644 --- a/packages/kbn-test/src/functional_tests/lib/run_ftr.js +++ b/packages/kbn-test/src/functional_tests/lib/run_ftr.js @@ -22,7 +22,7 @@ import { CliError } from './run_cli'; async function createFtr({ configPath, - options: { installDir, log, bail, grep, updateBaselines, suiteFiles, suiteTags }, + options: { installDir, log, bail, grep, updateBaselines, suiteFiles, suiteTags, updateSnapshots }, }) { const config = await readConfigFile(log, configPath); @@ -37,6 +37,7 @@ async function createFtr({ installDir, }, updateBaselines, + updateSnapshots, suiteFiles: { include: [...suiteFiles.include, ...config.get('suiteFiles.include')], exclude: [...suiteFiles.exclude, ...config.get('suiteFiles.exclude')], diff --git a/packages/kbn-test/tsconfig.json b/packages/kbn-test/tsconfig.json index 3219e6cf3d6e..6d94389f82ca 100644 --- a/packages/kbn-test/tsconfig.json +++ b/packages/kbn-test/tsconfig.json @@ -5,6 +5,9 @@ "src/**/*", "index.d.ts" ], + "exclude": [ + "types/ftr_globals/**/*" + ], "compilerOptions": { "declaration": true, "emitDeclarationOnly": true, diff --git a/test/mocha_decorations.d.ts b/packages/kbn-test/types/ftr_globals/mocha.d.ts similarity index 73% rename from test/mocha_decorations.d.ts rename to packages/kbn-test/types/ftr_globals/mocha.d.ts index 5ad289eb4f1a..d143b742b6dd 100644 --- a/test/mocha_decorations.d.ts +++ b/packages/kbn-test/types/ftr_globals/mocha.d.ts @@ -19,27 +19,11 @@ import { Suite } from 'mocha'; -type Tags = - | 'ciGroup1' - | 'ciGroup2' - | 'ciGroup3' - | 'ciGroup4' - | 'ciGroup5' - | 'ciGroup6' - | 'ciGroup7' - | 'ciGroup8' - | 'ciGroup9' - | 'ciGroup10' - | 'ciGroup11' - | 'ciGroup12'; - -// We need to use the namespace here to match the Mocha definition declare module 'mocha' { interface Suite { /** * Assign tags to the test suite to determine in which CI job it should be run. */ - tags(tags: T | T[]): void; - tags(tags: T | T[]): void; + tags(tags: string[] | string): void; } } diff --git a/packages/kbn-test/types/ftr_globals/snapshots.d.ts b/packages/kbn-test/types/ftr_globals/snapshots.d.ts new file mode 100644 index 000000000000..ab247a72991e --- /dev/null +++ b/packages/kbn-test/types/ftr_globals/snapshots.d.ts @@ -0,0 +1,25 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +declare const expectSnapshot: ( + received: any +) => { + toMatch: () => void; + toMatchInline: (_actual?: any) => void; +}; diff --git a/test/tsconfig.json b/test/tsconfig.json index 390e0b88c3d5..df26441b0806 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -4,7 +4,7 @@ "incremental": false, "types": ["node", "mocha", "flot"] }, - "include": ["**/*", "../typings/elastic__node_crypto.d.ts", "typings/**/*"], + "include": ["**/*", "../typings/elastic__node_crypto.d.ts", "typings/**/*", "../packages/kbn-test/types/ftr_globals/**/*"], "exclude": ["plugin_functional/plugins/**/*", "interpreter_functional/plugins/**/*"], "references": [ { "path": "../src/core/tsconfig.json" }, diff --git a/x-pack/test/api_integration/apis/uptime/rest/index.ts b/x-pack/test/api_integration/apis/uptime/rest/index.ts index 6f410add0fa4..f59b79a6b7bf 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/index.ts +++ b/x-pack/test/api_integration/apis/uptime/rest/index.ts @@ -9,15 +9,12 @@ import { settingsObjectId, settingsObjectType, } from '../../../../../plugins/uptime/server/lib/saved_objects'; -import { registerMochaHooksForSnapshots } from '../../../../apm_api_integration/common/match_snapshot'; export default function ({ getService, loadTestFile }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const server = getService('kibanaServer'); describe('uptime REST endpoints', () => { - registerMochaHooksForSnapshots(); - beforeEach('clear settings', async () => { try { await server.savedObjects.delete({ diff --git a/x-pack/test/api_integration/apis/uptime/rest/monitor_states_real_data.ts b/x-pack/test/api_integration/apis/uptime/rest/monitor_states_real_data.ts index 08a339ed5932..bdc18ac831d2 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/monitor_states_real_data.ts +++ b/x-pack/test/api_integration/apis/uptime/rest/monitor_states_real_data.ts @@ -9,7 +9,6 @@ import { isRight } from 'fp-ts/lib/Either'; import { FtrProviderContext } from '../../../ftr_provider_context'; import { MonitorSummariesResultType } from '../../../../../plugins/uptime/common/runtime_types'; import { API_URLS } from '../../../../../plugins/uptime/common/constants'; -import { expectSnapshot } from '../../../../apm_api_integration/common/match_snapshot'; interface ExpectedMonitorStatesPage { response: any; diff --git a/x-pack/test/apm_api_integration/basic/tests/correlations/ranges.ts b/x-pack/test/apm_api_integration/basic/tests/correlations/ranges.ts index 0a730217e53f..751ee8753c44 100644 --- a/x-pack/test/apm_api_integration/basic/tests/correlations/ranges.ts +++ b/x-pack/test/apm_api_integration/basic/tests/correlations/ranges.ts @@ -9,7 +9,6 @@ import { format } from 'url'; import { PromiseReturnType } from '../../../../../plugins/observability/typings/common'; import archives_metadata from '../../../common/archives_metadata'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; -import { expectSnapshot } from '../../../common/match_snapshot'; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); diff --git a/x-pack/test/apm_api_integration/basic/tests/correlations/slow_durations.ts b/x-pack/test/apm_api_integration/basic/tests/correlations/slow_durations.ts index 0cfdf3ec474d..3cf1c2cecb42 100644 --- a/x-pack/test/apm_api_integration/basic/tests/correlations/slow_durations.ts +++ b/x-pack/test/apm_api_integration/basic/tests/correlations/slow_durations.ts @@ -9,7 +9,6 @@ import { format } from 'url'; import { PromiseReturnType } from '../../../../../plugins/observability/typings/common'; import archives_metadata from '../../../common/archives_metadata'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; -import { expectSnapshot } from '../../../common/match_snapshot'; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); diff --git a/x-pack/test/apm_api_integration/basic/tests/index.ts b/x-pack/test/apm_api_integration/basic/tests/index.ts index 39dd721c7067..0381e5f51bb9 100644 --- a/x-pack/test/apm_api_integration/basic/tests/index.ts +++ b/x-pack/test/apm_api_integration/basic/tests/index.ts @@ -4,12 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ import { FtrProviderContext } from '../../common/ftr_provider_context'; -import { registerMochaHooksForSnapshots } from '../../common/match_snapshot'; export default function apmApiIntegrationTests({ loadTestFile }: FtrProviderContext) { describe('APM specs (basic)', function () { - registerMochaHooksForSnapshots(); - this.tags('ciGroup1'); loadTestFile(require.resolve('./feature_controls')); diff --git a/x-pack/test/apm_api_integration/basic/tests/metrics_charts/metrics_charts.ts b/x-pack/test/apm_api_integration/basic/tests/metrics_charts/metrics_charts.ts index cae562b3f5dc..d52aa2727d65 100644 --- a/x-pack/test/apm_api_integration/basic/tests/metrics_charts/metrics_charts.ts +++ b/x-pack/test/apm_api_integration/basic/tests/metrics_charts/metrics_charts.ts @@ -8,7 +8,6 @@ import { first } from 'lodash'; import { MetricsChartsByAgentAPIResponse } from '../../../../../plugins/apm/server/lib/metrics/get_metrics_chart_data_by_agent'; import { GenericMetricsChart } from '../../../../../plugins/apm/server/lib/metrics/transform_metrics_chart'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; -import { expectSnapshot } from '../../../common/match_snapshot'; interface ChartResponse { body: MetricsChartsByAgentAPIResponse; diff --git a/x-pack/test/apm_api_integration/basic/tests/observability_overview/observability_overview.ts b/x-pack/test/apm_api_integration/basic/tests/observability_overview/observability_overview.ts index 01fa09630e85..cdeab9ecbdc4 100644 --- a/x-pack/test/apm_api_integration/basic/tests/observability_overview/observability_overview.ts +++ b/x-pack/test/apm_api_integration/basic/tests/observability_overview/observability_overview.ts @@ -5,7 +5,6 @@ */ import expect from '@kbn/expect'; import archives_metadata from '../../../common/archives_metadata'; -import { expectSnapshot } from '../../../common/match_snapshot'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; export default function ApiTest({ getService }: FtrProviderContext) { diff --git a/x-pack/test/apm_api_integration/basic/tests/service_maps/service_maps.ts b/x-pack/test/apm_api_integration/basic/tests/service_maps/service_maps.ts index d729680154c1..3820a7665105 100644 --- a/x-pack/test/apm_api_integration/basic/tests/service_maps/service_maps.ts +++ b/x-pack/test/apm_api_integration/basic/tests/service_maps/service_maps.ts @@ -5,7 +5,6 @@ */ import expect from '@kbn/expect'; -import { expectSnapshot } from '../../../common/match_snapshot'; import archives_metadata from '../../../common/archives_metadata'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; diff --git a/x-pack/test/apm_api_integration/basic/tests/service_overview/error_groups.ts b/x-pack/test/apm_api_integration/basic/tests/service_overview/error_groups.ts index b699a30d4041..088b7cb8bb56 100644 --- a/x-pack/test/apm_api_integration/basic/tests/service_overview/error_groups.ts +++ b/x-pack/test/apm_api_integration/basic/tests/service_overview/error_groups.ts @@ -7,7 +7,6 @@ import expect from '@kbn/expect'; import qs from 'querystring'; import { pick, uniqBy } from 'lodash'; -import { expectSnapshot } from '../../../common/match_snapshot'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; import archives from '../../../common/archives_metadata'; diff --git a/x-pack/test/apm_api_integration/basic/tests/services/top_services.ts b/x-pack/test/apm_api_integration/basic/tests/services/top_services.ts index cd2bdb7fde19..4d70c4e94943 100644 --- a/x-pack/test/apm_api_integration/basic/tests/services/top_services.ts +++ b/x-pack/test/apm_api_integration/basic/tests/services/top_services.ts @@ -7,7 +7,6 @@ import expect from '@kbn/expect'; import { isEmpty, pick } from 'lodash'; import { PromiseReturnType } from '../../../../../plugins/observability/typings/common'; -import { expectSnapshot } from '../../../common/match_snapshot'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; import archives_metadata from '../../../common/archives_metadata'; diff --git a/x-pack/test/apm_api_integration/basic/tests/services/transaction_types.ts b/x-pack/test/apm_api_integration/basic/tests/services/transaction_types.ts index 1221ce0198d8..40b6db6997f8 100644 --- a/x-pack/test/apm_api_integration/basic/tests/services/transaction_types.ts +++ b/x-pack/test/apm_api_integration/basic/tests/services/transaction_types.ts @@ -6,7 +6,6 @@ import expect from '@kbn/expect'; import archives_metadata from '../../../common/archives_metadata'; -import { expectSnapshot } from '../../../common/match_snapshot'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; export default function ApiTest({ getService }: FtrProviderContext) { diff --git a/x-pack/test/apm_api_integration/basic/tests/settings/agent_configuration.ts b/x-pack/test/apm_api_integration/basic/tests/settings/agent_configuration.ts index 32a06b8fb880..bde9364efc68 100644 --- a/x-pack/test/apm_api_integration/basic/tests/settings/agent_configuration.ts +++ b/x-pack/test/apm_api_integration/basic/tests/settings/agent_configuration.ts @@ -6,7 +6,6 @@ import expect from '@kbn/expect'; import { omit, orderBy } from 'lodash'; -import { expectSnapshot } from '../../../common/match_snapshot'; import { AgentConfigurationIntake } from '../../../../../plugins/apm/common/agent_configuration/configuration_types'; import { AgentConfigSearchParams } from '../../../../../plugins/apm/server/routes/settings/agent_configuration'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; diff --git a/x-pack/test/apm_api_integration/basic/tests/settings/anomaly_detection/read_user.ts b/x-pack/test/apm_api_integration/basic/tests/settings/anomaly_detection/read_user.ts index 2c8f13ce79f7..a9e6eae8bed8 100644 --- a/x-pack/test/apm_api_integration/basic/tests/settings/anomaly_detection/read_user.ts +++ b/x-pack/test/apm_api_integration/basic/tests/settings/anomaly_detection/read_user.ts @@ -5,7 +5,6 @@ */ import expect from '@kbn/expect'; -import { expectSnapshot } from '../../../../common/match_snapshot'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; export default function apiTest({ getService }: FtrProviderContext) { diff --git a/x-pack/test/apm_api_integration/basic/tests/settings/anomaly_detection/write_user.ts b/x-pack/test/apm_api_integration/basic/tests/settings/anomaly_detection/write_user.ts index d1dbd15f4dce..4fa3e46430e9 100644 --- a/x-pack/test/apm_api_integration/basic/tests/settings/anomaly_detection/write_user.ts +++ b/x-pack/test/apm_api_integration/basic/tests/settings/anomaly_detection/write_user.ts @@ -5,7 +5,6 @@ */ import expect from '@kbn/expect'; -import { expectSnapshot } from '../../../../common/match_snapshot'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; export default function apiTest({ getService }: FtrProviderContext) { diff --git a/x-pack/test/apm_api_integration/basic/tests/settings/custom_link.ts b/x-pack/test/apm_api_integration/basic/tests/settings/custom_link.ts index 60b4020e73dc..8ac5566fc2c4 100644 --- a/x-pack/test/apm_api_integration/basic/tests/settings/custom_link.ts +++ b/x-pack/test/apm_api_integration/basic/tests/settings/custom_link.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ import expect from '@kbn/expect'; -import { expectSnapshot } from '../../../common/match_snapshot'; import { CustomLink } from '../../../../../plugins/apm/common/custom_link/custom_link_types'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; diff --git a/x-pack/test/apm_api_integration/basic/tests/traces/top_traces.ts b/x-pack/test/apm_api_integration/basic/tests/traces/top_traces.ts index 6a3a1ddd0f6a..4dbd6cc4cd6f 100644 --- a/x-pack/test/apm_api_integration/basic/tests/traces/top_traces.ts +++ b/x-pack/test/apm_api_integration/basic/tests/traces/top_traces.ts @@ -6,7 +6,6 @@ import expect from '@kbn/expect'; import { sortBy } from 'lodash'; import archives_metadata from '../../../common/archives_metadata'; -import { expectSnapshot } from '../../../common/match_snapshot'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; export default function ApiTest({ getService }: FtrProviderContext) { diff --git a/x-pack/test/apm_api_integration/basic/tests/transaction_groups/breakdown.ts b/x-pack/test/apm_api_integration/basic/tests/transaction_groups/breakdown.ts index f2e58718870b..24f542c222d6 100644 --- a/x-pack/test/apm_api_integration/basic/tests/transaction_groups/breakdown.ts +++ b/x-pack/test/apm_api_integration/basic/tests/transaction_groups/breakdown.ts @@ -5,7 +5,6 @@ */ import expect from '@kbn/expect'; import archives_metadata from '../../../common/archives_metadata'; -import { expectSnapshot } from '../../../common/match_snapshot'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; export default function ApiTest({ getService }: FtrProviderContext) { diff --git a/x-pack/test/apm_api_integration/basic/tests/transaction_groups/distribution.ts b/x-pack/test/apm_api_integration/basic/tests/transaction_groups/distribution.ts index e0b03e1a91f4..a93aff5c8cf3 100644 --- a/x-pack/test/apm_api_integration/basic/tests/transaction_groups/distribution.ts +++ b/x-pack/test/apm_api_integration/basic/tests/transaction_groups/distribution.ts @@ -7,7 +7,6 @@ import expect from '@kbn/expect'; import qs from 'querystring'; import { isEmpty } from 'lodash'; import archives_metadata from '../../../common/archives_metadata'; -import { expectSnapshot } from '../../../common/match_snapshot'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; export default function ApiTest({ getService }: FtrProviderContext) { diff --git a/x-pack/test/apm_api_integration/basic/tests/transaction_groups/error_rate.ts b/x-pack/test/apm_api_integration/basic/tests/transaction_groups/error_rate.ts index 86309c91b0bc..da3d07a0e83a 100644 --- a/x-pack/test/apm_api_integration/basic/tests/transaction_groups/error_rate.ts +++ b/x-pack/test/apm_api_integration/basic/tests/transaction_groups/error_rate.ts @@ -6,7 +6,6 @@ import expect from '@kbn/expect'; import { first, last } from 'lodash'; import archives_metadata from '../../../common/archives_metadata'; -import { expectSnapshot } from '../../../common/match_snapshot'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; export default function ApiTest({ getService }: FtrProviderContext) { diff --git a/x-pack/test/apm_api_integration/basic/tests/transaction_groups/top_transaction_groups.ts b/x-pack/test/apm_api_integration/basic/tests/transaction_groups/top_transaction_groups.ts index 2e802957a95e..d4fdfe6d0fc7 100644 --- a/x-pack/test/apm_api_integration/basic/tests/transaction_groups/top_transaction_groups.ts +++ b/x-pack/test/apm_api_integration/basic/tests/transaction_groups/top_transaction_groups.ts @@ -6,7 +6,6 @@ import expect from '@kbn/expect'; import { sortBy } from 'lodash'; import archives_metadata from '../../../common/archives_metadata'; -import { expectSnapshot } from '../../../common/match_snapshot'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; function sortTransactionGroups(items: any[]) { diff --git a/x-pack/test/apm_api_integration/basic/tests/transaction_groups/transaction_charts.ts b/x-pack/test/apm_api_integration/basic/tests/transaction_groups/transaction_charts.ts index c3b969d76566..5ebbdfa16d9a 100644 --- a/x-pack/test/apm_api_integration/basic/tests/transaction_groups/transaction_charts.ts +++ b/x-pack/test/apm_api_integration/basic/tests/transaction_groups/transaction_charts.ts @@ -6,7 +6,6 @@ import expect from '@kbn/expect'; import archives_metadata from '../../../common/archives_metadata'; import { PromiseReturnType } from '../../../../../plugins/observability/typings/common'; -import { expectSnapshot } from '../../../common/match_snapshot'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; export default function ApiTest({ getService }: FtrProviderContext) { diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/csm_services.ts b/x-pack/test/apm_api_integration/trial/tests/csm/csm_services.ts index 6235e7abd37e..05c6439508ec 100644 --- a/x-pack/test/apm_api_integration/trial/tests/csm/csm_services.ts +++ b/x-pack/test/apm_api_integration/trial/tests/csm/csm_services.ts @@ -5,7 +5,6 @@ */ import expect from '@kbn/expect'; -import { expectSnapshot } from '../../../common/match_snapshot'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; export default function rumServicesApiTests({ getService }: FtrProviderContext) { diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/has_rum_data.ts b/x-pack/test/apm_api_integration/trial/tests/csm/has_rum_data.ts index 12fdb5ba9704..f2033e03f582 100644 --- a/x-pack/test/apm_api_integration/trial/tests/csm/has_rum_data.ts +++ b/x-pack/test/apm_api_integration/trial/tests/csm/has_rum_data.ts @@ -5,7 +5,6 @@ */ import expect from '@kbn/expect'; -import { expectSnapshot } from '../../../common/match_snapshot'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; export default function rumHasDataApiTests({ getService }: FtrProviderContext) { diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/js_errors.ts b/x-pack/test/apm_api_integration/trial/tests/csm/js_errors.ts index 0edffe7999a6..6fc8cb4c1d4e 100644 --- a/x-pack/test/apm_api_integration/trial/tests/csm/js_errors.ts +++ b/x-pack/test/apm_api_integration/trial/tests/csm/js_errors.ts @@ -5,7 +5,6 @@ */ import expect from '@kbn/expect'; -import { expectSnapshot } from '../../../common/match_snapshot'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; export default function rumJsErrorsApiTests({ getService }: FtrProviderContext) { diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/long_task_metrics.ts b/x-pack/test/apm_api_integration/trial/tests/csm/long_task_metrics.ts index 518c4ef8a81a..6db5de24baa9 100644 --- a/x-pack/test/apm_api_integration/trial/tests/csm/long_task_metrics.ts +++ b/x-pack/test/apm_api_integration/trial/tests/csm/long_task_metrics.ts @@ -5,7 +5,6 @@ */ import expect from '@kbn/expect'; -import { expectSnapshot } from '../../../common/match_snapshot'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; export default function rumServicesApiTests({ getService }: FtrProviderContext) { diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/page_views.ts b/x-pack/test/apm_api_integration/trial/tests/csm/page_views.ts index ca5670d41d8e..5d910862843d 100644 --- a/x-pack/test/apm_api_integration/trial/tests/csm/page_views.ts +++ b/x-pack/test/apm_api_integration/trial/tests/csm/page_views.ts @@ -5,7 +5,6 @@ */ import expect from '@kbn/expect'; -import { expectSnapshot } from '../../../common/match_snapshot'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; export default function rumServicesApiTests({ getService }: FtrProviderContext) { diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/url_search.ts b/x-pack/test/apm_api_integration/trial/tests/csm/url_search.ts index c887fa3e7764..961c78343463 100644 --- a/x-pack/test/apm_api_integration/trial/tests/csm/url_search.ts +++ b/x-pack/test/apm_api_integration/trial/tests/csm/url_search.ts @@ -5,7 +5,6 @@ */ import expect from '@kbn/expect'; -import { expectSnapshot } from '../../../common/match_snapshot'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; export default function rumServicesApiTests({ getService }: FtrProviderContext) { diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/web_core_vitals.ts b/x-pack/test/apm_api_integration/trial/tests/csm/web_core_vitals.ts index 5dbe266deeb8..7e970493eb61 100644 --- a/x-pack/test/apm_api_integration/trial/tests/csm/web_core_vitals.ts +++ b/x-pack/test/apm_api_integration/trial/tests/csm/web_core_vitals.ts @@ -5,7 +5,6 @@ */ import expect from '@kbn/expect'; -import { expectSnapshot } from '../../../common/match_snapshot'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; export default function rumServicesApiTests({ getService }: FtrProviderContext) { diff --git a/x-pack/test/apm_api_integration/trial/tests/index.ts b/x-pack/test/apm_api_integration/trial/tests/index.ts index a67dd1bcbd7a..97ab662313c7 100644 --- a/x-pack/test/apm_api_integration/trial/tests/index.ts +++ b/x-pack/test/apm_api_integration/trial/tests/index.ts @@ -5,14 +5,11 @@ */ import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; -import { registerMochaHooksForSnapshots } from '../../common/match_snapshot'; export default function observabilityApiIntegrationTests({ loadTestFile }: FtrProviderContext) { describe('APM specs (trial)', function () { this.tags('ciGroup1'); - registerMochaHooksForSnapshots(); - describe('Services', function () { loadTestFile(require.resolve('./services/annotations')); loadTestFile(require.resolve('./services/top_services.ts')); diff --git a/x-pack/test/apm_api_integration/trial/tests/service_maps/service_maps.ts b/x-pack/test/apm_api_integration/trial/tests/service_maps/service_maps.ts index 9c01833f78e5..b1e29b220dd5 100644 --- a/x-pack/test/apm_api_integration/trial/tests/service_maps/service_maps.ts +++ b/x-pack/test/apm_api_integration/trial/tests/service_maps/service_maps.ts @@ -9,7 +9,6 @@ import expect from '@kbn/expect'; import { isEmpty, uniq } from 'lodash'; import archives_metadata from '../../../common/archives_metadata'; import { PromiseReturnType } from '../../../../../plugins/observability/typings/common'; -import { expectSnapshot } from '../../../common/match_snapshot'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; export default function serviceMapsApiTests({ getService }: FtrProviderContext) { diff --git a/x-pack/test/apm_api_integration/trial/tests/services/top_services.ts b/x-pack/test/apm_api_integration/trial/tests/services/top_services.ts index bb611013351d..90cad966ba10 100644 --- a/x-pack/test/apm_api_integration/trial/tests/services/top_services.ts +++ b/x-pack/test/apm_api_integration/trial/tests/services/top_services.ts @@ -5,7 +5,6 @@ */ import expect from '@kbn/expect'; -import { expectSnapshot } from '../../../common/match_snapshot'; import { PromiseReturnType } from '../../../../../plugins/observability/typings/common'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; import archives_metadata from '../../../common/archives_metadata'; diff --git a/x-pack/test/apm_api_integration/trial/tests/services/transaction_groups_charts.ts b/x-pack/test/apm_api_integration/trial/tests/services/transaction_groups_charts.ts index 47e465596e0d..99e90b8433c8 100644 --- a/x-pack/test/apm_api_integration/trial/tests/services/transaction_groups_charts.ts +++ b/x-pack/test/apm_api_integration/trial/tests/services/transaction_groups_charts.ts @@ -5,7 +5,6 @@ */ import expect from '@kbn/expect'; -import { expectSnapshot } from '../../../common/match_snapshot'; import { PromiseReturnType } from '../../../../../plugins/observability/typings/common'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; import archives_metadata from '../../../common/archives_metadata'; diff --git a/x-pack/test/mocha_decorations.d.ts b/x-pack/test/mocha_decorations.d.ts deleted file mode 100644 index 44f43a22de1f..000000000000 --- a/x-pack/test/mocha_decorations.d.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { Suite } from 'mocha'; - -// We need to use the namespace here to match the Mocha definition -declare module 'mocha' { - interface Suite { - /** - * Assign tags to the test suite to determine in which CI job it should be run. - */ - tags(tags: string[] | string): void; - } -} diff --git a/x-pack/test/tsconfig.json b/x-pack/test/tsconfig.json index e041292ebf3c..3ac7026d16a1 100644 --- a/x-pack/test/tsconfig.json +++ b/x-pack/test/tsconfig.json @@ -5,7 +5,7 @@ "incremental": false, "types": ["mocha", "node", "flot"] }, - "include": ["**/*", "../typings/**/*"], + "include": ["**/*", "../typings/**/*", "../../packages/kbn-test/types/ftr_globals/**/*"], "exclude": ["../typings/jest.d.ts"], "references": [ { "path": "../../src/core/tsconfig.json" }, From 893b2961c0da9b267ca07aa9cb7f793fcca92acc Mon Sep 17 00:00:00 2001 From: Christos Nasikas Date: Thu, 19 Nov 2020 10:24:38 +0200 Subject: [PATCH 36/49] [Security Solution][Detections] Fix adding an action to detection rules (#83722) --- .../rules/rule_actions_field/index.test.tsx | 9 ++++- .../rules/rule_actions_field/index.tsx | 10 ++++-- .../rules/step_rule_actions/index.test.tsx | 12 ++++++- .../rules/step_rule_actions/index.tsx | 4 +-- .../pages/detection_engine/rules/helpers.tsx | 35 +++++++++++-------- .../triggers_actions_ui/public/index.ts | 1 + 6 files changed, 49 insertions(+), 22 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_field/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_field/index.test.tsx index cce6c72ca4cc..65e25b788b3d 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_field/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_field/index.test.tsx @@ -31,13 +31,20 @@ describe('RuleActionsField', () => { }, }, }); + + const messageVariables = { + context: [], + state: [], + params: [], + }; + const Component = () => { const field = useFormFieldMock(); const { form } = useForm(); return (
    - + ); }; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_field/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_field/index.tsx index 4ff1b4e4f20f..0211788509db 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_field/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_field/index.tsx @@ -12,17 +12,21 @@ import ReactMarkdown from 'react-markdown'; import styled from 'styled-components'; import { NOTIFICATION_SUPPORTED_ACTION_TYPES_IDS } from '../../../../../common/constants'; -import { SelectField, useFormContext } from '../../../../shared_imports'; +import { FieldHook, useFormContext } from '../../../../shared_imports'; import { ActionForm, ActionType, loadActionTypes, + ActionVariables, } from '../../../../../../triggers_actions_ui/public'; import { AlertAction } from '../../../../../../alerts/common'; import { useKibana } from '../../../../common/lib/kibana'; import { FORM_ERRORS_TITLE } from './translations'; -type ThrottleSelectField = typeof SelectField; +interface Props { + field: FieldHook; + messageVariables: ActionVariables; +} const DEFAULT_ACTION_GROUP_ID = 'default'; const DEFAULT_ACTION_MESSAGE = @@ -34,7 +38,7 @@ const FieldErrorsContainer = styled.div` } `; -export const RuleActionsField: ThrottleSelectField = ({ field, messageVariables }) => { +export const RuleActionsField: React.FC = ({ field, messageVariables }) => { const [fieldErrors, setFieldErrors] = useState(null); const [supportedActionTypes, setSupportedActionTypes] = useState(); const form = useFormContext(); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/index.test.tsx index a1d7e69b7a60..565998806033 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/index.test.tsx @@ -30,10 +30,20 @@ jest.mock('../../../../common/lib/kibana', () => ({ }), })); +const actionMessageParams = { + context: [], + state: [], + params: [], +}; + describe('StepRuleActions', () => { it('renders correctly', () => { const wrapper = shallow( - + ); expect(wrapper.find('[data-test-subj="stepRuleActions"]')).toHaveLength(1); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/index.tsx index dd1d92e7e72a..daf4e3c01bbb 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/index.tsx @@ -16,7 +16,7 @@ import { import { findIndex } from 'lodash/fp'; import React, { FC, memo, useCallback, useEffect, useMemo } from 'react'; -import { ActionVariable } from '../../../../../../triggers_actions_ui/public'; +import { ActionVariables } from '../../../../../../triggers_actions_ui/public'; import { RuleStep, RuleStepProps, @@ -38,7 +38,7 @@ import { APP_ID } from '../../../../../common/constants'; interface StepRuleActionsProps extends RuleStepProps { defaultValues?: ActionsStepRule | null; - actionMessageParams: ActionVariable[]; + actionMessageParams: ActionVariables; } const stepActionsDefaultValue: ActionsStepRule = { diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx index ffcf25d25379..513982f099c6 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx @@ -11,7 +11,7 @@ import { useLocation } from 'react-router-dom'; import styled from 'styled-components'; import { EuiFlexItem } from '@elastic/eui'; -import { ActionVariable } from '../../../../../../triggers_actions_ui/public'; +import { ActionVariables } from '../../../../../../triggers_actions_ui/public'; import { RuleAlertAction } from '../../../../../common/detection_engine/types'; import { assertUnreachable } from '../../../../../common/utility_types'; import { transformRuleToAlertAction } from '../../../../../common/detection_engine/transform_actions'; @@ -366,21 +366,26 @@ export const getActionMessageRuleParams = (ruleType: Type): string[] => { return ruleParamsKeys; }; -export const getActionMessageParams = memoizeOne((ruleType: Type | undefined): ActionVariable[] => { - if (!ruleType) { - return []; +export const getActionMessageParams = memoizeOne( + (ruleType: Type | undefined): ActionVariables => { + if (!ruleType) { + return { state: [], params: [] }; + } + const actionMessageRuleParams = getActionMessageRuleParams(ruleType); + // Prefixes are being added automatically by the ActionTypeForm + return { + state: [{ name: 'signals_count', description: 'state.signals_count' }], + params: [], + context: [ + { name: 'results_link', description: 'context.results_link' }, + ...actionMessageRuleParams.map((param) => { + const extendedParam = `rule.${param}`; + return { name: extendedParam, description: `context.${extendedParam}` }; + }), + ], + }; } - const actionMessageRuleParams = getActionMessageRuleParams(ruleType); - - return [ - { name: 'state.signals_count', description: 'state.signals_count' }, - { name: '{context.results_link}', description: 'context.results_link' }, - ...actionMessageRuleParams.map((param) => { - const extendedParam = `context.rule.${param}`; - return { name: extendedParam, description: extendedParam }; - }), - ]; -}); +); // typed as null not undefined as the initial state for this value is null. export const userHasNoPermissions = (canUserCRUD: boolean | null): boolean => diff --git a/x-pack/plugins/triggers_actions_ui/public/index.ts b/x-pack/plugins/triggers_actions_ui/public/index.ts index 3187451d2600..c479359ff7e6 100644 --- a/x-pack/plugins/triggers_actions_ui/public/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/index.ts @@ -21,6 +21,7 @@ export { AlertTypeParamsExpressionProps, ValidationResult, ActionVariable, + ActionVariables, ActionConnector, IErrorObject, } from './types'; From 6a2c415a98cf56a6cfed38690297d378a63c07b1 Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Thu, 19 Nov 2020 06:08:56 -0500 Subject: [PATCH 37/49] Revert "[Alerting] Add `alert.updatedAt` field to represent date of last user edit (#83578)" This reverts commit acc3e2f443e3c60dfc923aa1b3b179f34cf69804. --- .../server/alerts_client/alerts_client.ts | 39 +++++++++-------- .../server/alerts_client/tests/create.test.ts | 7 --- .../alerts_client/tests/disable.test.ts | 6 +-- .../server/alerts_client/tests/enable.test.ts | 6 +-- .../server/alerts_client/tests/find.test.ts | 1 - .../server/alerts_client/tests/get.test.ts | 1 - .../tests/get_alert_instance_summary.test.ts | 1 - .../alerts_client/tests/mute_all.test.ts | 5 +-- .../alerts_client/tests/mute_instance.test.ts | 5 +-- .../alerts_client/tests/unmute_all.test.ts | 5 +-- .../tests/unmute_instance.test.ts | 5 +-- .../server/alerts_client/tests/update.test.ts | 7 +-- .../tests/update_api_key.test.ts | 6 +-- .../alerts/server/saved_objects/index.ts | 2 - .../alerts/server/saved_objects/mappings.json | 3 -- .../server/saved_objects/migrations.test.ts | 43 +------------------ .../alerts/server/saved_objects/migrations.ts | 20 --------- .../partially_update_alert.test.ts | 1 - x-pack/plugins/alerts/server/types.ts | 1 - .../spaces_only/tests/alerting/create.ts | 1 - .../tests/alerting/execution_status.ts | 22 ---------- .../spaces_only/tests/alerting/migrations.ts | 9 ---- 22 files changed, 30 insertions(+), 166 deletions(-) diff --git a/x-pack/plugins/alerts/server/alerts_client/alerts_client.ts b/x-pack/plugins/alerts/server/alerts_client/alerts_client.ts index c08ff9449d15..e97b37f16faf 100644 --- a/x-pack/plugins/alerts/server/alerts_client/alerts_client.ts +++ b/x-pack/plugins/alerts/server/alerts_client/alerts_client.ts @@ -228,17 +228,14 @@ export class AlertsClient { this.validateActions(alertType, data.actions); - const createTime = Date.now(); const { references, actions } = await this.denormalizeActions(data.actions); - const rawAlert: RawAlert = { ...data, ...this.apiKeyAsAlertAttributes(createdAPIKey, username), actions, createdBy: username, updatedBy: username, - createdAt: new Date(createTime).toISOString(), - updatedAt: new Date(createTime).toISOString(), + createdAt: new Date().toISOString(), params: validatedAlertTypeParams as RawAlert['params'], muteAll: false, mutedInstanceIds: [], @@ -292,7 +289,12 @@ export class AlertsClient { }); createdAlert.attributes.scheduledTaskId = scheduledTask.id; } - return this.getAlertFromRaw(createdAlert.id, createdAlert.attributes, references); + return this.getAlertFromRaw( + createdAlert.id, + createdAlert.attributes, + createdAlert.updated_at, + references + ); } public async get({ id }: { id: string }): Promise { @@ -302,7 +304,7 @@ export class AlertsClient { result.attributes.consumer, ReadOperations.Get ); - return this.getAlertFromRaw(result.id, result.attributes, result.references); + return this.getAlertFromRaw(result.id, result.attributes, result.updated_at, result.references); } public async getAlertState({ id }: { id: string }): Promise { @@ -391,11 +393,13 @@ export class AlertsClient { type: 'alert', }); - const authorizedData = data.map(({ id, attributes, references }) => { + // eslint-disable-next-line @typescript-eslint/naming-convention + const authorizedData = data.map(({ id, attributes, updated_at, references }) => { ensureAlertTypeIsAuthorized(attributes.alertTypeId, attributes.consumer); return this.getAlertFromRaw( id, fields ? (pick(attributes, fields) as RawAlert) : attributes, + updated_at, references ); }); @@ -581,7 +585,6 @@ export class AlertsClient { params: validatedAlertTypeParams as RawAlert['params'], actions, updatedBy: username, - updatedAt: new Date().toISOString(), }); try { updatedObject = await this.unsecuredSavedObjectsClient.create( @@ -604,7 +607,12 @@ export class AlertsClient { throw e; } - return this.getPartialAlertFromRaw(id, updatedObject.attributes, updatedObject.references); + return this.getPartialAlertFromRaw( + id, + updatedObject.attributes, + updatedObject.updated_at, + updatedObject.references + ); } private apiKeyAsAlertAttributes( @@ -669,7 +677,6 @@ export class AlertsClient { await this.createAPIKey(this.generateAPIKeyName(attributes.alertTypeId, attributes.name)), username ), - updatedAt: new Date().toISOString(), updatedBy: username, }); try { @@ -744,7 +751,6 @@ export class AlertsClient { username ), updatedBy: username, - updatedAt: new Date().toISOString(), }); try { await this.unsecuredSavedObjectsClient.update('alert', id, updateAttributes, { version }); @@ -823,7 +829,6 @@ export class AlertsClient { apiKey: null, apiKeyOwner: null, updatedBy: await this.getUserName(), - updatedAt: new Date().toISOString(), }), { version } ); @@ -870,7 +875,6 @@ export class AlertsClient { muteAll: true, mutedInstanceIds: [], updatedBy: await this.getUserName(), - updatedAt: new Date().toISOString(), }); const updateOptions = { version }; @@ -909,7 +913,6 @@ export class AlertsClient { muteAll: false, mutedInstanceIds: [], updatedBy: await this.getUserName(), - updatedAt: new Date().toISOString(), }); const updateOptions = { version }; @@ -954,7 +957,6 @@ export class AlertsClient { this.updateMeta({ mutedInstanceIds, updatedBy: await this.getUserName(), - updatedAt: new Date().toISOString(), }), { version } ); @@ -997,7 +999,6 @@ export class AlertsClient { alertId, this.updateMeta({ updatedBy: await this.getUserName(), - updatedAt: new Date().toISOString(), mutedInstanceIds: mutedInstanceIds.filter((id: string) => id !== alertInstanceId), }), { version } @@ -1049,17 +1050,19 @@ export class AlertsClient { private getAlertFromRaw( id: string, rawAlert: RawAlert, + updatedAt: SavedObject['updated_at'], references: SavedObjectReference[] | undefined ): Alert { // In order to support the partial update API of Saved Objects we have to support // partial updates of an Alert, but when we receive an actual RawAlert, it is safe // to cast the result to an Alert - return this.getPartialAlertFromRaw(id, rawAlert, references) as Alert; + return this.getPartialAlertFromRaw(id, rawAlert, updatedAt, references) as Alert; } private getPartialAlertFromRaw( id: string, - { createdAt, updatedAt, meta, scheduledTaskId, ...rawAlert }: Partial, + { createdAt, meta, scheduledTaskId, ...rawAlert }: Partial, + updatedAt: SavedObject['updated_at'] = createdAt, references: SavedObjectReference[] | undefined ): PartialAlert { // Not the prettiest code here, but if we want to use most of the diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/create.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/create.test.ts index 6d259029ac48..ee407b1a6d50 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/create.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/create.test.ts @@ -196,7 +196,6 @@ describe('create()', () => { createdAt: '2019-02-12T21:01:22.479Z', createdBy: 'elastic', updatedBy: 'elastic', - updatedAt: '2019-02-12T21:01:22.479Z', muteAll: false, mutedInstanceIds: [], actions: [ @@ -331,7 +330,6 @@ describe('create()', () => { "foo", ], "throttle": null, - "updatedAt": "2019-02-12T21:01:22.479Z", "updatedBy": "elastic", } `); @@ -420,7 +418,6 @@ describe('create()', () => { bar: true, }, createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), actions: [ { group: 'default', @@ -558,7 +555,6 @@ describe('create()', () => { bar: true, }, createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), actions: [ { group: 'default', @@ -635,7 +631,6 @@ describe('create()', () => { bar: true, }, createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), actions: [ { group: 'default', @@ -976,7 +971,6 @@ describe('create()', () => { createdBy: 'elastic', createdAt: '2019-02-12T21:01:22.479Z', updatedBy: 'elastic', - updatedAt: '2019-02-12T21:01:22.479Z', enabled: true, meta: { versionApiKeyLastmodified: 'v7.10.0', @@ -1098,7 +1092,6 @@ describe('create()', () => { createdBy: 'elastic', createdAt: '2019-02-12T21:01:22.479Z', updatedBy: 'elastic', - updatedAt: '2019-02-12T21:01:22.479Z', enabled: false, meta: { versionApiKeyLastmodified: 'v7.10.0', diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/disable.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/disable.test.ts index 8c9ab9494a50..11ce0027f82d 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/disable.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/disable.test.ts @@ -12,7 +12,7 @@ import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/s import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; import { AlertsAuthorization } from '../../authorization/alerts_authorization'; import { ActionsAuthorization } from '../../../../actions/server'; -import { getBeforeSetup, setGlobalDate } from './lib'; +import { getBeforeSetup } from './lib'; import { InvalidatePendingApiKey } from '../../types'; const taskManager = taskManagerMock.createStart(); @@ -45,8 +45,6 @@ beforeEach(() => { getBeforeSetup(alertsClientParams, taskManager, alertTypeRegistry); }); -setGlobalDate(); - describe('disable()', () => { let alertsClient: AlertsClient; const existingAlert = { @@ -138,7 +136,6 @@ describe('disable()', () => { scheduledTaskId: null, apiKey: null, apiKeyOwner: null, - updatedAt: '2019-02-12T21:01:22.479Z', updatedBy: 'elastic', actions: [ { @@ -193,7 +190,6 @@ describe('disable()', () => { scheduledTaskId: null, apiKey: null, apiKeyOwner: null, - updatedAt: '2019-02-12T21:01:22.479Z', updatedBy: 'elastic', actions: [ { diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/enable.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/enable.test.ts index feec1d1b9334..16e83c42d893 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/enable.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/enable.test.ts @@ -13,7 +13,7 @@ import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; import { AlertsAuthorization } from '../../authorization/alerts_authorization'; import { ActionsAuthorization } from '../../../../actions/server'; import { TaskStatus } from '../../../../task_manager/server'; -import { getBeforeSetup, setGlobalDate } from './lib'; +import { getBeforeSetup } from './lib'; import { InvalidatePendingApiKey } from '../../types'; const taskManager = taskManagerMock.createStart(); @@ -46,8 +46,6 @@ beforeEach(() => { getBeforeSetup(alertsClientParams, taskManager, alertTypeRegistry); }); -setGlobalDate(); - describe('enable()', () => { let alertsClient: AlertsClient; const existingAlert = { @@ -188,7 +186,6 @@ describe('enable()', () => { meta: { versionApiKeyLastmodified: kibanaVersion, }, - updatedAt: '2019-02-12T21:01:22.479Z', updatedBy: 'elastic', apiKey: null, apiKeyOwner: null, @@ -295,7 +292,6 @@ describe('enable()', () => { apiKey: Buffer.from('123:abc').toString('base64'), apiKeyOwner: 'elastic', updatedBy: 'elastic', - updatedAt: '2019-02-12T21:01:22.479Z', actions: [ { group: 'default', diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/find.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/find.test.ts index 3d7473a74698..1b3a776bd23e 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/find.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/find.test.ts @@ -79,7 +79,6 @@ describe('find()', () => { bar: true, }, createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), actions: [ { group: 'default', diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/get.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/get.test.ts index 3f0c783f424d..5c0d80f159b3 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/get.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/get.test.ts @@ -59,7 +59,6 @@ describe('get()', () => { bar: true, }, createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), actions: [ { group: 'default', diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_instance_summary.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_instance_summary.test.ts index 9bd61c0fe66d..269b2eb2ab7a 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_instance_summary.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_instance_summary.test.ts @@ -76,7 +76,6 @@ const BaseAlertInstanceSummarySavedObject: SavedObject = { createdBy: null, updatedBy: null, createdAt: mockedDateString, - updatedAt: mockedDateString, apiKey: null, apiKeyOwner: null, throttle: null, diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/mute_all.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/mute_all.test.ts index 14ebca213558..868fa3d8c6aa 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/mute_all.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/mute_all.test.ts @@ -12,7 +12,7 @@ import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/s import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; import { AlertsAuthorization } from '../../authorization/alerts_authorization'; import { ActionsAuthorization } from '../../../../actions/server'; -import { getBeforeSetup, setGlobalDate } from './lib'; +import { getBeforeSetup } from './lib'; const taskManager = taskManagerMock.createStart(); const alertTypeRegistry = alertTypeRegistryMock.create(); @@ -43,8 +43,6 @@ beforeEach(() => { getBeforeSetup(alertsClientParams, taskManager, alertTypeRegistry); }); -setGlobalDate(); - describe('muteAll()', () => { test('mutes an alert', async () => { const alertsClient = new AlertsClient(alertsClientParams); @@ -76,7 +74,6 @@ describe('muteAll()', () => { { muteAll: true, mutedInstanceIds: [], - updatedAt: '2019-02-12T21:01:22.479Z', updatedBy: 'elastic', }, { diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/mute_instance.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/mute_instance.test.ts index c2188f128cb4..05ca741f480c 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/mute_instance.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/mute_instance.test.ts @@ -12,7 +12,7 @@ import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/s import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; import { AlertsAuthorization } from '../../authorization/alerts_authorization'; import { ActionsAuthorization } from '../../../../actions/server'; -import { getBeforeSetup, setGlobalDate } from './lib'; +import { getBeforeSetup } from './lib'; const taskManager = taskManagerMock.createStart(); const alertTypeRegistry = alertTypeRegistryMock.create(); @@ -44,8 +44,6 @@ beforeEach(() => { getBeforeSetup(alertsClientParams, taskManager, alertTypeRegistry); }); -setGlobalDate(); - describe('muteInstance()', () => { test('mutes an alert instance', async () => { const alertsClient = new AlertsClient(alertsClientParams); @@ -70,7 +68,6 @@ describe('muteInstance()', () => { '1', { mutedInstanceIds: ['2'], - updatedAt: '2019-02-12T21:01:22.479Z', updatedBy: 'elastic', }, { diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/unmute_all.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/unmute_all.test.ts index d92304ab873b..5ef1af9b6f0e 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/unmute_all.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/unmute_all.test.ts @@ -12,7 +12,7 @@ import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/s import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; import { AlertsAuthorization } from '../../authorization/alerts_authorization'; import { ActionsAuthorization } from '../../../../actions/server'; -import { getBeforeSetup, setGlobalDate } from './lib'; +import { getBeforeSetup } from './lib'; const taskManager = taskManagerMock.createStart(); const alertTypeRegistry = alertTypeRegistryMock.create(); @@ -44,8 +44,6 @@ beforeEach(() => { getBeforeSetup(alertsClientParams, taskManager, alertTypeRegistry); }); -setGlobalDate(); - describe('unmuteAll()', () => { test('unmutes an alert', async () => { const alertsClient = new AlertsClient(alertsClientParams); @@ -77,7 +75,6 @@ describe('unmuteAll()', () => { { muteAll: false, mutedInstanceIds: [], - updatedAt: '2019-02-12T21:01:22.479Z', updatedBy: 'elastic', }, { diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/unmute_instance.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/unmute_instance.test.ts index 3486df98f2f0..88692239ac2f 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/unmute_instance.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/unmute_instance.test.ts @@ -12,7 +12,7 @@ import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/s import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; import { AlertsAuthorization } from '../../authorization/alerts_authorization'; import { ActionsAuthorization } from '../../../../actions/server'; -import { getBeforeSetup, setGlobalDate } from './lib'; +import { getBeforeSetup } from './lib'; const taskManager = taskManagerMock.createStart(); const alertTypeRegistry = alertTypeRegistryMock.create(); @@ -44,8 +44,6 @@ beforeEach(() => { getBeforeSetup(alertsClientParams, taskManager, alertTypeRegistry); }); -setGlobalDate(); - describe('unmuteInstance()', () => { test('unmutes an alert instance', async () => { const alertsClient = new AlertsClient(alertsClientParams); @@ -71,7 +69,6 @@ describe('unmuteInstance()', () => { { mutedInstanceIds: [], updatedBy: 'elastic', - updatedAt: '2019-02-12T21:01:22.479Z', }, { version: '123' } ); diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/update.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/update.test.ts index d0bb2607f7a4..ad58e36ade72 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/update.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/update.test.ts @@ -140,8 +140,8 @@ describe('update()', () => { ], scheduledTaskId: 'task-123', createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), }, + updated_at: new Date().toISOString(), references: [ { name: 'action_0', @@ -300,7 +300,6 @@ describe('update()', () => { "foo", ], "throttle": null, - "updatedAt": "2019-02-12T21:01:22.479Z", "updatedBy": "elastic", } `); @@ -363,7 +362,6 @@ describe('update()', () => { bar: true, }, createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), actions: [ { group: 'default', @@ -486,7 +484,6 @@ describe('update()', () => { "foo", ], "throttle": "5m", - "updatedAt": "2019-02-12T21:01:22.479Z", "updatedBy": "elastic", } `); @@ -537,7 +534,6 @@ describe('update()', () => { bar: true, }, createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), actions: [ { group: 'default', @@ -652,7 +648,6 @@ describe('update()', () => { "foo", ], "throttle": "5m", - "updatedAt": "2019-02-12T21:01:22.479Z", "updatedBy": "elastic", } `); diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/update_api_key.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/update_api_key.test.ts index ca5f44078f51..af178a1fac5f 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/update_api_key.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/update_api_key.test.ts @@ -12,7 +12,7 @@ import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/s import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; import { AlertsAuthorization } from '../../authorization/alerts_authorization'; import { ActionsAuthorization } from '../../../../actions/server'; -import { getBeforeSetup, setGlobalDate } from './lib'; +import { getBeforeSetup } from './lib'; import { InvalidatePendingApiKey } from '../../types'; const taskManager = taskManagerMock.createStart(); @@ -44,8 +44,6 @@ beforeEach(() => { getBeforeSetup(alertsClientParams, taskManager, alertTypeRegistry); }); -setGlobalDate(); - describe('updateApiKey()', () => { let alertsClient: AlertsClient; const existingAlert = { @@ -115,7 +113,6 @@ describe('updateApiKey()', () => { apiKey: Buffer.from('234:abc').toString('base64'), apiKeyOwner: 'elastic', updatedBy: 'elastic', - updatedAt: '2019-02-12T21:01:22.479Z', actions: [ { group: 'default', @@ -165,7 +162,6 @@ describe('updateApiKey()', () => { enabled: true, apiKey: Buffer.from('234:abc').toString('base64'), apiKeyOwner: 'elastic', - updatedAt: '2019-02-12T21:01:22.479Z', updatedBy: 'elastic', actions: [ { diff --git a/x-pack/plugins/alerts/server/saved_objects/index.ts b/x-pack/plugins/alerts/server/saved_objects/index.ts index dfe122f56bc4..da30273e93c6 100644 --- a/x-pack/plugins/alerts/server/saved_objects/index.ts +++ b/x-pack/plugins/alerts/server/saved_objects/index.ts @@ -16,7 +16,6 @@ export const AlertAttributesExcludedFromAAD = [ 'muteAll', 'mutedInstanceIds', 'updatedBy', - 'updatedAt', 'executionStatus', ]; @@ -29,7 +28,6 @@ export type AlertAttributesExcludedFromAADType = | 'muteAll' | 'mutedInstanceIds' | 'updatedBy' - | 'updatedAt' | 'executionStatus'; export function setupSavedObjects( diff --git a/x-pack/plugins/alerts/server/saved_objects/mappings.json b/x-pack/plugins/alerts/server/saved_objects/mappings.json index f40a7d9075ee..a6c92080f18b 100644 --- a/x-pack/plugins/alerts/server/saved_objects/mappings.json +++ b/x-pack/plugins/alerts/server/saved_objects/mappings.json @@ -62,9 +62,6 @@ "createdAt": { "type": "date" }, - "updatedAt": { - "type": "date" - }, "apiKey": { "type": "binary" }, diff --git a/x-pack/plugins/alerts/server/saved_objects/migrations.test.ts b/x-pack/plugins/alerts/server/saved_objects/migrations.test.ts index a4cbc18e13b4..8c9d10769b18 100644 --- a/x-pack/plugins/alerts/server/saved_objects/migrations.test.ts +++ b/x-pack/plugins/alerts/server/saved_objects/migrations.test.ts @@ -261,48 +261,8 @@ describe('7.10.0 migrates with failure', () => { }); }); -describe('7.11.0', () => { - beforeEach(() => { - jest.resetAllMocks(); - encryptedSavedObjectsSetup.createMigration.mockImplementation( - (shouldMigrateWhenPredicate, migration) => migration - ); - }); - - test('add updatedAt field to alert - set to SavedObject updated_at attribute', () => { - const migration711 = getMigrations(encryptedSavedObjectsSetup)['7.11.0']; - const alert = getMockData({}, true); - expect(migration711(alert, { log })).toEqual({ - ...alert, - attributes: { - ...alert.attributes, - updatedAt: alert.updated_at, - }, - }); - }); - - test('add updatedAt field to alert - set to createdAt when SavedObject updated_at is not defined', () => { - const migration711 = getMigrations(encryptedSavedObjectsSetup)['7.11.0']; - const alert = getMockData({}); - expect(migration711(alert, { log })).toEqual({ - ...alert, - attributes: { - ...alert.attributes, - updatedAt: alert.attributes.createdAt, - }, - }); - }); -}); - -function getUpdatedAt(): string { - const updatedAt = new Date(); - updatedAt.setHours(updatedAt.getHours() + 2); - return updatedAt.toISOString(); -} - function getMockData( - overwrites: Record = {}, - withSavedObjectUpdatedAt: boolean = false + overwrites: Record = {} ): SavedObjectUnsanitizedDoc> { return { attributes: { @@ -335,7 +295,6 @@ function getMockData( ], ...overwrites, }, - updated_at: withSavedObjectUpdatedAt ? getUpdatedAt() : undefined, id: uuid.v4(), type: 'alert', }; diff --git a/x-pack/plugins/alerts/server/saved_objects/migrations.ts b/x-pack/plugins/alerts/server/saved_objects/migrations.ts index d8ebced03c5a..0b2c86b84f67 100644 --- a/x-pack/plugins/alerts/server/saved_objects/migrations.ts +++ b/x-pack/plugins/alerts/server/saved_objects/migrations.ts @@ -37,15 +37,8 @@ export function getMigrations( ) ); - const migrationAlertUpdatedAtDate = encryptedSavedObjects.createMigration( - // migrate all documents in 7.11 in order to add the "updatedAt" field - (doc): doc is SavedObjectUnsanitizedDoc => true, - pipeMigrations(setAlertUpdatedAtDate) - ); - return { '7.10.0': executeMigrationWithErrorHandling(migrationWhenRBACWasIntroduced, '7.10.0'), - '7.11.0': executeMigrationWithErrorHandling(migrationAlertUpdatedAtDate, '7.11.0'), }; } @@ -66,19 +59,6 @@ function executeMigrationWithErrorHandling( }; } -const setAlertUpdatedAtDate = ( - doc: SavedObjectUnsanitizedDoc -): SavedObjectUnsanitizedDoc => { - const updatedAt = doc.updated_at || doc.attributes.createdAt; - return { - ...doc, - attributes: { - ...doc.attributes, - updatedAt, - }, - }; -}; - const consumersToChange: Map = new Map( Object.entries({ alerting: 'alerts', diff --git a/x-pack/plugins/alerts/server/saved_objects/partially_update_alert.test.ts b/x-pack/plugins/alerts/server/saved_objects/partially_update_alert.test.ts index 8041ec551bb0..50815c797e39 100644 --- a/x-pack/plugins/alerts/server/saved_objects/partially_update_alert.test.ts +++ b/x-pack/plugins/alerts/server/saved_objects/partially_update_alert.test.ts @@ -95,7 +95,6 @@ const DefaultAttributes = { muteAll: true, mutedInstanceIds: ['muted-instance-id-1', 'muted-instance-id-2'], updatedBy: 'someone', - updatedAt: '2019-02-12T21:01:22.479Z', }; const InvalidAttributes = { ...DefaultAttributes, foo: 'bar' }; diff --git a/x-pack/plugins/alerts/server/types.ts b/x-pack/plugins/alerts/server/types.ts index 4ccf251540a1..dde162815665 100644 --- a/x-pack/plugins/alerts/server/types.ts +++ b/x-pack/plugins/alerts/server/types.ts @@ -148,7 +148,6 @@ export interface RawAlert extends SavedObjectAttributes { createdBy: string | null; updatedBy: string | null; createdAt: string; - updatedAt: string; apiKey: string | null; apiKeyOwner: string | null; throttle: string | null; diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts index cf7fc9edd952..41f6b66c30aa 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts @@ -91,7 +91,6 @@ export default function createAlertTests({ getService }: FtrProviderContext) { }); expect(Date.parse(response.body.createdAt)).to.be.greaterThan(0); expect(Date.parse(response.body.updatedAt)).to.be.greaterThan(0); - expect(Date.parse(response.body.updatedAt)).to.eql(Date.parse(response.body.createdAt)); expect(typeof response.body.scheduledTaskId).to.be('string'); const { _source: taskRecord } = await getScheduledTask(response.body.scheduledTaskId); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/execution_status.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/execution_status.ts index 642173a7c2c6..5ebce8edf6fb 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/execution_status.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/execution_status.ts @@ -63,7 +63,6 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon ); expect(response.status).to.eql(200); const alertId = response.body.id; - const alertUpdatedAt = response.body.updatedAt; dates.push(response.body.executionStatus.lastExecutionDate); objectRemover.add(Spaces.space1.id, alertId, 'alert', 'alerts'); @@ -71,7 +70,6 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon dates.push(executionStatus.lastExecutionDate); dates.push(Date.now()); ensureDatetimesAreOrdered(dates); - ensureAlertUpdatedAtHasNotChanged(alertId, alertUpdatedAt); // Ensure AAD isn't broken await checkAAD({ @@ -99,7 +97,6 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon ); expect(response.status).to.eql(200); const alertId = response.body.id; - const alertUpdatedAt = response.body.updatedAt; dates.push(response.body.executionStatus.lastExecutionDate); objectRemover.add(Spaces.space1.id, alertId, 'alert', 'alerts'); @@ -107,7 +104,6 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon dates.push(executionStatus.lastExecutionDate); dates.push(Date.now()); ensureDatetimesAreOrdered(dates); - ensureAlertUpdatedAtHasNotChanged(alertId, alertUpdatedAt); // Ensure AAD isn't broken await checkAAD({ @@ -132,7 +128,6 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon ); expect(response.status).to.eql(200); const alertId = response.body.id; - const alertUpdatedAt = response.body.updatedAt; dates.push(response.body.executionStatus.lastExecutionDate); objectRemover.add(Spaces.space1.id, alertId, 'alert', 'alerts'); @@ -140,7 +135,6 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon dates.push(executionStatus.lastExecutionDate); dates.push(Date.now()); ensureDatetimesAreOrdered(dates); - ensureAlertUpdatedAtHasNotChanged(alertId, alertUpdatedAt); // Ensure AAD isn't broken await checkAAD({ @@ -168,14 +162,12 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon ); expect(response.status).to.eql(200); const alertId = response.body.id; - const alertUpdatedAt = response.body.updatedAt; objectRemover.add(Spaces.space1.id, alertId, 'alert', 'alerts'); const executionStatus = await waitForStatus(alertId, new Set(['error'])); expect(executionStatus.error).to.be.ok(); expect(executionStatus.error.reason).to.be('execute'); expect(executionStatus.error.message).to.be('this alert is intended to fail'); - ensureAlertUpdatedAtHasNotChanged(alertId, alertUpdatedAt); }); it('should eventually have error reason "unknown" when appropriate', async () => { @@ -191,7 +183,6 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon ); expect(response.status).to.eql(200); const alertId = response.body.id; - const alertUpdatedAt = response.body.updatedAt; objectRemover.add(Spaces.space1.id, alertId, 'alert', 'alerts'); let executionStatus = await waitForStatus(alertId, new Set(['ok'])); @@ -210,7 +201,6 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon executionStatus = await waitForStatus(alertId, new Set(['error'])); expect(executionStatus.error).to.be.ok(); expect(executionStatus.error.reason).to.be('unknown'); - ensureAlertUpdatedAtHasNotChanged(alertId, alertUpdatedAt); const message = 'params invalid: [param1]: expected value of type [string] but got [number]'; expect(executionStatus.error.message).to.be(message); @@ -316,18 +306,6 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon await delay(WaitForStatusIncrement); return await waitForStatus(id, statuses, waitMillis - WaitForStatusIncrement); } - - async function ensureAlertUpdatedAtHasNotChanged(alertId: string, originalUpdatedAt: string) { - const response = await supertest.get( - `${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${alertId}` - ); - const { updatedAt, executionStatus } = response.body; - expect(Date.parse(updatedAt)).to.be.greaterThan(0); - expect(Date.parse(updatedAt)).to.eql(Date.parse(originalUpdatedAt)); - expect(Date.parse(executionStatus.lastExecutionDate)).to.be.greaterThan( - Date.parse(originalUpdatedAt) - ); - } } function expectErrorExecutionStatus(executionStatus: Record, startDate: number) { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/migrations.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/migrations.ts index bd6afacf206d..17070a14069c 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/migrations.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/migrations.ts @@ -82,14 +82,5 @@ export default function createGetTests({ getService }: FtrProviderContext) { }, ]); }); - - it('7.11.0 migrates alerts to contain `updatedAt` field', async () => { - const response = await supertest.get( - `${getUrlPrefix(``)}/api/alerts/alert/74f3e6d7-b7bb-477d-ac28-92ee22728e6e` - ); - - expect(response.status).to.eql(200); - expect(response.body.updatedAt).to.eql('2020-06-17T15:35:39.839Z'); - }); }); } From 514b50e4c2d7a3be79d77e73838ff57b6cf1304a Mon Sep 17 00:00:00 2001 From: John Schulz Date: Thu, 19 Nov 2020 07:19:41 -0500 Subject: [PATCH 38/49] Forward any registry cache-control header for files (#83680) closes #83631 ### Problem Assets are served with a `cache-control` header that prevents any caching ### Root cause Likely from this code https://github.com/elastic/kibana/blob/2a365ff6329544465227e61141ded6fba8bb2c80/src/core/server/http/http_tools.ts#L40-L43 Also based on these tests, it seems this is default/expected behavior https://github.com/elastic/kibana/blob/b3eefb97da8e712789b5c5d2eeae65c886ed8f64/src/core/server/http/integration_tests/router.test.ts#L510-L520 ### Proposed solution Set the header via the response handler as shown in this test: https://github.com/elastic/kibana/blob/b3eefb97da8e712789b5c5d2eeae65c886ed8f64/src/core/server/http/integration_tests/router.test.ts#L522-L536 ### This PR If this registry response contains a `cache-control` header, that value is included in the EPM response as well In `master`, which points to `epr-snapshot` Screen Shot 2020-11-18 at 12 33 47 PM which matches https://epr-snapshot.elastic.co/package/apache/0.2.6/img/logo_apache.svg or using `epr.elastic.co`, Screen Shot 2020-11-18 at 12 31 56 PM which matches https://epr.elastic.co/package/apache/0.2.6/img/logo_apache.svg --- .../fleet/server/routes/epm/handlers.ts | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/fleet/server/routes/epm/handlers.ts b/x-pack/plugins/fleet/server/routes/epm/handlers.ts index 1d221b8b1eea..ce03d0eeb382 100644 --- a/x-pack/plugins/fleet/server/routes/epm/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/epm/handlers.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { TypeOf } from '@kbn/config-schema'; -import { RequestHandler, CustomHttpResponseOptions } from 'src/core/server'; +import { RequestHandler, ResponseHeaders, KnownHeaders } from 'src/core/server'; import { GetInfoResponse, InstallPackageResponse, @@ -103,15 +103,21 @@ export const getFileHandler: RequestHandler = { + + const headersToProxy: KnownHeaders[] = ['content-type', 'cache-control']; + const proxiedHeaders = headersToProxy.reduce((headers, knownHeader) => { + const value = registryResponse.headers.get(knownHeader); + if (value !== null) { + headers[knownHeader] = value; + } + return headers; + }, {} as ResponseHeaders); + + return response.custom({ body: registryResponse.body, statusCode: registryResponse.status, - }; - if (contentType !== null) { - customResponseObj.headers = { 'Content-Type': contentType }; - } - return response.custom(customResponseObj); + headers: proxiedHeaders, + }); } catch (error) { return defaultIngestErrorHandler({ error, response }); } From ffdc507668740ec7af69c9f45e3dc7793507092f Mon Sep 17 00:00:00 2001 From: Gidi Meir Morris Date: Thu, 19 Nov 2020 12:50:26 +0000 Subject: [PATCH 39/49] fixed pagination in connectors list (#83638) Ensures we specify the page on the EuiTable so that pagination is retain after rerenders. --- .../actions_connectors_list.test.tsx | 88 +++++++++++++------ .../components/actions_connectors_list.tsx | 12 ++- 2 files changed, 72 insertions(+), 28 deletions(-) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx index 71e1c60a92ae..226b9de8b677 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx @@ -16,6 +16,8 @@ import { chartPluginMock } from '../../../../../../../../src/plugins/charts/publ import { dataPluginMock } from '../../../../../../../../src/plugins/data/public/mocks'; import { alertingPluginMock } from '../../../../../../alerts/public/mocks'; import { featuresPluginMock } from '../../../../../../features/public/mocks'; +import { ActionConnector } from '../../../../types'; +import { times } from 'lodash'; jest.mock('../../../lib/action_connector_api', () => ({ loadAllActions: jest.fn(), @@ -109,36 +111,38 @@ describe('actions_connectors_list component empty', () => { describe('actions_connectors_list component with items', () => { let wrapper: ReactWrapper; - async function setup() { + async function setup(actionConnectors?: ActionConnector[]) { const { loadAllActions, loadActionTypes } = jest.requireMock( '../../../lib/action_connector_api' ); - loadAllActions.mockResolvedValueOnce([ - { - id: '1', - actionTypeId: 'test', - description: 'My test', - isPreconfigured: false, - referencedByCount: 1, - config: {}, - }, - { - id: '2', - actionTypeId: 'test2', - description: 'My test 2', - referencedByCount: 1, - isPreconfigured: false, - config: {}, - }, - { - id: '3', - actionTypeId: 'test2', - description: 'My preconfigured test 2', - referencedByCount: 1, - isPreconfigured: true, - config: {}, - }, - ]); + loadAllActions.mockResolvedValueOnce( + actionConnectors ?? [ + { + id: '1', + actionTypeId: 'test', + description: 'My test', + isPreconfigured: false, + referencedByCount: 1, + config: {}, + }, + { + id: '2', + actionTypeId: 'test2', + description: 'My test 2', + referencedByCount: 1, + isPreconfigured: false, + config: {}, + }, + { + id: '3', + actionTypeId: 'test2', + description: 'My preconfigured test 2', + referencedByCount: 1, + isPreconfigured: true, + config: {}, + }, + ] + ); loadActionTypes.mockResolvedValueOnce([ { id: 'test', @@ -217,6 +221,36 @@ describe('actions_connectors_list component with items', () => { expect(wrapper.find('[data-test-subj="preConfiguredTitleMessage"]')).toHaveLength(2); }); + it('supports pagination', async () => { + await setup( + times(15, (index) => ({ + id: `connector${index}`, + actionTypeId: 'test', + name: `My test ${index}`, + secrets: {}, + description: `My test ${index}`, + isPreconfigured: false, + referencedByCount: 1, + config: {}, + })) + ); + expect(wrapper.find('[data-test-subj="actionsTable"]').first().prop('pagination')) + .toMatchInlineSnapshot(` + Object { + "initialPageIndex": 0, + "pageIndex": 0, + } + `); + wrapper.find('[data-test-subj="pagination-button-1"]').first().simulate('click'); + expect(wrapper.find('[data-test-subj="actionsTable"]').first().prop('pagination')) + .toMatchInlineSnapshot(` + Object { + "initialPageIndex": 0, + "pageIndex": 1, + } + `); + }); + test('if select item for edit should render ConnectorEditFlyout', async () => { await setup(); await wrapper.find('[data-test-subj="edit1"]').first().simulate('click'); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx index ff5585cf04db..c5d0a6aae54f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx @@ -18,6 +18,7 @@ import { EuiToolTip, EuiButtonIcon, EuiEmptyPrompt, + Criteria, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { omit } from 'lodash'; @@ -54,6 +55,7 @@ export const ActionsConnectorsList: React.FunctionComponent = () => { const [actionTypesIndex, setActionTypesIndex] = useState(undefined); const [actions, setActions] = useState([]); + const [pageIndex, setPageIndex] = useState(0); const [selectedItems, setSelectedItems] = useState([]); const [isLoadingActionTypes, setIsLoadingActionTypes] = useState(false); const [isLoadingActions, setIsLoadingActions] = useState(false); @@ -233,7 +235,15 @@ export const ActionsConnectorsList: React.FunctionComponent = () => { : '', })} data-test-subj="actionsTable" - pagination={true} + pagination={{ + initialPageIndex: 0, + pageIndex, + }} + onTableChange={({ page }: Criteria) => { + if (page) { + setPageIndex(page.index); + } + }} selection={ canDelete ? { From 1b6cfe819d4e68553a6bf84b3cee2712eb05cb7c Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Thu, 19 Nov 2020 08:43:14 -0500 Subject: [PATCH 40/49] [Fleet] Rename ingestManager plugin ID fleet (#83200) --- docs/developer/plugin-list.asciidoc | 8 +-- packages/kbn-optimizer/limits.yml | 2 +- .../setup-custom-kibana-user-role.ts | 4 +- x-pack/plugins/fleet/README.md | 4 +- .../plugins/fleet/common/constants/plugin.ts | 2 +- .../common/services/decode_cloud_id.test.ts | 2 +- .../services/is_agent_upgradeable.test.ts | 2 +- .../services/is_diff_path_protocol.test.ts | 2 +- .../services/is_valid_namespace.test.ts | 2 +- .../package_policies_to_agent_inputs.test.ts | 2 +- .../package_to_package_policy.test.ts | 2 +- x-pack/plugins/fleet/common/types/index.ts | 2 +- x-pack/plugins/fleet/kibana.json | 2 +- x-pack/plugins/fleet/package.json | 4 +- .../fleet/constants/page_paths.ts | 2 +- .../fleet/hooks/use_capabilities.ts | 2 +- .../applications/fleet/hooks/use_config.ts | 4 +- .../applications/fleet/hooks/use_deps.ts | 6 +- .../fleet/public/applications/fleet/index.tsx | 20 +++---- .../has_invalid_but_required_var.test.ts | 2 +- .../services/is_advanced_var.test.ts | 2 +- .../services/validate_package_policy.test.ts | 4 +- .../components/agent_unenroll_modal/index.tsx | 2 +- .../fleet/sections/agents/index.tsx | 6 +- x-pack/plugins/fleet/public/index.ts | 6 +- x-pack/plugins/fleet/public/plugin.ts | 59 +++++++++++-------- .../server/collectors/config_collectors.ts | 4 +- .../fleet/server/collectors/register.ts | 4 +- x-pack/plugins/fleet/server/index.ts | 13 ++-- x-pack/plugins/fleet/server/mocks.ts | 4 +- x-pack/plugins/fleet/server/plugin.ts | 54 ++++++++--------- .../fleet/server/routes/agent/index.ts | 4 +- .../server/routes/limited_concurrency.test.ts | 8 +-- .../server/routes/limited_concurrency.ts | 4 +- .../server/routes/setup/handlers.test.ts | 10 ++-- .../fleet/server/routes/setup/handlers.ts | 2 +- .../fleet/server/routes/setup/index.ts | 16 ++--- .../fleet/server/services/app_context.ts | 16 ++--- .../plugins/fleet/server/services/config.ts | 10 ++-- .../home/data_streams_tab.test.ts | 6 +- x-pack/plugins/index_management/kibana.json | 14 +---- .../public/application/app_context.tsx | 4 +- .../application/mount_management_section.ts | 6 +- .../data_stream_list/data_stream_list.tsx | 8 +-- .../plugins/index_management/public/plugin.ts | 4 +- .../plugins/index_management/public/types.ts | 4 +- .../index.tsx | 16 ++--- .../public/pages/landing/index.tsx | 4 +- x-pack/plugins/security_solution/kibana.json | 4 +- .../public/app/home/setup.tsx | 10 ++-- .../__snapshots__/link_to_app.test.tsx.snap | 10 ++-- .../components/endpoint/link_to_app.test.tsx | 28 ++++----- .../common/hooks/endpoint/ingest_enabled.ts | 12 ++-- .../use_navigate_to_app_event_handler.ts | 2 +- .../mock/endpoint/app_context_render.tsx | 2 +- .../mock/endpoint/app_root_provider.tsx | 2 +- .../mock/endpoint/dependencies_start_mock.ts | 6 +- .../public/common/store/types.ts | 4 +- .../pages/endpoint_hosts/view/hooks.ts | 12 ++-- .../pages/endpoint_hosts/view/index.test.tsx | 18 +++--- .../pages/endpoint_hosts/view/index.tsx | 16 ++--- .../endpoint_policy_edit_extension.tsx | 14 ++--- .../pages/policy/view/policy_list.tsx | 6 +- .../security_solution/public/plugin.tsx | 4 +- .../plugins/security_solution/public/types.ts | 4 +- .../endpoint/endpoint_app_context_services.ts | 6 +- .../server/endpoint/mocks.ts | 10 ++-- .../endpoint/routes/metadata/metadata.test.ts | 2 +- .../routes/metadata/metadata_v1.test.ts | 2 +- .../security_solution/server/plugin.ts | 14 ++--- .../translations/translations/ja-JP.json | 10 ---- .../translations/translations/zh-CN.json | 10 ---- .../apis/features/features/features.ts | 2 +- .../apis/security/privileges.ts | 2 +- .../apis/security/privileges_basic.ts | 2 +- .../apis/agents/delete.ts | 4 +- .../fleet_api_integration/apis/agents/list.ts | 4 +- ...gest_manager_create_package_policy_page.ts | 2 +- 78 files changed, 277 insertions(+), 317 deletions(-) rename x-pack/plugins/observability/public/components/app/{ingest_manager_panel => fleet_panel}/index.tsx (74%) diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index 198b0372d925..5ee713161058 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -377,6 +377,10 @@ and actions. |Backend and core front-end react-components for GeoJson file upload. Only supports the Maps plugin. +|{kib-repo}blob/{branch}/x-pack/plugins/fleet/README.md[fleet] +|Fleet needs to have Elasticsearch API keys enabled, and also to have TLS enabled on kibana, (if you want to run Kibana without TLS you can provide the following config flag --xpack.fleet.agents.tlsCheckDisabled=false) + + |{kib-repo}blob/{branch}/x-pack/plugins/global_search/README.md[globalSearch] |The GlobalSearch plugin provides an easy way to search for various objects, such as applications or dashboards from the Kibana instance, from both server and client-side plugins @@ -413,10 +417,6 @@ Index Management by running this series of requests in Console: the infrastructure monitoring use-case within Kibana. -|{kib-repo}blob/{branch}/x-pack/plugins/fleet/README.md[ingestManager] -|Fleet needs to have Elasticsearch API keys enabled, and also to have TLS enabled on kibana, (if you want to run Kibana without TLS you can provide the following config flag --xpack.fleet.agents.tlsCheckDisabled=false) - - |{kib-repo}blob/{branch}/x-pack/plugins/ingest_pipelines/README.md[ingestPipelines] |The ingest_pipelines plugin provides Kibana support for Elasticsearch's ingest nodes. Please refer to the Elasticsearch documentation for more details. diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 7cdbe844c290..a97104fcf1a8 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -36,7 +36,7 @@ pageLoadAssetSize: indexManagement: 140608 indexPatternManagement: 154222 infra: 197873 - ingestManager: 415829 + fleet: 415829 ingestPipelines: 58003 inputControlVis: 172675 inspector: 148711 diff --git a/x-pack/plugins/apm/scripts/kibana-security/setup-custom-kibana-user-role.ts b/x-pack/plugins/apm/scripts/kibana-security/setup-custom-kibana-user-role.ts index cf17c9dbbf2e..11383f23964f 100644 --- a/x-pack/plugins/apm/scripts/kibana-security/setup-custom-kibana-user-role.ts +++ b/x-pack/plugins/apm/scripts/kibana-security/setup-custom-kibana-user-role.ts @@ -148,7 +148,7 @@ async function init() { indexPatterns: ['read'], savedObjectsManagement: ['read'], stackAlerts: ['read'], - ingestManager: ['read'], + fleet: ['read'], actions: ['read'], }, }, @@ -181,7 +181,7 @@ async function init() { indexPatterns: ['all'], savedObjectsManagement: ['all'], stackAlerts: ['all'], - ingestManager: ['all'], + fleet: ['all'], actions: ['all'], }, }, diff --git a/x-pack/plugins/fleet/README.md b/x-pack/plugins/fleet/README.md index 614e1aba2ab8..b1f52dbed9cf 100644 --- a/x-pack/plugins/fleet/README.md +++ b/x-pack/plugins/fleet/README.md @@ -1,4 +1,4 @@ -# Ingest Manager +# Fleet ## Plugin @@ -46,6 +46,8 @@ One common development workflow is: This plugin follows the `common`, `server`, `public` structure from the [Architecture Style Guide ](https://github.com/elastic/kibana/blob/master/style_guides/architecture_style_guide.md#file-and-folder-structure). We also follow the pattern of developing feature branches under your personal fork of Kibana. +Note: The plugin was previously named Ingest Manager it's possible that some variables are still named with that old plugin name. + ### Tests #### API integration tests diff --git a/x-pack/plugins/fleet/common/constants/plugin.ts b/x-pack/plugins/fleet/common/constants/plugin.ts index c2390bb43395..e7262761c4dc 100644 --- a/x-pack/plugins/fleet/common/constants/plugin.ts +++ b/x-pack/plugins/fleet/common/constants/plugin.ts @@ -3,4 +3,4 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -export const PLUGIN_ID = 'ingestManager'; +export const PLUGIN_ID = 'fleet'; diff --git a/x-pack/plugins/fleet/common/services/decode_cloud_id.test.ts b/x-pack/plugins/fleet/common/services/decode_cloud_id.test.ts index dcec54f47440..8a5fee3ee217 100644 --- a/x-pack/plugins/fleet/common/services/decode_cloud_id.test.ts +++ b/x-pack/plugins/fleet/common/services/decode_cloud_id.test.ts @@ -5,7 +5,7 @@ */ import { decodeCloudId } from './decode_cloud_id'; -describe('Ingest Manager - decodeCloudId', () => { +describe('Fleet - decodeCloudId', () => { it('parses various CloudID formats', () => { const tests = [ { diff --git a/x-pack/plugins/fleet/common/services/is_agent_upgradeable.test.ts b/x-pack/plugins/fleet/common/services/is_agent_upgradeable.test.ts index dc61f4898478..1a9e5f09f667 100644 --- a/x-pack/plugins/fleet/common/services/is_agent_upgradeable.test.ts +++ b/x-pack/plugins/fleet/common/services/is_agent_upgradeable.test.ts @@ -94,7 +94,7 @@ const getAgent = ({ } return agent; }; -describe('Ingest Manager - isAgentUpgradeable', () => { +describe('Fleet - isAgentUpgradeable', () => { it('returns false if agent reports not upgradeable with agent version < kibana version', () => { expect(isAgentUpgradeable(getAgent({ version: '7.9.0' }), '8.0.0')).toBe(false); }); diff --git a/x-pack/plugins/fleet/common/services/is_diff_path_protocol.test.ts b/x-pack/plugins/fleet/common/services/is_diff_path_protocol.test.ts index c488d552d767..6c49bba49a58 100644 --- a/x-pack/plugins/fleet/common/services/is_diff_path_protocol.test.ts +++ b/x-pack/plugins/fleet/common/services/is_diff_path_protocol.test.ts @@ -5,7 +5,7 @@ */ import { isDiffPathProtocol } from './is_diff_path_protocol'; -describe('Ingest Manager - isDiffPathProtocol', () => { +describe('Fleet - isDiffPathProtocol', () => { it('returns true for different paths', () => { expect( isDiffPathProtocol([ diff --git a/x-pack/plugins/fleet/common/services/is_valid_namespace.test.ts b/x-pack/plugins/fleet/common/services/is_valid_namespace.test.ts index 3ed9e3a087a9..8d60c4aa61dc 100644 --- a/x-pack/plugins/fleet/common/services/is_valid_namespace.test.ts +++ b/x-pack/plugins/fleet/common/services/is_valid_namespace.test.ts @@ -5,7 +5,7 @@ */ import { isValidNamespace } from './is_valid_namespace'; -describe('Ingest Manager - isValidNamespace', () => { +describe('Fleet - isValidNamespace', () => { it('returns true for valid namespaces', () => { expect(isValidNamespace('default').valid).toBe(true); expect(isValidNamespace('namespace-with-dash').valid).toBe(true); diff --git a/x-pack/plugins/fleet/common/services/package_policies_to_agent_inputs.test.ts b/x-pack/plugins/fleet/common/services/package_policies_to_agent_inputs.test.ts index 1df06df1de27..f721afb63914 100644 --- a/x-pack/plugins/fleet/common/services/package_policies_to_agent_inputs.test.ts +++ b/x-pack/plugins/fleet/common/services/package_policies_to_agent_inputs.test.ts @@ -6,7 +6,7 @@ import { PackagePolicy, PackagePolicyInput } from '../types'; import { storedPackagePoliciesToAgentInputs } from './package_policies_to_agent_inputs'; -describe('Ingest Manager - storedPackagePoliciesToAgentInputs', () => { +describe('Fleet - storedPackagePoliciesToAgentInputs', () => { const mockPackagePolicy: PackagePolicy = { id: 'some-uuid', name: 'mock-package-policy', diff --git a/x-pack/plugins/fleet/common/services/package_to_package_policy.test.ts b/x-pack/plugins/fleet/common/services/package_to_package_policy.test.ts index e81207300a5f..ae4de55ffa9a 100644 --- a/x-pack/plugins/fleet/common/services/package_to_package_policy.test.ts +++ b/x-pack/plugins/fleet/common/services/package_to_package_policy.test.ts @@ -7,7 +7,7 @@ import { installationStatuses } from '../constants'; import { PackageInfo } from '../types'; import { packageToPackagePolicy, packageToPackagePolicyInputs } from './package_to_package_policy'; -describe('Ingest Manager - packageToPackagePolicy', () => { +describe('Fleet - packageToPackagePolicy', () => { const mockPackage: PackageInfo = { name: 'mock-package', title: 'Mock package', diff --git a/x-pack/plugins/fleet/common/types/index.ts b/x-pack/plugins/fleet/common/types/index.ts index ba76194b1d9b..e0827ef7cf40 100644 --- a/x-pack/plugins/fleet/common/types/index.ts +++ b/x-pack/plugins/fleet/common/types/index.ts @@ -6,7 +6,7 @@ export * from './models'; export * from './rest_spec'; -export interface IngestManagerConfigType { +export interface FleetConfigType { enabled: boolean; registryUrl?: string; registryProxyUrl?: string; diff --git a/x-pack/plugins/fleet/kibana.json b/x-pack/plugins/fleet/kibana.json index 5ea6d21e1282..81b56682b47e 100644 --- a/x-pack/plugins/fleet/kibana.json +++ b/x-pack/plugins/fleet/kibana.json @@ -1,5 +1,5 @@ { - "id": "ingestManager", + "id": "fleet", "version": "kibana", "server": true, "ui": true, diff --git a/x-pack/plugins/fleet/package.json b/x-pack/plugins/fleet/package.json index d2bb7a1621d9..e374dabb8245 100644 --- a/x-pack/plugins/fleet/package.json +++ b/x-pack/plugins/fleet/package.json @@ -1,7 +1,7 @@ { "author": "Elastic", - "name": "ingest-manager", + "name": "fleet", "version": "8.0.0", "private": true, "license": "Elastic-License" -} \ No newline at end of file +} diff --git a/x-pack/plugins/fleet/public/applications/fleet/constants/page_paths.ts b/x-pack/plugins/fleet/public/applications/fleet/constants/page_paths.ts index 1273fb9b86ca..996375365167 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/constants/page_paths.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/constants/page_paths.ts @@ -31,7 +31,7 @@ export interface DynamicPagePathValues { [key: string]: string; } -export const BASE_PATH = '/app/ingestManager'; +export const BASE_PATH = '/app/fleet'; // If routing paths are changed here, please also check to see if // `pagePathGetters()`, below, needs any modifications diff --git a/x-pack/plugins/fleet/public/applications/fleet/hooks/use_capabilities.ts b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_capabilities.ts index 0a16c4a62a7d..d8535183bb84 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/hooks/use_capabilities.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_capabilities.ts @@ -8,5 +8,5 @@ import { useCore } from './'; export function useCapabilities() { const core = useCore(); - return core.application.capabilities.ingestManager; + return core.application.capabilities.fleet; } diff --git a/x-pack/plugins/fleet/public/applications/fleet/hooks/use_config.ts b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_config.ts index d3f27a180cfd..e12265d16242 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/hooks/use_config.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_config.ts @@ -5,9 +5,9 @@ */ import React, { useContext } from 'react'; -import { IngestManagerConfigType } from '../../../plugin'; +import { FleetConfigType } from '../../../plugin'; -export const ConfigContext = React.createContext(null); +export const ConfigContext = React.createContext(null); export function useConfig() { const config = useContext(ConfigContext); diff --git a/x-pack/plugins/fleet/public/applications/fleet/hooks/use_deps.ts b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_deps.ts index 25e4ee8fca43..bf8f33297882 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/hooks/use_deps.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_deps.ts @@ -5,11 +5,11 @@ */ import React, { useContext } from 'react'; -import { IngestManagerSetupDeps, IngestManagerStartDeps } from '../../../plugin'; +import { FleetSetupDeps, FleetStartDeps } from '../../../plugin'; export const DepsContext = React.createContext<{ - setup: IngestManagerSetupDeps; - start: IngestManagerStartDeps; + setup: FleetSetupDeps; + start: FleetStartDeps; } | null>(null); export function useSetupDeps() { diff --git a/x-pack/plugins/fleet/public/applications/fleet/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/index.tsx index d4e652ad9583..51c897b3661c 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/index.tsx @@ -14,11 +14,7 @@ import { EuiErrorBoundary, EuiPanel, EuiEmptyPrompt, EuiCode } from '@elastic/eu import { CoreStart, AppMountParameters } from 'src/core/public'; import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; import { EuiThemeProvider } from '../../../../xpack_legacy/common'; -import { - IngestManagerSetupDeps, - IngestManagerConfigType, - IngestManagerStartDeps, -} from '../../plugin'; +import { FleetSetupDeps, FleetConfigType, FleetStartDeps } from '../../plugin'; import { PAGE_ROUTING_PATHS } from './constants'; import { DefaultLayout, WithoutHeaderLayout } from './layouts'; import { Loading, Error } from './components'; @@ -241,9 +237,9 @@ const IngestManagerApp = ({ }: { basepath: string; coreStart: CoreStart; - setupDeps: IngestManagerSetupDeps; - startDeps: IngestManagerStartDeps; - config: IngestManagerConfigType; + setupDeps: FleetSetupDeps; + startDeps: FleetStartDeps; + config: FleetConfigType; history: AppMountParameters['history']; kibanaVersion: string; extensions: UIExtensionsStorage; @@ -271,9 +267,9 @@ const IngestManagerApp = ({ export function renderApp( coreStart: CoreStart, { element, appBasePath, history }: AppMountParameters, - setupDeps: IngestManagerSetupDeps, - startDeps: IngestManagerStartDeps, - config: IngestManagerConfigType, + setupDeps: FleetSetupDeps, + startDeps: FleetStartDeps, + config: FleetConfigType, kibanaVersion: string, extensions: UIExtensionsStorage ) { @@ -296,7 +292,7 @@ export function renderApp( }; } -export const teardownIngestManager = (coreStart: CoreStart) => { +export const teardownFleet = (coreStart: CoreStart) => { coreStart.chrome.docTitle.reset(); coreStart.chrome.setBreadcrumbs([]); licenseService.stop(); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/has_invalid_but_required_var.test.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/has_invalid_but_required_var.test.ts index 679ae4b1456d..05eb40fecb1c 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/has_invalid_but_required_var.test.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/has_invalid_but_required_var.test.ts @@ -5,7 +5,7 @@ */ import { hasInvalidButRequiredVar } from './has_invalid_but_required_var'; -describe('Ingest Manager - hasInvalidButRequiredVar', () => { +describe('Fleet - hasInvalidButRequiredVar', () => { it('returns true for invalid & required vars', () => { expect( hasInvalidButRequiredVar( diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/is_advanced_var.test.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/is_advanced_var.test.ts index 67796d69863f..d58068683086 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/is_advanced_var.test.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/is_advanced_var.test.ts @@ -5,7 +5,7 @@ */ import { isAdvancedVar } from './is_advanced_var'; -describe('Ingest Manager - isAdvancedVar', () => { +describe('Fleet - isAdvancedVar', () => { it('returns true for vars that should be show under advanced options', () => { expect( isAdvancedVar({ diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/validate_package_policy.test.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/validate_package_policy.test.ts index 8d46fed1ff14..e3e29134d405 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/validate_package_policy.test.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/services/validate_package_policy.test.ts @@ -7,7 +7,7 @@ import { installationStatuses } from '../../../../../../../common/constants'; import { PackageInfo, NewPackagePolicy, RegistryPolicyTemplate } from '../../../../types'; import { validatePackagePolicy, validationHasErrors } from './validate_package_policy'; -describe('Ingest Manager - validatePackagePolicy()', () => { +describe('Fleet - validatePackagePolicy()', () => { const mockPackage = ({ name: 'mock-package', title: 'Mock package', @@ -496,7 +496,7 @@ describe('Ingest Manager - validatePackagePolicy()', () => { }); }); -describe('Ingest Manager - validationHasErrors()', () => { +describe('Fleet - validationHasErrors()', () => { it('returns true for stream validation results with errors', () => { expect( validationHasErrors({ diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_unenroll_modal/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_unenroll_modal/index.tsx index 74f2303c70c0..1b3935a86f65 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_unenroll_modal/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/agent_unenroll_modal/index.tsx @@ -144,7 +144,7 @@ export const AgentUnenrollAgentModal: React.FunctionComponent = ({ }} > { useBreadcrumbs('fleet'); - const core = useCore(); const { agents } = useConfig(); + const capabilities = useCapabilities(); const fleetStatus = useFleetStatus(); @@ -35,7 +35,7 @@ export const FleetApp: React.FunctionComponent = () => { /> ); } - if (!core.application.capabilities.ingestManager.read) { + if (!capabilities.read) { return ; } diff --git a/x-pack/plugins/fleet/public/index.ts b/x-pack/plugins/fleet/public/index.ts index 1de001a6fc69..be53af77f4b4 100644 --- a/x-pack/plugins/fleet/public/index.ts +++ b/x-pack/plugins/fleet/public/index.ts @@ -4,12 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ import { PluginInitializerContext } from 'src/core/public'; -import { IngestManagerPlugin } from './plugin'; +import { FleetPlugin } from './plugin'; -export { IngestManagerSetup, IngestManagerStart } from './plugin'; +export { FleetSetup, FleetStart } from './plugin'; export const plugin = (initializerContext: PluginInitializerContext) => { - return new IngestManagerPlugin(initializerContext); + return new FleetPlugin(initializerContext); }; export type { NewPackagePolicy } from './applications/fleet/types'; diff --git a/x-pack/plugins/fleet/public/plugin.ts b/x-pack/plugins/fleet/public/plugin.ts index 377ba770b5ca..7e523b3fa594 100644 --- a/x-pack/plugins/fleet/public/plugin.ts +++ b/x-pack/plugins/fleet/public/plugin.ts @@ -11,7 +11,7 @@ import { CoreStart, } from 'src/core/public'; import { i18n } from '@kbn/i18n'; -import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/public'; +import { DEFAULT_APP_CATEGORIES, AppNavLinkStatus } from '../../../../src/core/public'; import { DataPublicPluginSetup, DataPublicPluginStart } from '../../../../src/plugins/data/public'; import { HomePublicPluginSetup, @@ -21,7 +21,7 @@ import { LicensingPluginSetup } from '../../licensing/public'; import { PLUGIN_ID, CheckPermissionsResponse, PostIngestSetupResponse } from '../common'; import { BASE_PATH } from './applications/fleet/constants'; -import { IngestManagerConfigType } from '../common/types'; +import { FleetConfigType } from '../common/types'; import { setupRouteService, appRoutesService } from '../common'; import { licenseService } from './applications/fleet/hooks/use_license'; import { setHttpClient } from './applications/fleet/hooks/use_request/use_request'; @@ -33,44 +33,42 @@ import { import { createExtensionRegistrationCallback } from './applications/fleet/services/ui_extensions'; import { UIExtensionRegistrationCallback, UIExtensionsStorage } from './applications/fleet/types'; -export { IngestManagerConfigType } from '../common/types'; +export { FleetConfigType } from '../common/types'; -// We need to provide an object instead of void so that dependent plugins know when Ingest Manager +// We need to provide an object instead of void so that dependent plugins know when Fleet // is disabled. // eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface IngestManagerSetup {} +export interface FleetSetup {} /** - * Describes public IngestManager plugin contract returned at the `start` stage. + * Describes public Fleet plugin contract returned at the `start` stage. */ -export interface IngestManagerStart { +export interface FleetStart { registerExtension: UIExtensionRegistrationCallback; isInitialized: () => Promise; } -export interface IngestManagerSetupDeps { +export interface FleetSetupDeps { licensing: LicensingPluginSetup; data: DataPublicPluginSetup; home?: HomePublicPluginSetup; } -export interface IngestManagerStartDeps { +export interface FleetStartDeps { data: DataPublicPluginStart; } -export class IngestManagerPlugin - implements - Plugin { - private config: IngestManagerConfigType; +export class FleetPlugin implements Plugin { + private config: FleetConfigType; private kibanaVersion: string; private extensions: UIExtensionsStorage = {}; constructor(private readonly initializerContext: PluginInitializerContext) { - this.config = this.initializerContext.config.get(); + this.config = this.initializerContext.config.get(); this.kibanaVersion = initializerContext.env.packageInfo.version; } - public setup(core: CoreSetup, deps: IngestManagerSetupDeps) { + public setup(core: CoreSetup, deps: FleetSetupDeps) { const config = this.config; const kibanaVersion = this.kibanaVersion; const extensions = this.extensions; @@ -81,7 +79,7 @@ export class IngestManagerPlugin // Set up license service licenseService.start(deps.licensing.license$); - // Register main Ingest Manager app + // Register main Fleet app core.application.register({ id: PLUGIN_ID, category: DEFAULT_APP_CATEGORIES.management, @@ -91,10 +89,10 @@ export class IngestManagerPlugin async mount(params: AppMountParameters) { const [coreStart, startDeps] = (await core.getStartServices()) as [ CoreStart, - IngestManagerStartDeps, - IngestManagerStart + FleetStartDeps, + FleetStart ]; - const { renderApp, teardownIngestManager } = await import('./applications/fleet/'); + const { renderApp, teardownFleet } = await import('./applications/fleet/'); const unmount = renderApp( coreStart, params, @@ -107,11 +105,26 @@ export class IngestManagerPlugin return () => { unmount(); - teardownIngestManager(coreStart); + teardownFleet(coreStart); }; }, }); + // BWC < 7.11 redirect /app/ingestManager to /app/fleet + core.application.register({ + id: 'ingestManager', + category: DEFAULT_APP_CATEGORIES.management, + navLinkStatus: AppNavLinkStatus.hidden, + title: i18n.translate('xpack.fleet.oldAppTitle', { defaultMessage: 'Ingest Manager' }), + async mount(params: AppMountParameters) { + const [coreStart] = await core.getStartServices(); + coreStart.application.navigateToApp('fleet', { + path: params.history.location.hash, + }); + return () => {}; + }, + }); + // Register components for home/add data integration if (deps.home) { deps.home.tutorials.registerDirectoryNotice(PLUGIN_ID, TutorialDirectoryNotice); @@ -119,7 +132,7 @@ export class IngestManagerPlugin deps.home.tutorials.registerModuleNotice(PLUGIN_ID, TutorialModuleNotice); deps.home.featureCatalogue.register({ - id: 'ingestManager', + id: 'fleet', title: i18n.translate('xpack.fleet.featureCatalogueTitle', { defaultMessage: 'Add Elastic Agent', }), @@ -137,8 +150,8 @@ export class IngestManagerPlugin return {}; } - public async start(core: CoreStart): Promise { - let successPromise: ReturnType; + public async start(core: CoreStart): Promise { + let successPromise: ReturnType; return { isInitialized: () => { diff --git a/x-pack/plugins/fleet/server/collectors/config_collectors.ts b/x-pack/plugins/fleet/server/collectors/config_collectors.ts index c201d1d4dfa2..8fb4924a2ccf 100644 --- a/x-pack/plugins/fleet/server/collectors/config_collectors.ts +++ b/x-pack/plugins/fleet/server/collectors/config_collectors.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IngestManagerConfigType } from '..'; +import { FleetConfigType } from '..'; -export const getIsFleetEnabled = (config: IngestManagerConfigType) => { +export const getIsFleetEnabled = (config: FleetConfigType) => { return config.agents.enabled; }; diff --git a/x-pack/plugins/fleet/server/collectors/register.ts b/x-pack/plugins/fleet/server/collectors/register.ts index cb39e6a5be57..e7d95a7e8377 100644 --- a/x-pack/plugins/fleet/server/collectors/register.ts +++ b/x-pack/plugins/fleet/server/collectors/register.ts @@ -10,7 +10,7 @@ import { getIsFleetEnabled } from './config_collectors'; import { AgentUsage, getAgentUsage } from './agent_collectors'; import { getInternalSavedObjectsClient } from './helpers'; import { PackageUsage, getPackageUsage } from './package_collectors'; -import { IngestManagerConfigType } from '..'; +import { FleetConfigType } from '..'; interface Usage { fleet_enabled: boolean; @@ -20,7 +20,7 @@ interface Usage { export function registerIngestManagerUsageCollector( core: CoreSetup, - config: IngestManagerConfigType, + config: FleetConfigType, usageCollection: UsageCollectionSetup | undefined ): void { // usageCollection is an optional dependency, so make sure to return if it is not registered. diff --git a/x-pack/plugins/fleet/server/index.ts b/x-pack/plugins/fleet/server/index.ts index 3d34e37592dd..3d30acd3f8e0 100644 --- a/x-pack/plugins/fleet/server/index.ts +++ b/x-pack/plugins/fleet/server/index.ts @@ -5,7 +5,7 @@ */ import { schema, TypeOf } from '@kbn/config-schema'; import { PluginConfigDescriptor, PluginInitializerContext } from 'src/core/server'; -import { IngestManagerPlugin } from './plugin'; +import { FleetPlugin } from './plugin'; import { AGENT_POLICY_ROLLOUT_RATE_LIMIT_INTERVAL_MS, AGENT_POLICY_ROLLOUT_RATE_LIMIT_REQUEST_PER_INTERVAL, @@ -14,12 +14,7 @@ import { export { default as apm } from 'elastic-apm-node'; export { AgentService, ESIndexPatternService, getRegistryUrl, PackageService } from './services'; -export { - IngestManagerSetupContract, - IngestManagerSetupDeps, - IngestManagerStartContract, - ExternalCallback, -} from './plugin'; +export { FleetSetupContract, FleetSetupDeps, FleetStartContract, ExternalCallback } from './plugin'; export const config: PluginConfigDescriptor = { exposeToBrowser: { @@ -65,10 +60,10 @@ export const config: PluginConfigDescriptor = { }), }; -export type IngestManagerConfigType = TypeOf; +export type FleetConfigType = TypeOf; export { PackagePolicyServiceInterface } from './services/package_policy'; export const plugin = (initializerContext: PluginInitializerContext) => { - return new IngestManagerPlugin(initializerContext); + return new FleetPlugin(initializerContext); }; diff --git a/x-pack/plugins/fleet/server/mocks.ts b/x-pack/plugins/fleet/server/mocks.ts index 18b58b567365..c8aef287e443 100644 --- a/x-pack/plugins/fleet/server/mocks.ts +++ b/x-pack/plugins/fleet/server/mocks.ts @@ -5,12 +5,12 @@ */ import { loggingSystemMock, savedObjectsServiceMock } from 'src/core/server/mocks'; -import { IngestManagerAppContext } from './plugin'; +import { FleetAppContext } from './plugin'; import { encryptedSavedObjectsMock } from '../../encrypted_saved_objects/server/mocks'; import { securityMock } from '../../security/server/mocks'; import { PackagePolicyServiceInterface } from './services/package_policy'; -export const createAppContextStartContractMock = (): IngestManagerAppContext => { +export const createAppContextStartContractMock = (): FleetAppContext => { return { encryptedSavedObjectsStart: encryptedSavedObjectsMock.createStart(), savedObjects: savedObjectsServiceMock.createStartContract(), diff --git a/x-pack/plugins/fleet/server/plugin.ts b/x-pack/plugins/fleet/server/plugin.ts index bf5b2aac5064..47692d478b76 100644 --- a/x-pack/plugins/fleet/server/plugin.ts +++ b/x-pack/plugins/fleet/server/plugin.ts @@ -51,7 +51,7 @@ import { registerSettingsRoutes, registerAppRoutes, } from './routes'; -import { EsAssetReference, IngestManagerConfigType, NewPackagePolicy } from '../common'; +import { EsAssetReference, FleetConfigType, NewPackagePolicy } from '../common'; import { appContextService, licenseService, @@ -72,7 +72,7 @@ import { agentCheckinState } from './services/agents/checkin/state'; import { registerIngestManagerUsageCollector } from './collectors/register'; import { getInstallation } from './services/epm/packages'; -export interface IngestManagerSetupDeps { +export interface FleetSetupDeps { licensing: LicensingPluginSetup; security?: SecurityPluginSetup; features?: FeaturesPluginSetup; @@ -81,13 +81,13 @@ export interface IngestManagerSetupDeps { usageCollection?: UsageCollectionSetup; } -export type IngestManagerStartDeps = object; +export type FleetStartDeps = object; -export interface IngestManagerAppContext { +export interface FleetAppContext { encryptedSavedObjectsStart: EncryptedSavedObjectsPluginStart; encryptedSavedObjectsSetup?: EncryptedSavedObjectsPluginSetup; security?: SecurityPluginSetup; - config$?: Observable; + config$?: Observable; savedObjects: SavedObjectsServiceStart; isProductionMode: PluginInitializerContext['env']['mode']['prod']; kibanaVersion: PluginInitializerContext['env']['packageInfo']['version']; @@ -97,7 +97,7 @@ export interface IngestManagerAppContext { httpSetup?: HttpServiceSetup; } -export type IngestManagerSetupContract = void; +export type FleetSetupContract = void; const allSavedObjectTypes = [ OUTPUT_SAVED_OBJECT_TYPE, @@ -110,7 +110,7 @@ const allSavedObjectTypes = [ ]; /** - * Callbacks supported by the Ingest plugin + * Callbacks supported by the Fleet plugin */ export type ExternalCallback = [ 'packagePolicyCreate', @@ -124,52 +124,46 @@ export type ExternalCallback = [ export type ExternalCallbacksStorage = Map>; /** - * Describes public IngestManager plugin contract returned at the `startup` stage. + * Describes public Fleet plugin contract returned at the `startup` stage. */ -export interface IngestManagerStartContract { +export interface FleetStartContract { esIndexPatternService: ESIndexPatternService; packageService: PackageService; agentService: AgentService; /** - * Services for Ingest's package policies + * Services for Fleet's package policies */ packagePolicyService: typeof packagePolicyService; /** - * Register callbacks for inclusion in ingest API processing + * Register callbacks for inclusion in fleet API processing * @param args */ registerExternalCallback: (...args: ExternalCallback) => void; } -export class IngestManagerPlugin - implements - Plugin< - IngestManagerSetupContract, - IngestManagerStartContract, - IngestManagerSetupDeps, - IngestManagerStartDeps - > { +export class FleetPlugin + implements Plugin { private licensing$!: Observable; - private config$: Observable; + private config$: Observable; private security: SecurityPluginSetup | undefined; private cloud: CloudSetup | undefined; private logger: Logger | undefined; - private isProductionMode: IngestManagerAppContext['isProductionMode']; - private kibanaVersion: IngestManagerAppContext['kibanaVersion']; - private kibanaBranch: IngestManagerAppContext['kibanaBranch']; + private isProductionMode: FleetAppContext['isProductionMode']; + private kibanaVersion: FleetAppContext['kibanaVersion']; + private kibanaBranch: FleetAppContext['kibanaBranch']; private httpSetup: HttpServiceSetup | undefined; private encryptedSavedObjectsSetup: EncryptedSavedObjectsPluginSetup | undefined; constructor(private readonly initializerContext: PluginInitializerContext) { - this.config$ = this.initializerContext.config.create(); + this.config$ = this.initializerContext.config.create(); this.isProductionMode = this.initializerContext.env.mode.prod; this.kibanaVersion = this.initializerContext.env.packageInfo.version; this.kibanaBranch = this.initializerContext.env.packageInfo.branch; this.logger = this.initializerContext.logger.get(); } - public async setup(core: CoreSetup, deps: IngestManagerSetupDeps) { + public async setup(core: CoreSetup, deps: FleetSetupDeps) { this.httpSetup = core.http; this.licensing$ = deps.licensing.license$; if (deps.security) { @@ -186,15 +180,15 @@ export class IngestManagerPlugin if (deps.features) { deps.features.registerKibanaFeature({ id: PLUGIN_ID, - name: 'Ingest Manager', + name: 'Fleet', category: DEFAULT_APP_CATEGORIES.management, app: [PLUGIN_ID, 'kibana'], - catalogue: ['ingestManager'], + catalogue: ['fleet'], privileges: { all: { api: [`${PLUGIN_ID}-read`, `${PLUGIN_ID}-all`], app: [PLUGIN_ID, 'kibana'], - catalogue: ['ingestManager'], + catalogue: ['fleet'], savedObject: { all: allSavedObjectTypes, read: [], @@ -204,7 +198,7 @@ export class IngestManagerPlugin read: { api: [`${PLUGIN_ID}-read`], app: [PLUGIN_ID, 'kibana'], - catalogue: ['ingestManager'], // TODO: check if this is actually available to read user + catalogue: ['fleet'], // TODO: check if this is actually available to read user savedObject: { all: [], read: allSavedObjectTypes, @@ -264,7 +258,7 @@ export class IngestManagerPlugin plugins: { encryptedSavedObjects: EncryptedSavedObjectsPluginStart; } - ): Promise { + ): Promise { await appContextService.start({ encryptedSavedObjectsStart: plugins.encryptedSavedObjects, encryptedSavedObjectsSetup: this.encryptedSavedObjectsSetup, diff --git a/x-pack/plugins/fleet/server/routes/agent/index.ts b/x-pack/plugins/fleet/server/routes/agent/index.ts index 2f97a6bcde42..39b80c6d096d 100644 --- a/x-pack/plugins/fleet/server/routes/agent/index.ts +++ b/x-pack/plugins/fleet/server/routes/agent/index.ts @@ -55,7 +55,7 @@ import * as AgentService from '../../services/agents'; import { postNewAgentActionHandlerBuilder } from './actions_handlers'; import { appContextService } from '../../services'; import { postAgentUnenrollHandler, postBulkAgentsUnenrollHandler } from './unenroll_handler'; -import { IngestManagerConfigType } from '../..'; +import { FleetConfigType } from '../..'; import { postAgentUpgradeHandler, postBulkAgentsUpgradeHandler } from './upgrade_handler'; const ajv = new Ajv({ @@ -81,7 +81,7 @@ function makeValidator(jsonSchema: any) { }; } -export const registerRoutes = (router: IRouter, config: IngestManagerConfigType) => { +export const registerRoutes = (router: IRouter, config: FleetConfigType) => { // Get one router.get( { diff --git a/x-pack/plugins/fleet/server/routes/limited_concurrency.test.ts b/x-pack/plugins/fleet/server/routes/limited_concurrency.test.ts index cc358c32528c..ff304d82cb50 100644 --- a/x-pack/plugins/fleet/server/routes/limited_concurrency.test.ts +++ b/x-pack/plugins/fleet/server/routes/limited_concurrency.test.ts @@ -10,12 +10,12 @@ import { isLimitedRoute, registerLimitedConcurrencyRoutes, } from './limited_concurrency'; -import { IngestManagerConfigType } from '../index'; +import { FleetConfigType } from '../index'; describe('registerLimitedConcurrencyRoutes', () => { test(`doesn't call registerOnPreAuth if maxConcurrentConnections is 0`, async () => { const mockSetup = coreMock.createSetup(); - const mockConfig = { agents: { maxConcurrentConnections: 0 } } as IngestManagerConfigType; + const mockConfig = { agents: { maxConcurrentConnections: 0 } } as FleetConfigType; registerLimitedConcurrencyRoutes(mockSetup, mockConfig); expect(mockSetup.http.registerOnPreAuth).not.toHaveBeenCalled(); @@ -23,7 +23,7 @@ describe('registerLimitedConcurrencyRoutes', () => { test(`calls registerOnPreAuth once if maxConcurrentConnections is 1`, async () => { const mockSetup = coreMock.createSetup(); - const mockConfig = { agents: { maxConcurrentConnections: 1 } } as IngestManagerConfigType; + const mockConfig = { agents: { maxConcurrentConnections: 1 } } as FleetConfigType; registerLimitedConcurrencyRoutes(mockSetup, mockConfig); expect(mockSetup.http.registerOnPreAuth).toHaveBeenCalledTimes(1); @@ -31,7 +31,7 @@ describe('registerLimitedConcurrencyRoutes', () => { test(`calls registerOnPreAuth once if maxConcurrentConnections is 1000`, async () => { const mockSetup = coreMock.createSetup(); - const mockConfig = { agents: { maxConcurrentConnections: 1000 } } as IngestManagerConfigType; + const mockConfig = { agents: { maxConcurrentConnections: 1000 } } as FleetConfigType; registerLimitedConcurrencyRoutes(mockSetup, mockConfig); expect(mockSetup.http.registerOnPreAuth).toHaveBeenCalledTimes(1); diff --git a/x-pack/plugins/fleet/server/routes/limited_concurrency.ts b/x-pack/plugins/fleet/server/routes/limited_concurrency.ts index 609428f5477f..060d7d6b9905 100644 --- a/x-pack/plugins/fleet/server/routes/limited_concurrency.ts +++ b/x-pack/plugins/fleet/server/routes/limited_concurrency.ts @@ -11,7 +11,7 @@ import { OnPreAuthToolkit, } from 'kibana/server'; import { LIMITED_CONCURRENCY_ROUTE_TAG } from '../../common'; -import { IngestManagerConfigType } from '../index'; +import { FleetConfigType } from '../index'; export class MaxCounter { constructor(private readonly max: number = 1) {} @@ -74,7 +74,7 @@ export function createLimitedPreAuthHandler({ }; } -export function registerLimitedConcurrencyRoutes(core: CoreSetup, config: IngestManagerConfigType) { +export function registerLimitedConcurrencyRoutes(core: CoreSetup, config: FleetConfigType) { const max = config.agents.maxConcurrentConnections; if (!max) return; diff --git a/x-pack/plugins/fleet/server/routes/setup/handlers.test.ts b/x-pack/plugins/fleet/server/routes/setup/handlers.test.ts index 56c2eab38529..4d6f375ddf16 100644 --- a/x-pack/plugins/fleet/server/routes/setup/handlers.test.ts +++ b/x-pack/plugins/fleet/server/routes/setup/handlers.test.ts @@ -9,7 +9,7 @@ import { httpServerMock } from 'src/core/server/mocks'; import { PostIngestSetupResponse } from '../../../common'; import { RegistryError } from '../../errors'; import { createAppContextStartContractMock } from '../../mocks'; -import { ingestManagerSetupHandler } from './handlers'; +import { FleetSetupHandler } from './handlers'; import { appContextService } from '../../services/app_context'; import { setupIngestManager } from '../../services/setup'; @@ -21,7 +21,7 @@ jest.mock('../../services/setup', () => { const mockSetupIngestManager = setupIngestManager as jest.MockedFunction; -describe('ingestManagerSetupHandler', () => { +describe('FleetSetupHandler', () => { let context: ReturnType; let response: ReturnType; let request: ReturnType; @@ -44,7 +44,7 @@ describe('ingestManagerSetupHandler', () => { it('POST /setup succeeds w/200 and body of resolved value', async () => { mockSetupIngestManager.mockImplementation(() => Promise.resolve({ isIntialized: true })); - await ingestManagerSetupHandler(context, request, response); + await FleetSetupHandler(context, request, response); const expectedBody: PostIngestSetupResponse = { isInitialized: true }; expect(response.customError).toHaveBeenCalledTimes(0); @@ -55,7 +55,7 @@ describe('ingestManagerSetupHandler', () => { mockSetupIngestManager.mockImplementation(() => Promise.reject(new Error('SO method mocked to throw')) ); - await ingestManagerSetupHandler(context, request, response); + await FleetSetupHandler(context, request, response); expect(response.customError).toHaveBeenCalledTimes(1); expect(response.customError).toHaveBeenCalledWith({ @@ -71,7 +71,7 @@ describe('ingestManagerSetupHandler', () => { Promise.reject(new RegistryError('Registry method mocked to throw')) ); - await ingestManagerSetupHandler(context, request, response); + await FleetSetupHandler(context, request, response); expect(response.customError).toHaveBeenCalledTimes(1); expect(response.customError).toHaveBeenCalledWith({ statusCode: 502, diff --git a/x-pack/plugins/fleet/server/routes/setup/handlers.ts b/x-pack/plugins/fleet/server/routes/setup/handlers.ts index 0bd7b4e87506..b2ad9591bc2e 100644 --- a/x-pack/plugins/fleet/server/routes/setup/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/setup/handlers.ts @@ -72,7 +72,7 @@ export const createFleetSetupHandler: RequestHandler< } }; -export const ingestManagerSetupHandler: RequestHandler = async (context, request, response) => { +export const FleetSetupHandler: RequestHandler = async (context, request, response) => { const soClient = context.core.savedObjects.client; const callCluster = context.core.elasticsearch.legacy.client.callAsCurrentUser; diff --git a/x-pack/plugins/fleet/server/routes/setup/index.ts b/x-pack/plugins/fleet/server/routes/setup/index.ts index 6672a7e8933a..35715600d37d 100644 --- a/x-pack/plugins/fleet/server/routes/setup/index.ts +++ b/x-pack/plugins/fleet/server/routes/setup/index.ts @@ -6,15 +6,11 @@ import { IRouter } from 'src/core/server'; import { PLUGIN_ID, AGENTS_SETUP_API_ROUTES, SETUP_API_ROUTE } from '../../constants'; -import { IngestManagerConfigType } from '../../../common'; -import { - getFleetStatusHandler, - createFleetSetupHandler, - ingestManagerSetupHandler, -} from './handlers'; +import { FleetConfigType } from '../../../common'; +import { getFleetStatusHandler, createFleetSetupHandler, FleetSetupHandler } from './handlers'; import { PostFleetSetupRequestSchema } from '../../types'; -export const registerIngestManagerSetupRoute = (router: IRouter) => { +export const registerFleetSetupRoute = (router: IRouter) => { router.post( { path: SETUP_API_ROUTE, @@ -23,7 +19,7 @@ export const registerIngestManagerSetupRoute = (router: IRouter) => { // and will see `Unable to initialize Ingest Manager` in the UI options: { tags: [`access:${PLUGIN_ID}-read`] }, }, - ingestManagerSetupHandler + FleetSetupHandler ); }; @@ -49,9 +45,9 @@ export const registerGetFleetStatusRoute = (router: IRouter) => { ); }; -export const registerRoutes = (router: IRouter, config: IngestManagerConfigType) => { +export const registerRoutes = (router: IRouter, config: FleetConfigType) => { // Ingest manager setup - registerIngestManagerSetupRoute(router); + registerFleetSetupRoute(router); if (!config.agents.enabled) { return; diff --git a/x-pack/plugins/fleet/server/services/app_context.ts b/x-pack/plugins/fleet/server/services/app_context.ts index 7f82670a4d02..5c4e33d50b48 100644 --- a/x-pack/plugins/fleet/server/services/app_context.ts +++ b/x-pack/plugins/fleet/server/services/app_context.ts @@ -12,26 +12,26 @@ import { } from '../../../encrypted_saved_objects/server'; import packageJSON from '../../../../../package.json'; import { SecurityPluginSetup } from '../../../security/server'; -import { IngestManagerConfigType } from '../../common'; -import { ExternalCallback, ExternalCallbacksStorage, IngestManagerAppContext } from '../plugin'; +import { FleetConfigType } from '../../common'; +import { ExternalCallback, ExternalCallbacksStorage, FleetAppContext } from '../plugin'; import { CloudSetup } from '../../../cloud/server'; class AppContextService { private encryptedSavedObjects: EncryptedSavedObjectsClient | undefined; private encryptedSavedObjectsSetup: EncryptedSavedObjectsPluginSetup | undefined; private security: SecurityPluginSetup | undefined; - private config$?: Observable; - private configSubject$?: BehaviorSubject; + private config$?: Observable; + private configSubject$?: BehaviorSubject; private savedObjects: SavedObjectsServiceStart | undefined; - private isProductionMode: IngestManagerAppContext['isProductionMode'] = false; - private kibanaVersion: IngestManagerAppContext['kibanaVersion'] = packageJSON.version; - private kibanaBranch: IngestManagerAppContext['kibanaBranch'] = packageJSON.branch; + private isProductionMode: FleetAppContext['isProductionMode'] = false; + private kibanaVersion: FleetAppContext['kibanaVersion'] = packageJSON.version; + private kibanaBranch: FleetAppContext['kibanaBranch'] = packageJSON.branch; private cloud?: CloudSetup; private logger: Logger | undefined; private httpSetup?: HttpServiceSetup; private externalCallbacks: ExternalCallbacksStorage = new Map(); - public async start(appContext: IngestManagerAppContext) { + public async start(appContext: FleetAppContext) { this.encryptedSavedObjects = appContext.encryptedSavedObjectsStart?.getClient(); this.encryptedSavedObjectsSetup = appContext.encryptedSavedObjectsSetup; this.security = appContext.security; diff --git a/x-pack/plugins/fleet/server/services/config.ts b/x-pack/plugins/fleet/server/services/config.ts index 23cd38cc123c..f1f5611a20a0 100644 --- a/x-pack/plugins/fleet/server/services/config.ts +++ b/x-pack/plugins/fleet/server/services/config.ts @@ -4,21 +4,21 @@ * you may not use this file except in compliance with the Elastic License. */ import { Observable, Subscription } from 'rxjs'; -import { IngestManagerConfigType } from '../'; +import { FleetConfigType } from '../'; /** * Kibana config observable service, *NOT* agent policy */ class ConfigService { - private observable: Observable | null = null; + private observable: Observable | null = null; private subscription: Subscription | null = null; - private config: IngestManagerConfigType | null = null; + private config: FleetConfigType | null = null; - private updateInformation(config: IngestManagerConfigType) { + private updateInformation(config: FleetConfigType) { this.config = config; } - public start(config$: Observable) { + public start(config$: Observable) { this.observable = config$; this.subscription = this.observable.subscribe(this.updateInformation.bind(this)); } diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts index a76d5dc99cba..8ce307c103f4 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts @@ -67,9 +67,9 @@ describe('Data Streams tab', () => { expect(exists('templateList')).toBe(true); }); - test('when Ingest Manager is enabled, links to Ingest Manager', async () => { + test('when Fleet is enabled, links to Fleet', async () => { testBed = await setup({ - plugins: { ingestManager: { hi: 'ok' } }, + plugins: { fleet: { hi: 'ok' } }, }); await act(async () => { @@ -80,7 +80,7 @@ describe('Data Streams tab', () => { component.update(); // Assert against the text because the href won't be available, due to dependency upon our core mock. - expect(findEmptyPromptIndexTemplateLink().text()).toBe('Ingest Manager'); + expect(findEmptyPromptIndexTemplateLink().text()).toBe('Fleet'); }); }); diff --git a/x-pack/plugins/index_management/kibana.json b/x-pack/plugins/index_management/kibana.json index 4e4ad9b8e1d3..5dcff0ba942e 100644 --- a/x-pack/plugins/index_management/kibana.json +++ b/x-pack/plugins/index_management/kibana.json @@ -3,18 +3,8 @@ "version": "kibana", "server": true, "ui": true, - "requiredPlugins": [ - "home", - "licensing", - "management", - "features", - "share" - ], - "optionalPlugins": [ - "security", - "usageCollection", - "ingestManager" - ], + "requiredPlugins": ["home", "licensing", "management", "features", "share"], + "optionalPlugins": ["security", "usageCollection", "fleet"], "configPath": ["xpack", "index_management"], "requiredBundles": [ "kibanaReact", diff --git a/x-pack/plugins/index_management/public/application/app_context.tsx b/x-pack/plugins/index_management/public/application/app_context.tsx index 5094aa2763a0..c9337767365f 100644 --- a/x-pack/plugins/index_management/public/application/app_context.tsx +++ b/x-pack/plugins/index_management/public/application/app_context.tsx @@ -10,7 +10,7 @@ import { ManagementAppMountParams } from 'src/plugins/management/public'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/public'; import { CoreSetup, CoreStart } from '../../../../../src/core/public'; -import { IngestManagerSetup } from '../../../fleet/public'; +import { FleetSetup } from '../../../fleet/public'; import { IndexMgmtMetricsType } from '../types'; import { UiMetricService, NotificationService, HttpService } from './services'; import { ExtensionsService } from '../services'; @@ -25,7 +25,7 @@ export interface AppDependencies { }; plugins: { usageCollection: UsageCollectionSetup; - ingestManager?: IngestManagerSetup; + fleet?: FleetSetup; }; services: { uiMetricService: UiMetricService; diff --git a/x-pack/plugins/index_management/public/application/mount_management_section.ts b/x-pack/plugins/index_management/public/application/mount_management_section.ts index c15af4f19827..13e25f6d29a1 100644 --- a/x-pack/plugins/index_management/public/application/mount_management_section.ts +++ b/x-pack/plugins/index_management/public/application/mount_management_section.ts @@ -9,7 +9,7 @@ import { CoreSetup } from 'src/core/public'; import { ManagementAppMountParams } from 'src/plugins/management/public/'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/public'; -import { IngestManagerSetup } from '../../../fleet/public'; +import { FleetSetup } from '../../../fleet/public'; import { PLUGIN } from '../../common/constants'; import { ExtensionsService } from '../services'; import { IndexMgmtMetricsType, StartDependencies } from '../types'; @@ -32,7 +32,7 @@ export async function mountManagementSection( usageCollection: UsageCollectionSetup, services: InternalServices, params: ManagementAppMountParams, - ingestManager?: IngestManagerSetup + fleet?: FleetSetup ) { const { element, setBreadcrumbs, history } = params; const [core, startDependencies] = await coreSetup.getStartServices(); @@ -57,7 +57,7 @@ export async function mountManagementSection( }, plugins: { usageCollection, - ingestManager, + fleet, }, services, history, diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx index 0df5697a4281..bc7df7a70196 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx @@ -49,7 +49,7 @@ export const DataStreamList: React.FunctionComponent {' ' /* We need this space to separate these two sentences. */} - {ingestManager ? ( + {fleet ? ( {i18n.translate( 'xpack.idxMgmt.dataStreamList.emptyPrompt.noDataStreamsCtaIngestManagerLink', { - defaultMessage: 'Ingest Manager', + defaultMessage: 'Fleet', } )} diff --git a/x-pack/plugins/index_management/public/plugin.ts b/x-pack/plugins/index_management/public/plugin.ts index 855486528b79..58103688e610 100644 --- a/x-pack/plugins/index_management/public/plugin.ts +++ b/x-pack/plugins/index_management/public/plugin.ts @@ -40,7 +40,7 @@ export class IndexMgmtUIPlugin { plugins: SetupDependencies ): IndexManagementPluginSetup { const { http, notifications } = coreSetup; - const { ingestManager, usageCollection, management } = plugins; + const { fleet, usageCollection, management } = plugins; httpService.setup(http); notificationService.setup(notifications); @@ -58,7 +58,7 @@ export class IndexMgmtUIPlugin { uiMetricService: this.uiMetricService, extensionsService: this.extensionsService, }; - return mountManagementSection(coreSetup, usageCollection, services, params, ingestManager); + return mountManagementSection(coreSetup, usageCollection, services, params, fleet); }, }); diff --git a/x-pack/plugins/index_management/public/types.ts b/x-pack/plugins/index_management/public/types.ts index 34d060d93541..ee763ac83697 100644 --- a/x-pack/plugins/index_management/public/types.ts +++ b/x-pack/plugins/index_management/public/types.ts @@ -5,7 +5,7 @@ */ import { ExtensionsSetup } from './services'; -import { IngestManagerSetup } from '../../fleet/public'; +import { FleetSetup } from '../../fleet/public'; import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/public'; import { ManagementSetup } from '../../../../src/plugins/management/public'; import { SharePluginStart } from '../../../../src/plugins/share/public'; @@ -17,7 +17,7 @@ export interface IndexManagementPluginSetup { } export interface SetupDependencies { - ingestManager?: IngestManagerSetup; + fleet?: FleetSetup; usageCollection: UsageCollectionSetup; management: ManagementSetup; } diff --git a/x-pack/plugins/observability/public/components/app/ingest_manager_panel/index.tsx b/x-pack/plugins/observability/public/components/app/fleet_panel/index.tsx similarity index 74% rename from x-pack/plugins/observability/public/components/app/ingest_manager_panel/index.tsx rename to x-pack/plugins/observability/public/components/app/fleet_panel/index.tsx index 5d0c8a40ed3d..dfe683cf82c8 100644 --- a/x-pack/plugins/observability/public/components/app/ingest_manager_panel/index.tsx +++ b/x-pack/plugins/observability/public/components/app/fleet_panel/index.tsx @@ -13,14 +13,14 @@ import { EuiText } from '@elastic/eui'; import { EuiLink } from '@elastic/eui'; import { usePluginContext } from '../../../hooks/use_plugin_context'; -export function IngestManagerPanel() { +export function FleetPanel() { const { core } = usePluginContext(); return ( @@ -28,24 +28,24 @@ export function IngestManagerPanel() {

    - {i18n.translate('xpack.observability.ingestManager.title', { - defaultMessage: 'Have you seen our new Ingest Manager?', + {i18n.translate('xpack.observability.fleet.title', { + defaultMessage: 'Have you seen our new Fleet?', })}

    - {i18n.translate('xpack.observability.ingestManager.text', { + {i18n.translate('xpack.observability.fleet.text', { defaultMessage: 'The Elastic Agent provides a simple, unified way to add monitoring for logs, metrics, and other types of data to your hosts. You no longer need to install multiple Beats and other agents, making it easier and faster to deploy configurations across your infrastructure.', })} - - {i18n.translate('xpack.observability.ingestManager.button', { - defaultMessage: 'Try Ingest Manager Beta', + + {i18n.translate('xpack.observability.fleet.button', { + defaultMessage: 'Try Fleet Beta', })} diff --git a/x-pack/plugins/observability/public/pages/landing/index.tsx b/x-pack/plugins/observability/public/pages/landing/index.tsx index 24620f641c20..7377a1ca0ea5 100644 --- a/x-pack/plugins/observability/public/pages/landing/index.tsx +++ b/x-pack/plugins/observability/public/pages/landing/index.tsx @@ -18,7 +18,7 @@ import { import { i18n } from '@kbn/i18n'; import React, { useContext } from 'react'; import styled, { ThemeContext } from 'styled-components'; -import { IngestManagerPanel } from '../../components/app/ingest_manager_panel'; +import { FleetPanel } from '../../components/app/fleet_panel'; import { WithHeaderLayout } from '../../components/app/layout/with_header'; import { usePluginContext } from '../../hooks/use_plugin_context'; import { useTrackPageview } from '../../hooks/use_track_metric'; @@ -122,7 +122,7 @@ export function LandingPage() { - + diff --git a/x-pack/plugins/security_solution/kibana.json b/x-pack/plugins/security_solution/kibana.json index 145e34c4fc99..e7dbe6e46686 100644 --- a/x-pack/plugins/security_solution/kibana.json +++ b/x-pack/plugins/security_solution/kibana.json @@ -20,7 +20,7 @@ ], "optionalPlugins": [ "encryptedSavedObjects", - "ingestManager", + "fleet", "ml", "newsfeed", "security", @@ -33,5 +33,5 @@ ], "server": true, "ui": true, - "requiredBundles": ["esUiShared", "ingestManager", "kibanaUtils", "kibanaReact", "lists", "ml"] + "requiredBundles": ["esUiShared", "fleet", "kibanaUtils", "kibanaReact", "lists", "ml"] } diff --git a/x-pack/plugins/security_solution/public/app/home/setup.tsx b/x-pack/plugins/security_solution/public/app/home/setup.tsx index c3567e34a041..1ec62d63bd7f 100644 --- a/x-pack/plugins/security_solution/public/app/home/setup.tsx +++ b/x-pack/plugins/security_solution/public/app/home/setup.tsx @@ -6,12 +6,12 @@ import * as React from 'react'; import { i18n } from '@kbn/i18n'; import { NotificationsStart } from 'kibana/public'; -import { IngestManagerStart } from '../../../../fleet/public'; +import { FleetStart } from '../../../../fleet/public'; export const Setup: React.FunctionComponent<{ - ingestManager: IngestManagerStart; + fleet: FleetStart; notifications: NotificationsStart; -}> = ({ ingestManager, notifications }) => { +}> = ({ fleet, notifications }) => { React.useEffect(() => { const defaultText = i18n.translate('xpack.securitySolution.endpoint.ingestToastMessage', { defaultMessage: 'Ingest Manager failed during its setup.', @@ -32,8 +32,8 @@ export const Setup: React.FunctionComponent<{ }); }; - ingestManager.isInitialized().catch((error: Error) => displayToastWithModal(error.message)); - }, [ingestManager, notifications.toasts]); + fleet.isInitialized().catch((error: Error) => displayToastWithModal(error.message)); + }, [fleet, notifications.toasts]); return null; }; diff --git a/x-pack/plugins/security_solution/public/common/components/endpoint/__snapshots__/link_to_app.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/endpoint/__snapshots__/link_to_app.test.tsx.snap index 6838b673b90d..da8f0d8dcb6b 100644 --- a/x-pack/plugins/security_solution/public/common/components/endpoint/__snapshots__/link_to_app.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/__snapshots__/link_to_app.test.tsx.snap @@ -2,16 +2,16 @@ exports[`LinkToApp component should render with href 1`] = `
    @@ -23,7 +23,7 @@ exports[`LinkToApp component should render with href 1`] = ` exports[`LinkToApp component should render with minimum input 1`] = ` { }); it('should render with minimum input', () => { - expect(render({'link'})).toMatchSnapshot(); + expect(render({'link'})).toMatchSnapshot(); }); it('should render with href', () => { expect( render( - + {'link'} ) @@ -46,7 +46,7 @@ describe('LinkToApp component', () => { // Take `_event` (even though it is not used) so that `jest.fn` will have a type that expects to be called with an event const spyOnClickHandler: LinkToAppOnClickMock = jest.fn().mockImplementation((_event) => {}); const renderResult = render( - + {'link'} ); @@ -57,19 +57,19 @@ describe('LinkToApp component', () => { expect(spyOnClickHandler).toHaveBeenCalled(); expect(clickEventArg.preventDefault).toBeInstanceOf(Function); expect(clickEventArg.isDefaultPrevented()).toBe(true); - expect(fakeCoreStart.application.navigateToApp).toHaveBeenCalledWith('ingestManager', { + expect(fakeCoreStart.application.navigateToApp).toHaveBeenCalledWith('fleet', { path: undefined, state: undefined, }); }); it('should navigate to App with specific path', () => { const renderResult = render( - + {'link'} ); renderResult.find('EuiLink').simulate('click', { button: 0 }); - expect(fakeCoreStart.application.navigateToApp).toHaveBeenCalledWith('ingestManager', { + expect(fakeCoreStart.application.navigateToApp).toHaveBeenCalledWith('fleet', { path: '/some/path', state: undefined, }); @@ -77,9 +77,9 @@ describe('LinkToApp component', () => { it('should passes through EuiLinkProps', () => { const renderResult = render( { className: 'my-class', color: 'primary', 'data-test-subj': 'my-test-subject', - href: '/app/ingest', + href: '/app/fleet', onClick: expect.any(Function), }); }); @@ -105,7 +105,7 @@ describe('LinkToApp component', () => { try { } catch (e) { const renderResult = render( - + {'link'} ); @@ -119,7 +119,7 @@ describe('LinkToApp component', () => { ev.preventDefault(); }); const renderResult = render( - + {'link'} ); @@ -127,13 +127,13 @@ describe('LinkToApp component', () => { expect(fakeCoreStart.application.navigateToApp).not.toHaveBeenCalled(); }); it('should not to navigate if it was not left click', () => { - const renderResult = render({'link'}); + const renderResult = render({'link'}); renderResult.find('EuiLink').simulate('click', { button: 1 }); expect(fakeCoreStart.application.navigateToApp).not.toHaveBeenCalled(); }); it('should not to navigate if it includes an anchor target', () => { const renderResult = render( - + {'link'} ); @@ -142,7 +142,7 @@ describe('LinkToApp component', () => { }); it('should not to navigate if if meta|alt|ctrl|shift keys are pressed', () => { const renderResult = render( - + {'link'} ); diff --git a/x-pack/plugins/security_solution/public/common/hooks/endpoint/ingest_enabled.ts b/x-pack/plugins/security_solution/public/common/hooks/endpoint/ingest_enabled.ts index e48f48e50190..97e73380d9e2 100644 --- a/x-pack/plugins/security_solution/public/common/hooks/endpoint/ingest_enabled.ts +++ b/x-pack/plugins/security_solution/public/common/hooks/endpoint/ingest_enabled.ts @@ -7,7 +7,7 @@ import { ApplicationStart } from 'src/core/public'; import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; /** - * Returns an object which ingest permissions are allowed + * Returns an object which fleet permissions are allowed */ export const useIngestEnabledCheck = (): { allEnabled: boolean; @@ -17,12 +17,12 @@ export const useIngestEnabledCheck = (): { } => { const { services } = useKibana<{ application: ApplicationStart }>(); - // Check if Ingest Manager is present in the configuration - const show = Boolean(services.application.capabilities.ingestManager?.show); - const write = Boolean(services.application.capabilities.ingestManager?.write); - const read = Boolean(services.application.capabilities.ingestManager?.read); + // Check if Fleet is present in the configuration + const show = Boolean(services.application.capabilities.fleet?.show); + const write = Boolean(services.application.capabilities.fleet?.write); + const read = Boolean(services.application.capabilities.fleet?.read); - // Check if all Ingest Manager permissions are enabled + // Check if all Fleet permissions are enabled const allEnabled = show && read && write ? true : false; return { diff --git a/x-pack/plugins/security_solution/public/common/hooks/endpoint/use_navigate_to_app_event_handler.ts b/x-pack/plugins/security_solution/public/common/hooks/endpoint/use_navigate_to_app_event_handler.ts index 943b30925a54..30371f76f8ee 100644 --- a/x-pack/plugins/security_solution/public/common/hooks/endpoint/use_navigate_to_app_event_handler.ts +++ b/x-pack/plugins/security_solution/public/common/hooks/endpoint/use_navigate_to_app_event_handler.ts @@ -25,7 +25,7 @@ type EventHandlerCallback = MouseEventHandlerSee policies */ export const useNavigateToAppEventHandler = ( diff --git a/x-pack/plugins/security_solution/public/common/mock/endpoint/app_context_render.tsx b/x-pack/plugins/security_solution/public/common/mock/endpoint/app_context_render.tsx index 1b9e95f7d073..e55210e1dc09 100644 --- a/x-pack/plugins/security_solution/public/common/mock/endpoint/app_context_render.tsx +++ b/x-pack/plugins/security_solution/public/common/mock/endpoint/app_context_render.tsx @@ -29,7 +29,7 @@ export interface AppContextTestRender { store: Store; history: ReturnType; coreStart: ReturnType; - depsStart: Pick; + depsStart: Pick; middlewareSpy: MiddlewareActionSpyHelper; /** * A wrapper around `AppRootContext` component. Uses the mocked modules as input to the diff --git a/x-pack/plugins/security_solution/public/common/mock/endpoint/app_root_provider.tsx b/x-pack/plugins/security_solution/public/common/mock/endpoint/app_root_provider.tsx index fd6a483e538b..149d948a53fc 100644 --- a/x-pack/plugins/security_solution/public/common/mock/endpoint/app_root_provider.tsx +++ b/x-pack/plugins/security_solution/public/common/mock/endpoint/app_root_provider.tsx @@ -24,7 +24,7 @@ export const AppRootProvider = memo<{ store: Store; history: History; coreStart: CoreStart; - depsStart: Pick; + depsStart: Pick; children: ReactNode | ReactNode[]; }>( ({ diff --git a/x-pack/plugins/security_solution/public/common/mock/endpoint/dependencies_start_mock.ts b/x-pack/plugins/security_solution/public/common/mock/endpoint/dependencies_start_mock.ts index 3388fb535584..864b5e9df804 100644 --- a/x-pack/plugins/security_solution/public/common/mock/endpoint/dependencies_start_mock.ts +++ b/x-pack/plugins/security_solution/public/common/mock/endpoint/dependencies_start_mock.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IngestManagerStart } from '../../../../../fleet/public'; +import { FleetStart } from '../../../../../fleet/public'; import { dataPluginMock, Start as DataPublicStartMock, @@ -33,7 +33,7 @@ type DataMock = Omit & { */ export interface DepsStartMock { data: DataMock; - ingestManager: IngestManagerStart; + fleet: FleetStart; } /** @@ -56,7 +56,7 @@ export const depsStartMock: () => DepsStartMock = () => { return { data: dataMock, - ingestManager: { + fleet: { isInitialized: () => Promise.resolve(true), registerExtension: jest.fn(), }, diff --git a/x-pack/plugins/security_solution/public/common/store/types.ts b/x-pack/plugins/security_solution/public/common/store/types.ts index 189aa05b91f4..97cf14751cb2 100644 --- a/x-pack/plugins/security_solution/public/common/store/types.ts +++ b/x-pack/plugins/security_solution/public/common/store/types.ts @@ -76,7 +76,7 @@ export type ImmutableMiddleware = ( */ export type ImmutableMiddlewareFactory = ( coreStart: CoreStart, - depsStart: Pick + depsStart: Pick ) => ImmutableMiddleware; /** @@ -87,7 +87,7 @@ export type ImmutableMiddlewareFactory = ( */ export type SecuritySubPluginMiddlewareFactory = ( coreStart: CoreStart, - depsStart: Pick + depsStart: Pick ) => Array>>>; /** diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks.ts index a9c84678c88a..012bbed25d74 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks.ts @@ -24,22 +24,22 @@ export function useEndpointSelector(selector: (state: EndpointState) } /** - * Returns an object that contains Ingest app and URL information + * Returns an object that contains Fleet app and URL information */ export const useIngestUrl = (subpath: string): { url: string; appId: string; appPath: string } => { const { services } = useKibana(); return useMemo(() => { const appPath = `#/${subpath}`; return { - url: `${services.application.getUrlForApp('ingestManager')}${appPath}`, - appId: 'ingestManager', + url: `${services.application.getUrlForApp('fleet')}${appPath}`, + appId: 'fleet', appPath, }; }, [services.application, subpath]); }; /** - * Returns an object that contains Ingest app and URL information + * Returns an object that contains Fleet app and URL information */ export const useAgentDetailsIngestUrl = ( agentId: string @@ -48,8 +48,8 @@ export const useAgentDetailsIngestUrl = ( return useMemo(() => { const appPath = `#/fleet/agents/${agentId}/activity`; return { - url: `${services.application.getUrlForApp('ingestManager')}${appPath}`, - appId: 'ingestManager', + url: `${services.application.getUrlForApp('fleet')}${appPath}`, + appId: 'fleet', appPath, }; }, [services.application, agentId]); diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx index d785e3b3a131..4b955f2fe295 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx @@ -612,19 +612,19 @@ describe('when on the list page', () => { }); it('should include the link to reassignment in Ingest', async () => { - coreStart.application.getUrlForApp.mockReturnValue('/app/ingestManager'); + coreStart.application.getUrlForApp.mockReturnValue('/app/fleet'); const renderResult = await renderAndWaitForData(); const linkToReassign = await renderResult.findByTestId('endpointDetailsLinkToIngest'); expect(linkToReassign).not.toBeNull(); expect(linkToReassign.textContent).toEqual('Reassign Policy'); expect(linkToReassign.getAttribute('href')).toEqual( - `/app/ingestManager#/fleet/agents/${elasticAgentId}/activity?openReassignFlyout=true` + `/app/fleet#/fleet/agents/${elasticAgentId}/activity?openReassignFlyout=true` ); }); describe('when link to reassignment in Ingest is clicked', () => { beforeEach(async () => { - coreStart.application.getUrlForApp.mockReturnValue('/app/ingestManager'); + coreStart.application.getUrlForApp.mockReturnValue('/app/fleet'); const renderResult = await renderAndWaitForData(); const linkToReassign = await renderResult.findByTestId('endpointDetailsLinkToIngest'); reactTestingLibrary.act(() => { @@ -820,8 +820,8 @@ describe('when on the list page', () => { switch (appName) { case 'securitySolution': return '/app/security'; - case 'ingestManager': - return '/app/ingestManager'; + case 'fleet': + return '/app/fleet'; } return appName; }); @@ -852,9 +852,7 @@ describe('when on the list page', () => { }); const agentPolicyLink = await renderResult.findByTestId('agentPolicyLink'); - expect(agentPolicyLink.getAttribute('href')).toEqual( - `/app/ingestManager#/policies/${agentPolicyId}` - ); + expect(agentPolicyLink.getAttribute('href')).toEqual(`/app/fleet#/policies/${agentPolicyId}`); }); it('navigates to the Ingest Agent Details page', async () => { const renderResult = await renderAndWaitForData(); @@ -864,9 +862,7 @@ describe('when on the list page', () => { }); const agentDetailsLink = await renderResult.findByTestId('agentDetailsLink'); - expect(agentDetailsLink.getAttribute('href')).toEqual( - `/app/ingestManager#/fleet/agents/${agentId}` - ); + expect(agentDetailsLink.getAttribute('href')).toEqual(`/app/fleet#/fleet/agents/${agentId}`); }); }); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx index a37f256e359b..2b40a7507da8 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx @@ -177,7 +177,7 @@ export const EndpointList = () => { ); const handleCreatePolicyClick = useNavigateToAppEventHandler( - 'ingestManager', + 'fleet', { path: `#/integrations${ endpointPackageVersion ? `/endpoint-${endpointPackageVersion}/add-integration` : '' @@ -219,7 +219,7 @@ export const EndpointList = () => { const handleDeployEndpointsClick = useNavigateToAppEventHandler< AgentPolicyDetailsDeployAgentAction - >('ingestManager', { + >('fleet', { path: `#/policies/${selectedPolicyId}?openEnrollmentFlyout=true`, state: { onDoneNavigateTo: [ @@ -443,14 +443,14 @@ export const EndpointList = () => { icon="logoObservability" key="agentConfigLink" data-test-subj="agentPolicyLink" - navigateAppId="ingestManager" + navigateAppId="fleet" navigateOptions={{ path: `#${pagePathGetters.policy_details({ policyId: agentPolicies[item.metadata.Endpoint.policy.applied.id], })}`, }} href={`${services?.application?.getUrlForApp( - 'ingestManager' + 'fleet' )}#${pagePathGetters.policy_details({ policyId: agentPolicies[item.metadata.Endpoint.policy.applied.id], })}`} @@ -467,14 +467,14 @@ export const EndpointList = () => { icon="logoObservability" key="agentDetailsLink" data-test-subj="agentDetailsLink" - navigateAppId="ingestManager" + navigateAppId="fleet" navigateOptions={{ path: `#${pagePathGetters.fleet_agent_details({ agentId: item.metadata.elastic.agent.id, })}`, }} href={`${services?.application?.getUrlForApp( - 'ingestManager' + 'fleet' )}#${pagePathGetters.fleet_agent_details({ agentId: item.metadata.elastic.agent.id, })}`} @@ -591,12 +591,12 @@ export const EndpointList = () => { values={{ agentsLink: ( (() => { return [ - 'ingestManager', + 'fleet', { path: `#${pagePathGetters.edit_integration({ policyId: agentPolicyId, @@ -99,11 +99,11 @@ const EditFlowMessage = memo<{ path: getTrustedAppsListPath(), state: { backButtonUrl: navigateBackToIngest[1]?.path - ? `${getUrlForApp('ingestManager')}${navigateBackToIngest[1].path}` + ? `${getUrlForApp('fleet')}${navigateBackToIngest[1].path}` : undefined, onBackButtonNavigateTo: navigateBackToIngest, backButtonLabel: i18n.translate( - 'xpack.securitySolution.endpoint.ingestManager.editPackagePolicy.trustedAppsMessageReturnBackLabel', + 'xpack.securitySolution.endpoint.fleet.editPackagePolicy.trustedAppsMessageReturnBackLabel', { defaultMessage: 'Back to Edit Integration' } ), }, @@ -120,7 +120,7 @@ const EditFlowMessage = memo<{ data-test-subj="endpointActions" > @@ -135,7 +135,7 @@ const EditFlowMessage = memo<{ data-test-subj="securityPolicy" > , @@ -145,7 +145,7 @@ const EditFlowMessage = memo<{ data-test-subj="trustedAppsAction" > , @@ -156,7 +156,7 @@ const EditFlowMessage = memo<{ diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.tsx index 274032eea0c5..a3d6cbea3ddc 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.tsx @@ -147,7 +147,7 @@ export const PolicyList = React.memo(() => { } = usePolicyListSelector(selector); const handleCreatePolicyClick = useNavigateToAppEventHandler( - 'ingestManager', + 'fleet', { // We redirect to Ingest's Integaration page if we can't get the package version, and // to the Integration Endpoint Package Add Integration if we have package information. @@ -339,9 +339,9 @@ export const PolicyList = React.memo(() => { diff --git a/x-pack/plugins/security_solution/public/plugin.tsx b/x-pack/plugins/security_solution/public/plugin.tsx index 5cc0d79a3f9a..f97bec65d269 100644 --- a/x-pack/plugins/security_solution/public/plugin.tsx +++ b/x-pack/plugins/security_solution/public/plugin.tsx @@ -331,8 +331,8 @@ export class Plugin implements IPlugin + Pick > & { logger: Logger; manifestManager?: ManifestManager; @@ -74,7 +74,7 @@ export type EndpointAppContextServiceStartContract = Partial< security: SecurityPluginSetup; alerts: AlertsPluginStartContract; config: ConfigType; - registerIngestCallback?: IngestManagerStartContract['registerExternalCallback']; + registerIngestCallback?: FleetStartContract['registerExternalCallback']; savedObjectsStart: SavedObjectsServiceStart; }; diff --git a/x-pack/plugins/security_solution/server/endpoint/mocks.ts b/x-pack/plugins/security_solution/server/endpoint/mocks.ts index 588404fd516d..7a1a0f06a226 100644 --- a/x-pack/plugins/security_solution/server/endpoint/mocks.ts +++ b/x-pack/plugins/security_solution/server/endpoint/mocks.ts @@ -11,7 +11,7 @@ import { alertsMock } from '../../../alerts/server/mocks'; import { xpackMocks } from '../../../../mocks'; import { AgentService, - IngestManagerStartContract, + FleetStartContract, ExternalCallback, PackageService, } from '../../../fleet/server'; @@ -74,8 +74,8 @@ export const createMockEndpointAppContextServiceStartContract = (): jest.Mocked< alerts: alertsMock.createStart(), config, registerIngestCallback: jest.fn< - ReturnType, - Parameters + ReturnType, + Parameters >(), }; }; @@ -109,9 +109,7 @@ export const createMockAgentService = (): jest.Mocked => { * @param indexPattern a string index pattern to return when called by a test * @returns the same value as `indexPattern` parameter */ -export const createMockIngestManagerStartContract = ( - indexPattern: string -): IngestManagerStartContract => { +export const createMockFleetStartContract = (indexPattern: string): FleetStartContract => { return { esIndexPatternService: { getESIndexPattern: jest.fn().mockResolvedValue(indexPattern), diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts index 46a4363936b3..1f90c689a688 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts @@ -55,7 +55,7 @@ describe('test endpoint route', () => { let routeHandler: RequestHandler; // eslint-disable-next-line @typescript-eslint/no-explicit-any let routeConfig: RouteConfig; - // tests assume that ingestManager is enabled, and thus agentService is available + // tests assume that fleet is enabled, and thus agentService is available let mockAgentService: Required< ReturnType >['agentService']; diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata_v1.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata_v1.test.ts index 26f216f0474c..2c7d1e9e4840 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata_v1.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata_v1.test.ts @@ -50,7 +50,7 @@ describe('test endpoint route v1', () => { let routeHandler: RequestHandler; // eslint-disable-next-line @typescript-eslint/no-explicit-any let routeConfig: RouteConfig; - // tests assume that ingestManager is enabled, and thus agentService is available + // tests assume that fleet is enabled, and thus agentService is available let mockAgentService: Required< ReturnType >['agentService']; diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index 036c94cf5005..8a33b1df4caa 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -34,7 +34,7 @@ import { ListPluginSetup } from '../../lists/server'; import { EncryptedSavedObjectsPluginSetup as EncryptedSavedObjectsSetup } from '../../encrypted_saved_objects/server'; import { SpacesPluginSetup as SpacesSetup } from '../../spaces/server'; import { ILicense, LicensingPluginStart } from '../../licensing/server'; -import { IngestManagerStartContract, ExternalCallback } from '../../fleet/server'; +import { FleetStartContract, ExternalCallback } from '../../fleet/server'; import { TaskManagerSetupContract, TaskManagerStartContract } from '../../task_manager/server'; import { initServer } from './init_server'; import { compose } from './lib/compose/kibana'; @@ -93,7 +93,7 @@ export interface SetupPlugins { export interface StartPlugins { alerts: AlertPluginStartContract; data: DataPluginStart; - ingestManager?: IngestManagerStartContract; + fleet?: FleetStartContract; licensing: LicensingPluginStart; taskManager?: TaskManagerStartContract; telemetry?: TelemetryPluginStart; @@ -326,27 +326,27 @@ export class Plugin implements IPlugin void) | undefined; const exceptionListsStartEnabled = () => { - return this.lists && plugins.taskManager && plugins.ingestManager; + return this.lists && plugins.taskManager && plugins.fleet; }; if (exceptionListsStartEnabled()) { const exceptionListClient = this.lists!.getExceptionListClient(savedObjectsClient, 'kibana'); const artifactClient = new ArtifactClient(savedObjectsClient); - registerIngestCallback = plugins.ingestManager!.registerExternalCallback; + registerIngestCallback = plugins.fleet!.registerExternalCallback; manifestManager = new ManifestManager({ savedObjectsClient, artifactClient, exceptionListClient, - packagePolicyService: plugins.ingestManager!.packagePolicyService, + packagePolicyService: plugins.fleet!.packagePolicyService, logger: this.logger, cache: this.exceptionsCache, }); } this.endpointAppContextService.start({ - agentService: plugins.ingestManager?.agentService, - packageService: plugins.ingestManager?.packageService, + agentService: plugins.fleet?.agentService, + packageService: plugins.fleet?.packageService, appClientFactory: this.appClientFactory, security: this.setupPlugins!.security!, alerts: plugins.alerts, diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 7115f8c6eeb6..beb6325e4fec 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -15130,10 +15130,6 @@ "xpack.observability.home.sectionsubtitle": "ログ、メトリック、トレースを大規模に、1つのスタックにまとめて、環境内のあらゆる場所で生じるイベントの監視、分析、対応を行います。", "xpack.observability.home.sectionTitle": "エコシステム全体の一元的な可視性", "xpack.observability.home.title": "オブザーバビリティ", - "xpack.observability.ingestManager.beta": "ベータ", - "xpack.observability.ingestManager.button": "Ingest Managerベータを試す", - "xpack.observability.ingestManager.text": "Elasticエージェントでは、シンプルかつ統合された方法で、ログ、メトリック、他の種類のデータの監視をホストに追加することができます。複数のBeatsと他のエージェントをインストールする必要はありません。このため、インフラストラクチャ全体での構成のデプロイが簡単で高速になりました。", - "xpack.observability.ingestManager.title": "新しいIngest Managerをご覧になりましたか?", "xpack.observability.landing.breadcrumb": "はじめて使う", "xpack.observability.news.readFullStory": "詳細なストーリーを読む", "xpack.observability.news.title": "新機能", @@ -17435,12 +17431,6 @@ "xpack.securitySolution.endpoint.details.policyResponse.workflow": "ワークフロー", "xpack.securitySolution.endpoint.details.policyStatus": "ポリシー応答", "xpack.securitySolution.endpoint.details.policyStatusValue": "{policyStatus, select, success {成功} warning {警告} failure {失敗} other {不明}}", - "xpack.securitySolution.endpoint.ingestManager.createPackagePolicy.endpointConfiguration": "推奨のデフォルト値で統合が保存されます。後からこれを変更するには、エージェントポリシー内でEndpoint Security統合を編集します。", - "xpack.securitySolution.endpoint.ingestManager.editPackagePolicy.actionSecurityPolicy": "セキュリティポリシーを編集", - "xpack.securitySolution.endpoint.ingestManager.editPackagePolicy.actionTrustedApps": "信頼できるアプリケーションを表示", - "xpack.securitySolution.endpoint.ingestManager.editPackagePolicy.menuButton": "アクション", - "xpack.securitySolution.endpoint.ingestManager.editPackagePolicy.message": "詳細構成オプションを表示するには、メニューからアクションを選択します。", - "xpack.securitySolution.endpoint.ingestManager.editPackagePolicy.trustedAppsMessageReturnBackLabel": "統合の編集に戻る", "xpack.securitySolution.endpoint.ingestToastMessage": "Ingest Managerが設定中に失敗しました。", "xpack.securitySolution.endpoint.ingestToastTitle": "アプリを初期化できませんでした", "xpack.securitySolution.endpoint.list.actionmenu": "開く", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index b945c443741b..d069d43de740 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -15148,10 +15148,6 @@ "xpack.observability.home.sectionsubtitle": "通过根据需要将日志、指标和跟踪都置于单个堆栈上,来监测、分析和响应环境中任何位置发生的事件。", "xpack.observability.home.sectionTitle": "整个生态系统的统一可见性", "xpack.observability.home.title": "可观测性", - "xpack.observability.ingestManager.beta": "公测版", - "xpack.observability.ingestManager.button": "试用采集管理器公测版", - "xpack.observability.ingestManager.text": "通过 Elastic 代理,可以简单统一的方式将日志、指标和其他类型数据的监测添加到主机。不再需要安装多个 Beats 和其他代理,这简化和加快了将配置部署到整个基础设施的过程。", - "xpack.observability.ingestManager.title": "是否见过我们的新型采集管理器?", "xpack.observability.landing.breadcrumb": "入门", "xpack.observability.news.readFullStory": "详细了解", "xpack.observability.news.title": "最近的新闻", @@ -17453,12 +17449,6 @@ "xpack.securitySolution.endpoint.details.policyResponse.workflow": "工作流", "xpack.securitySolution.endpoint.details.policyStatus": "策略响应", "xpack.securitySolution.endpoint.details.policyStatusValue": "{policyStatus, select, success {成功} warning {警告} failure {失败} other {未知}}", - "xpack.securitySolution.endpoint.ingestManager.createPackagePolicy.endpointConfiguration": "我们将使用建议的默认值保存您的集成。稍后,您可以通过在代理策略中编辑 Endpoint Security 集成对其进行更改。", - "xpack.securitySolution.endpoint.ingestManager.editPackagePolicy.actionSecurityPolicy": "编辑安全策略", - "xpack.securitySolution.endpoint.ingestManager.editPackagePolicy.actionTrustedApps": "查看受信任的应用程序", - "xpack.securitySolution.endpoint.ingestManager.editPackagePolicy.menuButton": "操作", - "xpack.securitySolution.endpoint.ingestManager.editPackagePolicy.message": "通过从菜单中选择操作可找到更多高级配置选项", - "xpack.securitySolution.endpoint.ingestManager.editPackagePolicy.trustedAppsMessageReturnBackLabel": "返回以编辑集成", "xpack.securitySolution.endpoint.ingestToastMessage": "采集管理器在其设置期间失败。", "xpack.securitySolution.endpoint.ingestToastTitle": "应用无法初始化", "xpack.securitySolution.endpoint.list.actionmenu": "打开", diff --git a/x-pack/test/api_integration/apis/features/features/features.ts b/x-pack/test/api_integration/apis/features/features/features.ts index bc1df21773a7..4b1c8c073b5e 100644 --- a/x-pack/test/api_integration/apis/features/features/features.ts +++ b/x-pack/test/api_integration/apis/features/features/features.ts @@ -115,7 +115,7 @@ export default function ({ getService }: FtrProviderContext) { 'maps', 'uptime', 'siem', - 'ingestManager', + 'fleet', ].sort() ); }); diff --git a/x-pack/test/api_integration/apis/security/privileges.ts b/x-pack/test/api_integration/apis/security/privileges.ts index b6f77e984229..843dd983adf8 100644 --- a/x-pack/test/api_integration/apis/security/privileges.ts +++ b/x-pack/test/api_integration/apis/security/privileges.ts @@ -38,7 +38,7 @@ export default function ({ getService }: FtrProviderContext) { apm: ['all', 'read'], ml: ['all', 'read'], siem: ['all', 'read'], - ingestManager: ['all', 'read'], + fleet: ['all', 'read'], stackAlerts: ['all', 'read'], actions: ['all', 'read'], }, diff --git a/x-pack/test/api_integration/apis/security/privileges_basic.ts b/x-pack/test/api_integration/apis/security/privileges_basic.ts index 679e96dd2151..5df4d597efaa 100644 --- a/x-pack/test/api_integration/apis/security/privileges_basic.ts +++ b/x-pack/test/api_integration/apis/security/privileges_basic.ts @@ -36,7 +36,7 @@ export default function ({ getService }: FtrProviderContext) { apm: ['all', 'read'], ml: ['all', 'read'], siem: ['all', 'read'], - ingestManager: ['all', 'read'], + fleet: ['all', 'read'], stackAlerts: ['all', 'read'], actions: ['all', 'read'], }, diff --git a/x-pack/test/fleet_api_integration/apis/agents/delete.ts b/x-pack/test/fleet_api_integration/apis/agents/delete.ts index 39f518cb9369..b12a4513faef 100644 --- a/x-pack/test/fleet_api_integration/apis/agents/delete.ts +++ b/x-pack/test/fleet_api_integration/apis/agents/delete.ts @@ -15,7 +15,7 @@ export default function ({ getService }: FtrProviderContext) { fleet_user: { permissions: { feature: { - ingestManager: ['read'], + fleet: ['read'], }, spaces: ['*'], }, @@ -25,7 +25,7 @@ export default function ({ getService }: FtrProviderContext) { fleet_admin: { permissions: { feature: { - ingestManager: ['all'], + fleet: ['all'], }, spaces: ['*'], }, diff --git a/x-pack/test/fleet_api_integration/apis/agents/list.ts b/x-pack/test/fleet_api_integration/apis/agents/list.ts index cb7d97f49c9e..e6a62274d34a 100644 --- a/x-pack/test/fleet_api_integration/apis/agents/list.ts +++ b/x-pack/test/fleet_api_integration/apis/agents/list.ts @@ -26,7 +26,7 @@ export default function ({ getService }: FtrProviderContext) { fleet_user: { permissions: { feature: { - ingestManager: ['read'], + fleet: ['read'], }, spaces: ['*'], }, @@ -36,7 +36,7 @@ export default function ({ getService }: FtrProviderContext) { fleet_admin: { permissions: { feature: { - ingestManager: ['all'], + fleet: ['all'], }, spaces: ['*'], }, diff --git a/x-pack/test/security_solution_endpoint/page_objects/ingest_manager_create_package_policy_page.ts b/x-pack/test/security_solution_endpoint/page_objects/ingest_manager_create_package_policy_page.ts index 38ba50b08d50..747b62a9550c 100644 --- a/x-pack/test/security_solution_endpoint/page_objects/ingest_manager_create_package_policy_page.ts +++ b/x-pack/test/security_solution_endpoint/page_objects/ingest_manager_create_package_policy_page.ts @@ -98,7 +98,7 @@ export function IngestManagerCreatePackagePolicy({ * Navigates to the Ingest Agent configuration Edit Package Policy page */ async navigateToAgentPolicyEditPackagePolicy(agentPolicyId: string, packagePolicyId: string) { - await pageObjects.common.navigateToApp('ingestManager', { + await pageObjects.common.navigateToApp('fleet', { hash: `/policies/${agentPolicyId}/edit-integration/${packagePolicyId}`, }); await this.ensureOnEditPageOrFail(); From 4009edc3ddc86b840e4cc2933b84b9920d788918 Mon Sep 17 00:00:00 2001 From: Matthew Kime Date: Thu, 19 Nov 2020 07:45:45 -0600 Subject: [PATCH 41/49] [index patterns] improve index pattern cache (#83368) * cache index pattern promise, not index pattern --- .../index_patterns/_pattern_cache.ts | 4 +- .../index_patterns/index_patterns.test.ts | 49 ++++++++++++++++--- .../index_patterns/index_patterns.ts | 32 +++++++----- 3 files changed, 62 insertions(+), 23 deletions(-) diff --git a/src/plugins/data/common/index_patterns/index_patterns/_pattern_cache.ts b/src/plugins/data/common/index_patterns/index_patterns/_pattern_cache.ts index a3653bb529fa..19fe7c7c26c7 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/_pattern_cache.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/_pattern_cache.ts @@ -20,8 +20,8 @@ import { IndexPattern } from './index_pattern'; export interface PatternCache { - get: (id: string) => IndexPattern; - set: (id: string, value: IndexPattern) => IndexPattern; + get: (id: string) => Promise | undefined; + set: (id: string, value: Promise) => Promise; clear: (id: string) => void; clearAll: () => void; } diff --git a/src/plugins/data/common/index_patterns/index_patterns/index_patterns.test.ts b/src/plugins/data/common/index_patterns/index_patterns/index_patterns.test.ts index b22437ebbdb4..bf227615f76a 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/index_patterns.test.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/index_patterns.test.ts @@ -40,6 +40,7 @@ function setDocsourcePayload(id: string | null, providedPayload: any) { describe('IndexPatterns', () => { let indexPatterns: IndexPatternsService; let savedObjectsClient: SavedObjectsClientCommon; + let SOClientGetDelay = 0; beforeEach(() => { const indexPatternObj = { id: 'id', version: 'a', attributes: { title: 'title' } }; @@ -49,11 +50,14 @@ describe('IndexPatterns', () => { ); savedObjectsClient.delete = jest.fn(() => Promise.resolve({}) as Promise); savedObjectsClient.create = jest.fn(); - savedObjectsClient.get = jest.fn().mockImplementation(async (type, id) => ({ - id: object.id, - version: object.version, - attributes: object.attributes, - })); + savedObjectsClient.get = jest.fn().mockImplementation(async (type, id) => { + await new Promise((resolve) => setTimeout(resolve, SOClientGetDelay)); + return { + id: object.id, + version: object.version, + attributes: object.attributes, + }; + }); savedObjectsClient.update = jest .fn() .mockImplementation(async (type, id, body, { version }) => { @@ -87,6 +91,7 @@ describe('IndexPatterns', () => { }); test('does cache gets for the same id', async () => { + SOClientGetDelay = 1000; const id = '1'; setDocsourcePayload(id, { id: 'foo', @@ -96,10 +101,17 @@ describe('IndexPatterns', () => { }, }); - const indexPattern = await indexPatterns.get(id); + // make two requests before first can complete + const indexPatternPromise = indexPatterns.get(id); + indexPatterns.get(id); - expect(indexPattern).toBeDefined(); - expect(indexPattern).toBe(await indexPatterns.get(id)); + indexPatternPromise.then((indexPattern) => { + expect(savedObjectsClient.get).toBeCalledTimes(1); + expect(indexPattern).toBeDefined(); + }); + + expect(await indexPatternPromise).toBe(await indexPatterns.get(id)); + SOClientGetDelay = 0; }); test('savedObjectCache pre-fetches only title', async () => { @@ -211,4 +223,25 @@ describe('IndexPatterns', () => { expect(indexPatterns.savedObjectToSpec(savedObject)).toMatchSnapshot(); }); + + test('failed requests are not cached', async () => { + savedObjectsClient.get = jest + .fn() + .mockImplementation(async (type, id) => { + return { + id: object.id, + version: object.version, + attributes: object.attributes, + }; + }) + .mockRejectedValueOnce({}); + + const id = '1'; + + // failed request! + expect(indexPatterns.get(id)).rejects.toBeDefined(); + + // successful subsequent request + expect(async () => await indexPatterns.get(id)).toBeDefined(); + }); }); diff --git a/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts b/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts index 4f91079c1e13..d51de220111e 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts @@ -356,17 +356,7 @@ export class IndexPatternsService { }; }; - /** - * Get an index pattern by id. Cache optimized - * @param id - */ - - get = async (id: string): Promise => { - const cache = indexPatternCache.get(id); - if (cache) { - return cache; - } - + private getSavedObjectAndInit = async (id: string): Promise => { const savedObject = await this.savedObjectsClient.get( savedObjectType, id @@ -422,7 +412,6 @@ export class IndexPatternsService { : {}; const indexPattern = await this.create(spec, true); - indexPatternCache.set(id, indexPattern); if (isSaveRequired) { try { this.updateSavedObject(indexPattern); @@ -444,6 +433,23 @@ export class IndexPatternsService { return indexPattern; }; + /** + * Get an index pattern by id. Cache optimized + * @param id + */ + + get = async (id: string): Promise => { + const indexPatternPromise = + indexPatternCache.get(id) || indexPatternCache.set(id, this.getSavedObjectAndInit(id)); + + // don't cache failed requests + indexPatternPromise.catch(() => { + indexPatternCache.clear(id); + }); + + return indexPatternPromise; + }; + /** * Create a new index pattern instance * @param spec @@ -502,7 +508,7 @@ export class IndexPatternsService { id: indexPattern.id, }); indexPattern.id = response.id; - indexPatternCache.set(indexPattern.id, indexPattern); + indexPatternCache.set(indexPattern.id, Promise.resolve(indexPattern)); return indexPattern; } From 3b0215c26b72132e7c27c37a0023d90a6fc80bfd Mon Sep 17 00:00:00 2001 From: Gidi Meir Morris Date: Thu, 19 Nov 2020 14:37:28 +0000 Subject: [PATCH 42/49] [Task Manager] Ensures retries are inferred from the schedule of recurring tasks (#83682) This addresses a bug in Task Manager in the task timeout behaviour. When a recurring task's `retryAt` field is set (which happens at task run), it is currently scheduled to the task definition's `timeout` value, but the original intention was for these tasks to retry on their next scheduled run (originally identified as part of https://github.com/elastic/kibana/issues/39349). In this PR we ensure recurring task retries are scheduled according to their recurring schedule, rather than the default `timeout` of the task type. --- .../task_manager/server/lib/intervals.test.ts | 39 ++++++++ .../task_manager/server/lib/intervals.ts | 12 ++- .../server/task_running/task_runner.test.ts | 96 +++++++++++++++++++ .../server/task_running/task_runner.ts | 21 ++-- .../sample_task_plugin/server/plugin.ts | 11 +++ .../task_manager/task_management.ts | 22 +++++ 6 files changed, 190 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/task_manager/server/lib/intervals.test.ts b/x-pack/plugins/task_manager/server/lib/intervals.test.ts index 147e41e1a9d6..efef05843cb4 100644 --- a/x-pack/plugins/task_manager/server/lib/intervals.test.ts +++ b/x-pack/plugins/task_manager/server/lib/intervals.test.ts @@ -14,6 +14,7 @@ import { secondsFromNow, secondsFromDate, asInterval, + maxIntervalFromDate, } from './intervals'; let fakeTimer: sinon.SinonFakeTimers; @@ -159,6 +160,44 @@ describe('taskIntervals', () => { }); }); + describe('maxIntervalFromDate', () => { + test('it handles a single interval', () => { + const mins = _.random(1, 100); + const now = new Date(); + const expected = now.getTime() + mins * 60 * 1000; + expect(maxIntervalFromDate(now, `${mins}m`)!.getTime()).toEqual(expected); + }); + + test('it handles multiple intervals', () => { + const mins = _.random(1, 100); + const maxMins = mins + _.random(1, 100); + const now = new Date(); + const expected = now.getTime() + maxMins * 60 * 1000; + expect(maxIntervalFromDate(now, `${mins}m`, `${maxMins}m`)!.getTime()).toEqual(expected); + }); + + test('it handles multiple mixed type intervals', () => { + const mins = _.random(1, 100); + const seconds = _.random(1, 100); + const maxSeconds = Math.max(mins * 60, seconds) + _.random(1, 100); + const now = new Date(); + const expected = now.getTime() + maxSeconds * 1000; + expect( + maxIntervalFromDate(now, `${mins}m`, `${maxSeconds}s`, `${seconds}s`)!.getTime() + ).toEqual(expected); + }); + + test('it handles undefined intervals', () => { + const mins = _.random(1, 100); + const maxMins = mins + _.random(1, 100); + const now = new Date(); + const expected = now.getTime() + maxMins * 60 * 1000; + expect(maxIntervalFromDate(now, `${mins}m`, undefined, `${maxMins}m`)!.getTime()).toEqual( + expected + ); + }); + }); + describe('intervalFromDate', () => { test('it returns the given date plus n minutes', () => { const originalDate = new Date(2019, 1, 1); diff --git a/x-pack/plugins/task_manager/server/lib/intervals.ts b/x-pack/plugins/task_manager/server/lib/intervals.ts index 94537277123e..da04dffa4b5d 100644 --- a/x-pack/plugins/task_manager/server/lib/intervals.ts +++ b/x-pack/plugins/task_manager/server/lib/intervals.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { memoize } from 'lodash'; +import { isString, memoize } from 'lodash'; export enum IntervalCadence { Minute = 'm', @@ -57,6 +57,16 @@ export function intervalFromDate(date: Date, interval?: string): Date | undefine return secondsFromDate(date, parseIntervalAsSecond(interval)); } +export function maxIntervalFromDate( + date: Date, + ...intervals: Array +): Date | undefined { + const maxSeconds = Math.max(...intervals.filter(isString).map(parseIntervalAsSecond)); + if (!isNaN(maxSeconds)) { + return secondsFromDate(date, maxSeconds); + } +} + /** * Returns a date that is secs seconds from now. * diff --git a/x-pack/plugins/task_manager/server/task_running/task_runner.test.ts b/x-pack/plugins/task_manager/server/task_running/task_runner.test.ts index f5e2d3d96bc4..3777d89ce63d 100644 --- a/x-pack/plugins/task_manager/server/task_running/task_runner.test.ts +++ b/x-pack/plugins/task_manager/server/task_running/task_runner.test.ts @@ -393,6 +393,102 @@ describe('TaskManagerRunner', () => { ); }); + test('calculates retryAt by schedule when running a recurring task', async () => { + const intervalMinutes = 10; + const id = _.random(1, 20).toString(); + const initialAttempts = _.random(0, 2); + const { runner, store } = testOpts({ + instance: { + id, + attempts: initialAttempts, + schedule: { + interval: `${intervalMinutes}m`, + }, + }, + definitions: { + bar: { + title: 'Bar!', + createTaskRunner: () => ({ + run: async () => undefined, + }), + }, + }, + }); + + await runner.markTaskAsRunning(); + + sinon.assert.calledOnce(store.update); + const instance = store.update.args[0][0]; + + expect(instance.retryAt.getTime()).toEqual( + instance.startedAt.getTime() + intervalMinutes * 60 * 1000 + ); + }); + + test('calculates retryAt by default timout when it exceeds the schedule of a recurring task', async () => { + const intervalSeconds = 20; + const id = _.random(1, 20).toString(); + const initialAttempts = _.random(0, 2); + const { runner, store } = testOpts({ + instance: { + id, + attempts: initialAttempts, + schedule: { + interval: `${intervalSeconds}s`, + }, + }, + definitions: { + bar: { + title: 'Bar!', + createTaskRunner: () => ({ + run: async () => undefined, + }), + }, + }, + }); + + await runner.markTaskAsRunning(); + + sinon.assert.calledOnce(store.update); + const instance = store.update.args[0][0]; + + expect(instance.retryAt.getTime()).toEqual(instance.startedAt.getTime() + 5 * 60 * 1000); + }); + + test('calculates retryAt by timeout if it exceeds the schedule when running a recurring task', async () => { + const timeoutMinutes = 1; + const intervalSeconds = 20; + const id = _.random(1, 20).toString(); + const initialAttempts = _.random(0, 2); + const { runner, store } = testOpts({ + instance: { + id, + attempts: initialAttempts, + schedule: { + interval: `${intervalSeconds}s`, + }, + }, + definitions: { + bar: { + title: 'Bar!', + timeout: `${timeoutMinutes}m`, + createTaskRunner: () => ({ + run: async () => undefined, + }), + }, + }, + }); + + await runner.markTaskAsRunning(); + + sinon.assert.calledOnce(store.update); + const instance = store.update.args[0][0]; + + expect(instance.retryAt.getTime()).toEqual( + instance.startedAt.getTime() + timeoutMinutes * 60 * 1000 + ); + }); + test('uses getRetry function (returning date) on error when defined', async () => { const initialAttempts = _.random(1, 3); const nextRetry = new Date(Date.now() + _.random(15, 100) * 1000); diff --git a/x-pack/plugins/task_manager/server/task_running/task_runner.ts b/x-pack/plugins/task_manager/server/task_running/task_runner.ts index fb7a28c8f402..23d21d205ec2 100644 --- a/x-pack/plugins/task_manager/server/task_running/task_runner.ts +++ b/x-pack/plugins/task_manager/server/task_running/task_runner.ts @@ -26,7 +26,7 @@ import { startTaskTimer, TaskTiming, } from '../task_events'; -import { intervalFromDate, intervalFromNow } from '../lib/intervals'; +import { intervalFromDate, maxIntervalFromDate } from '../lib/intervals'; import { CancelFunction, CancellableTask, @@ -259,15 +259,16 @@ export class TaskManagerRunner implements TaskRunner { status: TaskStatus.Running, startedAt: now, attempts, - retryAt: this.instance.schedule - ? intervalFromNow(this.definition.timeout)! - : this.getRetryDelay({ - attempts, - // Fake an error. This allows retry logic when tasks keep timing out - // and lets us set a proper "retryAt" value each time. - error: new Error('Task timeout'), - addDuration: this.definition.timeout, - }) ?? null, + retryAt: + (this.instance.schedule + ? maxIntervalFromDate(now, this.instance.schedule!.interval, this.definition.timeout) + : this.getRetryDelay({ + attempts, + // Fake an error. This allows retry logic when tasks keep timing out + // and lets us set a proper "retryAt" value each time. + error: new Error('Task timeout'), + addDuration: this.definition.timeout, + })) ?? null, }); const timeUntilClaimExpiresAfterUpdate = howManyMsUntilOwnershipClaimExpires( diff --git a/x-pack/test/plugin_api_integration/plugins/sample_task_plugin/server/plugin.ts b/x-pack/test/plugin_api_integration/plugins/sample_task_plugin/server/plugin.ts index b5d2c98d8cbc..0326adb90775 100644 --- a/x-pack/test/plugin_api_integration/plugins/sample_task_plugin/server/plugin.ts +++ b/x-pack/test/plugin_api_integration/plugins/sample_task_plugin/server/plugin.ts @@ -115,6 +115,17 @@ export class SampleTaskManagerFixturePlugin }, }), }, + sampleRecurringTaskWhichHangs: { + title: 'Sample Recurring Task that Hangs for a minute', + description: 'A sample task that Hangs for a minute on each run.', + maxAttempts: 3, + timeout: '60s', + createTaskRunner: () => ({ + async run() { + return await new Promise((resolve) => {}); + }, + }), + }, sampleOneTimeTaskTimingOut: { title: 'Sample One-Time Task that Times Out', description: 'A sample task that times out each run.', diff --git a/x-pack/test/plugin_api_integration/test_suites/task_manager/task_management.ts b/x-pack/test/plugin_api_integration/test_suites/task_manager/task_management.ts index f34cb7594d28..7f4585fad472 100644 --- a/x-pack/test/plugin_api_integration/test_suites/task_manager/task_management.ts +++ b/x-pack/test/plugin_api_integration/test_suites/task_manager/task_management.ts @@ -260,6 +260,28 @@ export default function ({ getService }: FtrProviderContext) { }); }); + it('should schedule the retry of recurring tasks to run at the next schedule when they time out', async () => { + const intervalInMinutes = 30; + const intervalInMilliseconds = intervalInMinutes * 60 * 1000; + const task = await scheduleTask({ + taskType: 'sampleRecurringTaskWhichHangs', + schedule: { interval: `${intervalInMinutes}m` }, + params: {}, + }); + + await retry.try(async () => { + const [scheduledTask] = (await currentTasks()).docs; + expect(scheduledTask.id).to.eql(task.id); + const retryAt = Date.parse(scheduledTask.retryAt!); + expect(isNaN(retryAt)).to.be(false); + + const buffer = 10000; // 10 second buffer + const retryDelay = retryAt - Date.parse(task.runAt); + expect(retryDelay).to.be.greaterThan(intervalInMilliseconds - buffer); + expect(retryDelay).to.be.lessThan(intervalInMilliseconds + buffer); + }); + }); + it('should reschedule if task returns runAt', async () => { const nextRunMilliseconds = _.random(60000, 200000); const count = _.random(1, 20); From 3a870bf24f2344a808ef539f84401362421b41f3 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Thu, 19 Nov 2020 14:45:36 +0000 Subject: [PATCH 43/49] skip flaky suite (#83793) --- .../cypress/integration/alerts_detection_rules_custom.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts index d14e09d9384a..83f1a02aceeb 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts @@ -216,7 +216,8 @@ describe.skip('Custom detection rules creation', () => { }); }); -describe('Custom detection rules deletion and edition', () => { +// FLAKY: https://github.com/elastic/kibana/issues/83793 +describe.skip('Custom detection rules deletion and edition', () => { beforeEach(() => { esArchiverLoad('custom_rules'); loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); From ca5264959750a464bb07744734a5ba8578992daf Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Thu, 19 Nov 2020 14:48:51 +0000 Subject: [PATCH 44/49] skip flaky suite (#65278) --- .../cypress/integration/cases_connectors.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/cypress/integration/cases_connectors.spec.ts b/x-pack/plugins/security_solution/cypress/integration/cases_connectors.spec.ts index ed885ad653e5..1bba39078026 100644 --- a/x-pack/plugins/security_solution/cypress/integration/cases_connectors.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/cases_connectors.spec.ts @@ -17,7 +17,8 @@ import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; import { CASES_URL } from '../urls/navigation'; -describe('Cases connectors', () => { +// FLAKY: https://github.com/elastic/kibana/issues/65278 +describe.skip('Cases connectors', () => { before(() => { cy.server(); cy.route('POST', '**/api/actions/action').as('createConnector'); From 2cff5aad3cdf9ce94c16db9a92504487ef3db503 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Thu, 19 Nov 2020 14:51:45 +0000 Subject: [PATCH 45/49] skip flaky suite (#83771) --- .../cypress/integration/alerts_timeline.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_timeline.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_timeline.spec.ts index 31d8e4666d91..c28c4e842e08 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_timeline.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_timeline.spec.ts @@ -17,7 +17,8 @@ import { loginAndWaitForPage } from '../tasks/login'; import { DETECTIONS_URL } from '../urls/navigation'; -describe('Alerts timeline', () => { +// FLAKY: https://github.com/elastic/kibana/issues/83771 +describe.skip('Alerts timeline', () => { beforeEach(() => { esArchiverLoad('timeline_alerts'); loginAndWaitForPage(DETECTIONS_URL); From 25c3b4c95eeea7fb1ead1fa88a667c17878d3301 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Thu, 19 Nov 2020 14:56:34 +0000 Subject: [PATCH 46/49] skip flaky suite (#83773) --- .../security_solution/cypress/integration/alerts.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts.spec.ts index db841d2a732c..36dc38b68474 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts.spec.ts @@ -30,7 +30,8 @@ import { loginAndWaitForPage } from '../tasks/login'; import { DETECTIONS_URL } from '../urls/navigation'; -describe('Alerts', () => { +// FLAKY: https://github.com/elastic/kibana/issues/83773 +describe.skip('Alerts', () => { context('Closing alerts', () => { beforeEach(() => { esArchiverLoad('alerts'); From 5d05eeaab98d3ac1bdf5600864a367608f2fc4c2 Mon Sep 17 00:00:00 2001 From: Dario Gieselaar Date: Thu, 19 Nov 2020 15:58:15 +0100 Subject: [PATCH 47/49] Improve snapshot error messages (#83785) --- .../lib/snapshots/decorate_snapshot_ui.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.ts b/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.ts index 45550b55e73c..6004c48521c6 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.ts @@ -24,7 +24,6 @@ import { addSerializer, } from 'jest-snapshot'; import path from 'path'; -import expect from '@kbn/expect'; import prettier from 'prettier'; import babelTraverse from '@babel/traverse'; import { flatten, once } from 'lodash'; @@ -227,7 +226,9 @@ function expectToMatchSnapshot(snapshotContext: SnapshotContext, received: any) const matcher = toMatchSnapshot.bind(snapshotContext as any); const result = matcher(received); - expect(result.pass).to.eql(true, result.message()); + if (!result.pass) { + throw new Error(result.message()); + } } function expectToMatchInlineSnapshot( @@ -239,5 +240,7 @@ function expectToMatchInlineSnapshot( const result = arguments.length === 2 ? matcher(received) : matcher(received, _actual); - expect(result.pass).to.eql(true, result.message()); + if (!result.pass) { + throw new Error(result.message()); + } } From b263145ba2fe94fd0adfeeaed21aba10792b23db Mon Sep 17 00:00:00 2001 From: Daniil Date: Thu, 19 Nov 2020 18:02:40 +0300 Subject: [PATCH 48/49] [Data Table] Remove extra column in split mode (#83193) * Fix extra column in split table * Update table exports Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../public/legacy/agg_table/agg_table.js | 34 ++++--------------- .../public/legacy/agg_table/agg_table.test.js | 28 +++++++-------- 2 files changed, 19 insertions(+), 43 deletions(-) diff --git a/src/plugins/vis_type_table/public/legacy/agg_table/agg_table.js b/src/plugins/vis_type_table/public/legacy/agg_table/agg_table.js index a9ec431e9d94..d3eac891c81f 100644 --- a/src/plugins/vis_type_table/public/legacy/agg_table/agg_table.js +++ b/src/plugins/vis_type_table/public/legacy/agg_table/agg_table.js @@ -58,12 +58,8 @@ export function KbnAggTable(config, RecursionHelper) { }; self.toCsv = function (formatted) { - const rows = formatted ? $scope.rows : $scope.table.rows; - const columns = formatted ? [...$scope.formattedColumns] : [...$scope.table.columns]; - - if ($scope.splitRow && formatted) { - columns.unshift($scope.splitRow); - } + const rows = $scope.rows; + const columns = $scope.formattedColumns; const nonAlphaNumRE = /[^a-zA-Z0-9]/; const allDoubleQuoteRE = /"/g; @@ -77,7 +73,7 @@ export function KbnAggTable(config, RecursionHelper) { return val; } - let csvRows = []; + const csvRows = []; for (const row of rows) { const rowArray = []; for (const col of columns) { @@ -86,15 +82,11 @@ export function KbnAggTable(config, RecursionHelper) { formatted && col.formatter ? escape(col.formatter.convert(value)) : escape(value); rowArray.push(formattedValue); } - csvRows = [...csvRows, rowArray]; + csvRows.push(rowArray); } // add the columns to the rows - csvRows.unshift( - columns.map(function (col) { - return escape(formatted ? col.title : col.name); - }) - ); + csvRows.unshift(columns.map(({ title }) => escape(title))); return csvRows .map(function (row) { @@ -112,7 +104,6 @@ export function KbnAggTable(config, RecursionHelper) { if (!table) { $scope.rows = null; $scope.formattedColumns = null; - $scope.splitRow = null; return; } @@ -122,19 +113,12 @@ export function KbnAggTable(config, RecursionHelper) { if (typeof $scope.dimensions === 'undefined') return; - const { buckets, metrics, splitColumn, splitRow } = $scope.dimensions; + const { buckets, metrics } = $scope.dimensions; $scope.formattedColumns = table.columns .map(function (col, i) { const isBucket = buckets.find((bucket) => bucket.accessor === i); - const isSplitColumn = splitColumn - ? splitColumn.find((splitColumn) => splitColumn.accessor === i) - : undefined; - const isSplitRow = splitRow - ? splitRow.find((splitRow) => splitRow.accessor === i) - : undefined; - const dimension = - isBucket || isSplitColumn || metrics.find((metric) => metric.accessor === i); + const dimension = isBucket || metrics.find((metric) => metric.accessor === i); const formatter = dimension ? getFormatService().deserialize(dimension.format) @@ -147,10 +131,6 @@ export function KbnAggTable(config, RecursionHelper) { filterable: !!isBucket, }; - if (isSplitRow) { - $scope.splitRow = formattedColumn; - } - if (!dimension) return; const last = i === table.columns.length - 1; diff --git a/src/plugins/vis_type_table/public/legacy/agg_table/agg_table.test.js b/src/plugins/vis_type_table/public/legacy/agg_table/agg_table.test.js index c93fb4f8bd56..d97ef374def9 100644 --- a/src/plugins/vis_type_table/public/legacy/agg_table/agg_table.test.js +++ b/src/plugins/vis_type_table/public/legacy/agg_table/agg_table.test.js @@ -262,14 +262,12 @@ describe('Table Vis - AggTable Directive', function () { const $tableScope = $el.isolateScope(); const aggTable = $tableScope.aggTable; - $tableScope.table = { - columns: [ - { id: 'a', name: 'one' }, - { id: 'b', name: 'two' }, - { id: 'c', name: 'with double-quotes(")' }, - ], - rows: [{ a: 1, b: 2, c: '"foobar"' }], - }; + $tableScope.rows = [{ a: 1, b: 2, c: '"foobar"' }]; + $tableScope.formattedColumns = [ + { id: 'a', title: 'one' }, + { id: 'b', title: 'two' }, + { id: 'c', title: 'with double-quotes(")' }, + ]; expect(aggTable.toCsv()).toBe( 'one,two,"with double-quotes("")"' + '\r\n' + '1,2,"""foobar"""' + '\r\n' @@ -455,14 +453,12 @@ describe('Table Vis - AggTable Directive', function () { const aggTable = $tableScope.aggTable; const saveAs = sinon.stub(aggTable, '_saveAs'); - $tableScope.table = { - columns: [ - { id: 'a', name: 'one' }, - { id: 'b', name: 'two' }, - { id: 'c', name: 'with double-quotes(")' }, - ], - rows: [{ a: 1, b: 2, c: '"foobar"' }], - }; + $tableScope.rows = [{ a: 1, b: 2, c: '"foobar"' }]; + $tableScope.formattedColumns = [ + { id: 'a', title: 'one' }, + { id: 'b', title: 'two' }, + { id: 'c', title: 'with double-quotes(")' }, + ]; aggTable.csv.filename = 'somefilename.csv'; aggTable.exportAsCsv(); From 441b473f8e7f71821815a92695179f690ee8c6aa Mon Sep 17 00:00:00 2001 From: Lee Drengenberg Date: Thu, 19 Nov 2020 09:21:40 -0600 Subject: [PATCH 49/49] test just part of the message to avoid updates (#83703) --- .../apps/telemetry/_telemetry.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/x-pack/test/stack_functional_integration/apps/telemetry/_telemetry.js b/x-pack/test/stack_functional_integration/apps/telemetry/_telemetry.js index 09698675f067..5cfc88ec9bce 100644 --- a/x-pack/test/stack_functional_integration/apps/telemetry/_telemetry.js +++ b/x-pack/test/stack_functional_integration/apps/telemetry/_telemetry.js @@ -19,13 +19,10 @@ export default ({ getService, getPageObjects }) => { await appsMenu.clickLink('Stack Monitoring'); }); - it('should show banner Help us improve Kibana and Elasticsearch', async () => { - const expectedMessage = `Help us improve the Elastic Stack -To learn about how usage data helps us manage and improve our products and services, see our Privacy Statement. To stop collection, disable usage data here. -Dismiss`; + it('should show banner Help us improve the Elastic Stack', async () => { const actualMessage = await PageObjects.monitoring.getWelcome(); log.debug(`X-Pack message = ${actualMessage}`); - expect(actualMessage).to.be(expectedMessage); + expect(actualMessage).to.contain('Help us improve the Elastic Stack'); }); }); };