From 1e8f2f66eb20a549f5b708448f851ada943789f6 Mon Sep 17 00:00:00 2001 From: Chris Roberson Date: Sun, 13 Dec 2020 10:20:29 -0500 Subject: [PATCH 01/95] [Monitoring] Some progress on making alerts better in the UI (#81569) * Some progress on making alerts better in the UI * Handle edge case * Updates * More updates * Show kibana instances alerts better * Stop showing missing nodes and improve the detail alert UI * WIP * Fix the badge display * Okay I think this is finally working * Fix type issues * Fix tests * Fix tests * Fix alert counts * Fix setup mode listing * Better detail page view of alerts * Feedback * Sorting * Fix a couple small issues * Start of unit tests * I don't think we need this Mock type * Fix types * More tests * Improve tests and fix sorting * Make this test more resilient * Updates after merging master * Fix tests * Fix types, and improve tests * PR comments * Remove nextStep logic * PR feedback * PR feedback * Removing unnecessary changes * Fixing bad merge issues * Remove unused imports * Add tooltip to alerts grouped by node * Fix up stateFilter usage * Code clean up * PR feedback * Fix state filtering in the category list * Fix types * Fix test * Fix types * Update snapshots Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/plugins/monitoring/common/constants.ts | 36 + .../common/{formatting.js => formatting.ts} | 17 +- .../plugins/monitoring/common/types/alerts.ts | 3 + .../monitoring/public/alerts/badge.tsx | 236 ++- .../monitoring/public/alerts/callout.tsx | 158 +- .../public/alerts/configuration.tsx | 166 +++ .../monitoring/public/alerts/context.ts | 16 + ...get_alert_panels_by_category.test.tsx.snap | 1287 +++++++++++++++++ .../get_alert_panels_by_node.test.tsx.snap | 660 +++++++++ .../lib/get_alert_panels_by_category.test.tsx | 212 +++ .../lib/get_alert_panels_by_category.tsx | 228 +++ .../lib/get_alert_panels_by_node.test.tsx | 128 ++ .../alerts/lib/get_alert_panels_by_node.tsx | 138 ++ .../alerts/lib/sort_by_newest_alert.test.ts | 51 + .../public/alerts/lib/sort_by_newest_alert.ts | 14 + .../monitoring/public/alerts/panel.tsx | 175 +-- .../plugins/monitoring/public/alerts/types.ts | 29 + .../components/elasticsearch/node/advanced.js | 4 +- .../components/elasticsearch/nodes/nodes.js | 1 - .../public/views/base_controller.js | 13 +- .../public/views/elasticsearch/node/index.js | 29 +- .../monitoring/server/alerts/base_alert.ts | 3 + .../alerts/cluster_health_alert.test.ts | 1 + .../server/alerts/cluster_health_alert.ts | 3 + .../server/alerts/cpu_usage_alert.ts | 7 +- .../server/alerts/disk_usage_alert.ts | 10 +- ...asticsearch_version_mismatch_alert.test.ts | 1 + .../elasticsearch_version_mismatch_alert.ts | 6 + .../kibana_version_mismatch_alert.test.ts | 1 + .../alerts/kibana_version_mismatch_alert.ts | 6 + .../alerts/license_expiration_alert.test.ts | 1 + .../server/alerts/license_expiration_alert.ts | 3 + .../logstash_version_mismatch_alert.test.ts | 1 + .../alerts/logstash_version_mismatch_alert.ts | 6 + .../server/alerts/memory_usage_alert.ts | 10 +- .../missing_monitoring_data_alert.test.ts | 4 +- .../alerts/missing_monitoring_data_alert.ts | 7 +- .../server/alerts/nodes_changed_alert.test.ts | 1 + .../server/alerts/nodes_changed_alert.ts | 3 + .../thread_pool_rejections_alert_base.ts | 2 +- .../lib/alerts/fetch_legacy_alerts.test.ts | 1 + .../server/lib/alerts/fetch_legacy_alerts.ts | 1 + .../handle_response.test.js.snap | 8 + .../nodes/get_nodes/get_nodes.js | 3 +- .../nodes/get_nodes/handle_response.js | 1 + .../server/lib/pagination/filter.js | 2 +- .../translations/translations/ja-JP.json | 2 - .../translations/translations/zh-CN.json | 2 - .../fixtures/nodes_listing_cgroup.json | 6 +- .../fixtures/nodes_listing_green.json | 2 + .../fixtures/nodes_listing_red.json | 2 + 51 files changed, 3289 insertions(+), 418 deletions(-) rename x-pack/plugins/monitoring/common/{formatting.js => formatting.ts} (65%) create mode 100644 x-pack/plugins/monitoring/public/alerts/configuration.tsx create mode 100644 x-pack/plugins/monitoring/public/alerts/context.ts create mode 100644 x-pack/plugins/monitoring/public/alerts/lib/__snapshots__/get_alert_panels_by_category.test.tsx.snap create mode 100644 x-pack/plugins/monitoring/public/alerts/lib/__snapshots__/get_alert_panels_by_node.test.tsx.snap create mode 100644 x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_category.test.tsx create mode 100644 x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_category.tsx create mode 100644 x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_node.test.tsx create mode 100644 x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_node.tsx create mode 100644 x-pack/plugins/monitoring/public/alerts/lib/sort_by_newest_alert.test.ts create mode 100644 x-pack/plugins/monitoring/public/alerts/lib/sort_by_newest_alert.ts create mode 100644 x-pack/plugins/monitoring/public/alerts/types.ts diff --git a/x-pack/plugins/monitoring/common/constants.ts b/x-pack/plugins/monitoring/common/constants.ts index 8d1ffc89f5dd6..cf382701ca40f 100644 --- a/x-pack/plugins/monitoring/common/constants.ts +++ b/x-pack/plugins/monitoring/common/constants.ts @@ -453,6 +453,42 @@ export const ALERT_DETAILS = { }, }; +export const ALERT_PANEL_MENU = [ + { + label: i18n.translate('xpack.monitoring.alerts.badge.panelCategory.clusterHealth', { + defaultMessage: 'Cluster health', + }), + alerts: [ + { alertName: ALERT_NODES_CHANGED }, + { alertName: ALERT_CLUSTER_HEALTH }, + { alertName: ALERT_ELASTICSEARCH_VERSION_MISMATCH }, + { alertName: ALERT_KIBANA_VERSION_MISMATCH }, + { alertName: ALERT_LOGSTASH_VERSION_MISMATCH }, + ], + }, + { + label: i18n.translate('xpack.monitoring.alerts.badge.panelCategory.resourceUtilization', { + defaultMessage: 'Resource utilization', + }), + alerts: [ + { alertName: ALERT_CPU_USAGE }, + { alertName: ALERT_DISK_USAGE }, + { alertName: ALERT_MEMORY_USAGE }, + ], + }, + { + label: i18n.translate('xpack.monitoring.alerts.badge.panelCategory.errors', { + defaultMessage: 'Errors and exceptions', + }), + alerts: [ + { alertName: ALERT_MISSING_MONITORING_DATA }, + { alertName: ALERT_LICENSE_EXPIRATION }, + { alertName: ALERT_THREAD_POOL_SEARCH_REJECTIONS }, + { alertName: ALERT_THREAD_POOL_WRITE_REJECTIONS }, + ], + }, +]; + /** * A listing of all alert types */ diff --git a/x-pack/plugins/monitoring/common/formatting.js b/x-pack/plugins/monitoring/common/formatting.ts similarity index 65% rename from x-pack/plugins/monitoring/common/formatting.js rename to x-pack/plugins/monitoring/common/formatting.ts index b2a67b3cd48da..65159f532d2aa 100644 --- a/x-pack/plugins/monitoring/common/formatting.js +++ b/x-pack/plugins/monitoring/common/formatting.ts @@ -11,13 +11,14 @@ export const SMALL_FLOAT = '0.[00]'; export const LARGE_BYTES = '0,0.0 b'; export const SMALL_BYTES = '0.0 b'; export const LARGE_ABBREVIATED = '0,0.[0]a'; +export const ROUNDED_FLOAT = '00.[00]'; /** * Format the {@code date} in the user's expected date/time format using their guessed local time zone. * @param date Either a numeric Unix timestamp or a {@code Date} object * @returns The date formatted using 'LL LTS' */ -export function formatDateTimeLocal(date, useUTC = false, timezone = null) { +export function formatDateTimeLocal(date: number | Date, useUTC = false, timezone = null) { return useUTC ? moment.utc(date).format('LL LTS') : moment.tz(date, timezone || moment.tz.guess()).format('LL LTS'); @@ -28,6 +29,18 @@ export function formatDateTimeLocal(date, useUTC = false, timezone = null) { * @param {string} hash The complete hash * @return {string} The shortened hash */ -export function shortenPipelineHash(hash) { +export function shortenPipelineHash(hash: string) { return hash.substr(0, 6); } + +export function getDateFromNow(timestamp: string | number | Date, tz: string) { + return moment(timestamp) + .tz(tz === 'Browser' ? moment.tz.guess() : tz) + .fromNow(); +} + +export function getCalendar(timestamp: string | number | Date, tz: string) { + return moment(timestamp) + .tz(tz === 'Browser' ? moment.tz.guess() : tz) + .calendar(); +} diff --git a/x-pack/plugins/monitoring/common/types/alerts.ts b/x-pack/plugins/monitoring/common/types/alerts.ts index 0daa947b1c82a..0f10e0e48962b 100644 --- a/x-pack/plugins/monitoring/common/types/alerts.ts +++ b/x-pack/plugins/monitoring/common/types/alerts.ts @@ -7,6 +7,8 @@ import { Alert, SanitizedAlert } from '../../../alerts/common'; import { AlertParamType, AlertMessageTokenType, AlertSeverity } from '../enums'; +export type CommonAlert = Alert | SanitizedAlert; + export interface CommonAlertStatus { states: CommonAlertState[]; rawAlert: Alert | SanitizedAlert; @@ -179,6 +181,7 @@ export interface LegacyAlert { message: string; resolved_timestamp: string; metadata: LegacyAlertMetadata; + nodeName: string; nodes?: LegacyAlertNodesChangedList; } diff --git a/x-pack/plugins/monitoring/public/alerts/badge.tsx b/x-pack/plugins/monitoring/public/alerts/badge.tsx index b9e39e43ff73d..31a86757cac8e 100644 --- a/x-pack/plugins/monitoring/public/alerts/badge.tsx +++ b/x-pack/plugins/monitoring/public/alerts/badge.tsx @@ -4,187 +4,115 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Fragment } from 'react'; +import React from 'react'; import { i18n } from '@kbn/i18n'; -import { - EuiContextMenu, - EuiPopover, - EuiBadge, - EuiFlexGrid, - EuiFlexItem, - EuiText, -} from '@elastic/eui'; -import { CommonAlertStatus, CommonAlertState } from '../../common/types/alerts'; +import { EuiContextMenu, EuiPopover, EuiBadge, EuiSwitch } from '@elastic/eui'; +import { AlertState, CommonAlertStatus } from '../../common/types/alerts'; import { AlertSeverity } from '../../common/enums'; // @ts-ignore import { formatDateTimeLocal } from '../../common/formatting'; -import { AlertState } from '../../common/types/alerts'; -import { AlertPanel } from './panel'; -import { Legacy } from '../legacy_shims'; import { isInSetupMode } from '../lib/setup_mode'; import { SetupModeContext } from '../components/setup_mode/setup_mode_context'; - -function getDateFromState(state: CommonAlertState) { - const timestamp = state.state.ui.triggeredMS; - const tz = Legacy.shims.uiSettings.get('dateFormat:tz'); - return formatDateTimeLocal(timestamp, false, tz === 'Browser' ? null : tz); -} +import { AlertsContext } from './context'; +import { getAlertPanelsByCategory } from './lib/get_alert_panels_by_category'; +import { getAlertPanelsByNode } from './lib/get_alert_panels_by_node'; export const numberOfAlertsLabel = (count: number) => `${count} alert${count > 1 ? 's' : ''}`; -interface AlertInPanel { - alert: CommonAlertStatus; - alertState: CommonAlertState; -} +const MAX_TO_SHOW_BY_CATEGORY = 8; + +const PANEL_TITLE = i18n.translate('xpack.monitoring.alerts.badge.panelTitle', { + defaultMessage: 'Alerts', +}); + +const GROUP_BY_NODE = i18n.translate('xpack.monitoring.alerts.badge.groupByNode', { + defaultMessage: 'Group by node', +}); + +const GROUP_BY_TYPE = i18n.translate('xpack.monitoring.alerts.badge.groupByType', { + defaultMessage: 'Group by alert type', +}); interface Props { alerts: { [alertTypeId: string]: CommonAlertStatus }; stateFilter: (state: AlertState) => boolean; } export const AlertsBadge: React.FC = (props: Props) => { + // We do not always have the alerts that each consumer wants due to licensing const { stateFilter = () => true } = props; + const alerts = Object.values(props.alerts).filter((alertItem) => Boolean(alertItem?.rawAlert)); const [showPopover, setShowPopover] = React.useState(null); const inSetupMode = isInSetupMode(React.useContext(SetupModeContext)); - const alerts = Object.values(props.alerts).filter((alertItem) => Boolean(alertItem?.rawAlert)); - - if (alerts.length === 0) { - return null; - } - - const badges = []; - - if (inSetupMode) { - const button = ( - setShowPopover(true)} - > - {numberOfAlertsLabel(alerts.length)} - - ); - const panels = [ - { - id: 0, - title: i18n.translate('xpack.monitoring.alerts.badge.panelTitle', { - defaultMessage: 'Alerts', - }), - items: alerts.map(({ rawAlert }, index) => { - return { - name: {rawAlert.name}, - panel: index + 1, - }; - }), - }, - ...alerts.map((alertStatus, index) => { - return { - id: index + 1, - title: alertStatus.rawAlert.name, - width: 400, - content: , - }; - }), - ]; - - badges.push( - setShowPopover(null)} - panelPaddingSize="none" - anchorPosition="downLeft" - > - - - ); - } else { - const byType = { - [AlertSeverity.Danger]: [] as AlertInPanel[], - [AlertSeverity.Warning]: [] as AlertInPanel[], - [AlertSeverity.Success]: [] as AlertInPanel[], - }; + const alertsContext = React.useContext(AlertsContext); + const alertCount = inSetupMode + ? alerts.length + : alerts.reduce( + (sum, { states }) => sum + states.filter(({ state }) => stateFilter(state)).length, + 0 + ); + const [showByNode, setShowByNode] = React.useState( + !inSetupMode && alertCount > MAX_TO_SHOW_BY_CATEGORY + ); - for (const alert of alerts) { - for (const alertState of alert.states) { - if (alertState.firing && stateFilter(alertState.state)) { - const state = alertState.state as AlertState; - byType[state.ui.severity].push({ - alertState, - alert, - }); - } - } + React.useEffect(() => { + if (inSetupMode && showByNode) { + setShowByNode(false); } + }, [inSetupMode, showByNode]); - const typesToShow = [AlertSeverity.Danger, AlertSeverity.Warning]; - for (const type of typesToShow) { - const list = byType[type]; - if (list.length === 0) { - continue; - } + if (alertCount === 0) { + return null; + } - const button = ( - setShowPopover(type)} - > - {numberOfAlertsLabel(list.length)} - - ); + const groupByType = GROUP_BY_NODE; + const panels = showByNode + ? getAlertPanelsByNode(PANEL_TITLE, alerts, stateFilter) + : getAlertPanelsByCategory(PANEL_TITLE, inSetupMode, alerts, alertsContext, stateFilter); - const panels = [ + if (panels.length && !inSetupMode && panels[0].items) { + panels[0].items.push( + ...[ { - id: 0, - title: `Alerts`, - items: list.map(({ alert, alertState }, index) => { - return { - name: ( - - -

{getDateFromState(alertState)}

-
- {alert.rawAlert.name} -
- ), - panel: index + 1, - }; - }), + isSeparator: true as const, }, - ...list.map((alertStatus, index) => { - return { - id: index + 1, - title: getDateFromState(alertStatus.alertState), - width: 400, - content: , - }; - }), - ]; - - badges.push( - setShowPopover(null)} - panelPaddingSize="none" - anchorPosition="downLeft" - > - - - ); - } + { + name: ( + setShowByNode(!showByNode)} + label={showByNode ? GROUP_BY_TYPE : groupByType} + /> + ), + }, + ] + ); } + const button = ( + setShowPopover(true)} + > + {numberOfAlertsLabel(alertCount)} + + ); + return ( - - {badges.map((badge, index) => ( - - {badge} - - ))} - + setShowPopover(null)} + panelPaddingSize="none" + anchorPosition="downLeft" + > + + ); }; diff --git a/x-pack/plugins/monitoring/public/alerts/callout.tsx b/x-pack/plugins/monitoring/public/alerts/callout.tsx index 2f670ac221bf2..d3feb148cf986 100644 --- a/x-pack/plugins/monitoring/public/alerts/callout.tsx +++ b/x-pack/plugins/monitoring/public/alerts/callout.tsx @@ -5,78 +5,108 @@ */ import React, { Fragment } from 'react'; -import { i18n } from '@kbn/i18n'; -import { EuiCallOut, EuiSpacer } from '@elastic/eui'; -import { CommonAlertStatus } from '../../common/types/alerts'; -import { AlertSeverity } from '../../common/enums'; +import { + EuiPanel, + EuiSpacer, + EuiAccordion, + EuiListGroup, + EuiListGroupItem, + EuiTextColor, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, +} from '@elastic/eui'; import { replaceTokens } from './lib/replace_tokens'; -import { AlertMessage, AlertState } from '../../common/types/alerts'; - -const TYPES = [ - { - severity: AlertSeverity.Warning, - color: 'warning', - label: i18n.translate('xpack.monitoring.alerts.callout.warningLabel', { - defaultMessage: 'Warning alert(s)', - }), - }, - { - severity: AlertSeverity.Danger, - color: 'danger', - label: i18n.translate('xpack.monitoring.alerts.callout.dangerLabel', { - defaultMessage: 'Danger alert(s)', - }), - }, -]; +import { AlertMessage } from '../../common/types/alerts'; +import { AlertsByName } from './types'; +import { isInSetupMode } from '../lib/setup_mode'; +import { SetupModeContext } from '../components/setup_mode/setup_mode_context'; +import { AlertConfiguration } from './configuration'; interface Props { - alerts: { [alertTypeId: string]: CommonAlertStatus }; - stateFilter: (state: AlertState) => boolean; + alerts: AlertsByName; } export const AlertsCallout: React.FC = (props: Props) => { - const { alerts, stateFilter = () => true } = props; + const { alerts } = props; + const inSetupMode = isInSetupMode(React.useContext(SetupModeContext)); + + if (inSetupMode) { + return null; + } - const callouts = TYPES.map((type) => { - const list = []; - for (const alertTypeId of Object.keys(alerts)) { - const alertInstance = alerts[alertTypeId]; - for (const { firing, state } of alertInstance.states) { - if (firing && stateFilter(state) && state.ui.severity === type.severity) { - list.push(state); - } - } + const list = []; + for (const alertTypeId of Object.keys(alerts)) { + const alertInstance = alerts[alertTypeId]; + for (const state of alertInstance.states) { + list.push({ + alert: alertInstance, + state, + }); } + } - if (list.length) { - return ( - - -
    - {list.map((state, index) => { - const nextStepsUi = - state.ui.message.nextSteps && state.ui.message.nextSteps.length ? ( -
      - {state.ui.message.nextSteps.map( - (step: AlertMessage, nextStepIndex: number) => ( -
    • {replaceTokens(step)}
    • - ) - )} -
    - ) : null; + if (list.length === 0) { + return null; + } - return ( -
  • - {replaceTokens(state.ui.message)} - {nextStepsUi} -
  • - ); - })} -
-
- -
- ); - } + const accordions = list.map((status, index) => { + const buttonContent = ( +
+ + + + + + + + {replaceTokens(status.state.state.ui.message)} + + + +
+ ); + + const accordion = ( + + + {(status.state.state.ui.message.nextSteps || []).map((step: AlertMessage) => { + return {}} label={replaceTokens(step)} />; + })} + } + /> + + + ); + + const spacer = index !== list.length - 1 ? : null; + return ( +
+ {accordion} + {spacer} +
+ ); }); - return {callouts}; + + return ( + + {accordions} + + + ); }; diff --git a/x-pack/plugins/monitoring/public/alerts/configuration.tsx b/x-pack/plugins/monitoring/public/alerts/configuration.tsx new file mode 100644 index 0000000000000..c570e2c840f01 --- /dev/null +++ b/x-pack/plugins/monitoring/public/alerts/configuration.tsx @@ -0,0 +1,166 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React, { Fragment, useMemo } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiSwitch } from '@elastic/eui'; +import { CommonAlert } from '../../common/types/alerts'; +import { Legacy } from '../legacy_shims'; +import { hideBottomBar, showBottomBar } from '../lib/setup_mode'; +import { BASE_ALERT_API_PATH } from '../../../alerts/common'; + +interface Props { + alert: CommonAlert; + compressed?: boolean; +} +export const AlertConfiguration: React.FC = (props: Props) => { + const { alert, compressed } = props; + const [showFlyout, setShowFlyout] = React.useState(false); + const [isEnabled, setIsEnabled] = React.useState(alert.enabled); + const [isMuted, setIsMuted] = React.useState(alert.muteAll); + const [isSaving, setIsSaving] = React.useState(false); + + async function disableAlert() { + setIsSaving(true); + try { + await Legacy.shims.http.post(`${BASE_ALERT_API_PATH}/alert/${alert.id}/_disable`); + } catch (err) { + Legacy.shims.toastNotifications.addDanger({ + title: i18n.translate('xpack.monitoring.alerts.panel.disableAlert.errorTitle', { + defaultMessage: `Unable to disable alert`, + }), + text: err.message, + }); + } + setIsSaving(false); + } + async function enableAlert() { + setIsSaving(true); + try { + await Legacy.shims.http.post(`${BASE_ALERT_API_PATH}/alert/${alert.id}/_enable`); + } catch (err) { + Legacy.shims.toastNotifications.addDanger({ + title: i18n.translate('xpack.monitoring.alerts.panel.enableAlert.errorTitle', { + defaultMessage: `Unable to enable alert`, + }), + text: err.message, + }); + } + setIsSaving(false); + } + async function muteAlert() { + setIsSaving(true); + try { + await Legacy.shims.http.post(`${BASE_ALERT_API_PATH}/alert/${alert.id}/_mute_all`); + } catch (err) { + Legacy.shims.toastNotifications.addDanger({ + title: i18n.translate('xpack.monitoring.alerts.panel.muteAlert.errorTitle', { + defaultMessage: `Unable to mute alert`, + }), + text: err.message, + }); + } + setIsSaving(false); + } + async function unmuteAlert() { + setIsSaving(true); + try { + await Legacy.shims.http.post(`${BASE_ALERT_API_PATH}/alert/${alert.id}/_unmute_all`); + } catch (err) { + Legacy.shims.toastNotifications.addDanger({ + title: i18n.translate('xpack.monitoring.alerts.panel.ummuteAlert.errorTitle', { + defaultMessage: `Unable to unmute alert`, + }), + text: err.message, + }); + } + setIsSaving(false); + } + + const flyoutUi = useMemo( + () => + showFlyout && + Legacy.shims.triggersActionsUi.getEditAlertFlyout({ + initialAlert: alert, + onClose: () => { + setShowFlyout(false); + showBottomBar(); + }, + }), + // eslint-disable-next-line react-hooks/exhaustive-deps + [showFlyout] + ); + + return ( + + + + { + setShowFlyout(true); + hideBottomBar(); + }} + > + {i18n.translate('xpack.monitoring.alerts.panel.editAlert', { + defaultMessage: `Edit alert`, + })} + + + + { + if (isEnabled) { + setIsEnabled(false); + await disableAlert(); + } else { + setIsEnabled(true); + await enableAlert(); + } + }} + label={ + + } + /> + + + { + if (isMuted) { + setIsMuted(false); + await unmuteAlert(); + } else { + setIsMuted(true); + await muteAlert(); + } + }} + label={ + + } + /> + + + {flyoutUi} + + ); +}; diff --git a/x-pack/plugins/monitoring/public/alerts/context.ts b/x-pack/plugins/monitoring/public/alerts/context.ts new file mode 100644 index 0000000000000..1017a4ade6c73 --- /dev/null +++ b/x-pack/plugins/monitoring/public/alerts/context.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { AlertsByName } from './types'; + +export interface IAlertsContext { + allAlerts: AlertsByName; +} + +export const AlertsContext = React.createContext({ + allAlerts: {} as AlertsByName, +}); diff --git a/x-pack/plugins/monitoring/public/alerts/lib/__snapshots__/get_alert_panels_by_category.test.tsx.snap b/x-pack/plugins/monitoring/public/alerts/lib/__snapshots__/get_alert_panels_by_category.test.tsx.snap new file mode 100644 index 0000000000000..75637b5bfd6c2 --- /dev/null +++ b/x-pack/plugins/monitoring/public/alerts/lib/__snapshots__/get_alert_panels_by_category.test.tsx.snap @@ -0,0 +1,1287 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`getAlertPanelsByCategory non setup mode should allow for state filtering 1`] = ` +Array [ + Object { + "id": 0, + "items": Array [ + Object { + "name": + + Resource utilization + ( + 1 + ) + + , + "panel": 1, + }, + ], + "title": "Alerts", + }, + Object { + "id": 1, + "items": Array [ + Object { + "name": + monitoring_alert_cpu_usage_label + ( + 1 + ) + , + "panel": 2, + }, + ], + "title": "Resource utilization", + }, + Object { + "id": 2, + "items": Array [ + Object { + "name": + + + triggered:0 + + + + es_name_0 + + , + "panel": 3, + }, + Object { + "isSeparator": true, + }, + ], + "title": "monitoring_alert_cpu_usage_label", + }, + Object { + "content": , + "id": 3, + "title": "monitoring_alert_cpu_usage_label", + "width": 400, + }, +] +`; + +exports[`getAlertPanelsByCategory non setup mode should not show any alert if none are firing 1`] = ` +Array [ + Object { + "id": 0, + "items": Array [], + "title": "Alerts", + }, +] +`; + +exports[`getAlertPanelsByCategory non setup mode should properly group for alerts in a single category 1`] = ` +Array [ + Object { + "id": 0, + "items": Array [ + Object { + "name": + + Resource utilization + ( + 2 + ) + + , + "panel": 1, + }, + ], + "title": "Alerts", + }, + Object { + "id": 1, + "items": Array [ + Object { + "name": + monitoring_alert_jvm_memory_usage_label + ( + 2 + ) + , + "panel": 2, + }, + ], + "title": "Resource utilization", + }, + Object { + "id": 2, + "items": Array [ + Object { + "name": + + + triggered:1 + + + + es_name_1 + + , + "panel": 3, + }, + Object { + "isSeparator": true, + }, + Object { + "name": + + + triggered:0 + + + + es_name_0 + + , + "panel": 4, + }, + Object { + "isSeparator": true, + }, + ], + "title": "monitoring_alert_jvm_memory_usage_label", + }, + Object { + "content": , + "id": 3, + "title": "monitoring_alert_jvm_memory_usage_label", + "width": 400, + }, + Object { + "content": , + "id": 4, + "title": "monitoring_alert_jvm_memory_usage_label", + "width": 400, + }, +] +`; + +exports[`getAlertPanelsByCategory non setup mode should properly group for alerts in each category 1`] = ` +Array [ + Object { + "id": 0, + "items": Array [ + Object { + "name": + + Cluster health + ( + 2 + ) + + , + "panel": 1, + }, + Object { + "name": + + Resource utilization + ( + 1 + ) + + , + "panel": 2, + }, + Object { + "name": + + Errors and exceptions + ( + 2 + ) + + , + "panel": 3, + }, + ], + "title": "Alerts", + }, + Object { + "id": 1, + "items": Array [ + Object { + "name": + monitoring_alert_nodes_changed_label + ( + 2 + ) + , + "panel": 4, + }, + ], + "title": "Cluster health", + }, + Object { + "id": 2, + "items": Array [ + Object { + "name": + monitoring_alert_disk_usage_label + ( + 1 + ) + , + "panel": 5, + }, + ], + "title": "Resource utilization", + }, + Object { + "id": 3, + "items": Array [ + Object { + "name": + monitoring_alert_license_expiration_label + ( + 2 + ) + , + "panel": 6, + }, + ], + "title": "Errors and exceptions", + }, + Object { + "id": 4, + "items": Array [ + Object { + "name": + + + triggered:1 + + + + es_name_1 + + , + "panel": 7, + }, + Object { + "isSeparator": true, + }, + Object { + "name": + + + triggered:0 + + + + es_name_0 + + , + "panel": 8, + }, + Object { + "isSeparator": true, + }, + ], + "title": "monitoring_alert_nodes_changed_label", + }, + Object { + "id": 5, + "items": Array [ + Object { + "name": + + + triggered:0 + + + + es_name_0 + + , + "panel": 9, + }, + Object { + "isSeparator": true, + }, + ], + "title": "monitoring_alert_disk_usage_label", + }, + Object { + "id": 6, + "items": Array [ + Object { + "name": + + + triggered:1 + + + + es_name_1 + + , + "panel": 10, + }, + Object { + "isSeparator": true, + }, + Object { + "name": + + + triggered:0 + + + + es_name_0 + + , + "panel": 11, + }, + Object { + "isSeparator": true, + }, + ], + "title": "monitoring_alert_license_expiration_label", + }, + Object { + "content": , + "id": 7, + "title": "monitoring_alert_nodes_changed_label", + "width": 400, + }, + Object { + "content": , + "id": 8, + "title": "monitoring_alert_nodes_changed_label", + "width": 400, + }, + Object { + "content": , + "id": 9, + "title": "monitoring_alert_disk_usage_label", + "width": 400, + }, + Object { + "content": , + "id": 10, + "title": "monitoring_alert_license_expiration_label", + "width": 400, + }, + Object { + "content": , + "id": 11, + "title": "monitoring_alert_license_expiration_label", + "width": 400, + }, +] +`; + +exports[`getAlertPanelsByCategory setup mode should properly group for alerts in a single category 1`] = ` +Array [ + Object { + "id": 0, + "items": Array [ + Object { + "name": + Resource utilization + , + "panel": 1, + }, + ], + "title": "Alerts", + }, + Object { + "id": 1, + "items": Array [ + Object { + "name": + monitoring_alert_jvm_memory_usage_label + , + "panel": 2, + }, + ], + "title": "Resource utilization", + }, + Object { + "content": , + "id": 2, + "title": "monitoring_alert_jvm_memory_usage_label", + "width": 400, + }, +] +`; + +exports[`getAlertPanelsByCategory setup mode should properly group for alerts in each category 1`] = ` +Array [ + Object { + "id": 0, + "items": Array [ + Object { + "name": + Cluster health + , + "panel": 1, + }, + Object { + "name": + Resource utilization + , + "panel": 2, + }, + Object { + "name": + Errors and exceptions + , + "panel": 3, + }, + ], + "title": "Alerts", + }, + Object { + "id": 1, + "items": Array [ + Object { + "name": + monitoring_alert_nodes_changed_label + , + "panel": 4, + }, + ], + "title": "Cluster health", + }, + Object { + "id": 2, + "items": Array [ + Object { + "name": + monitoring_alert_disk_usage_label + , + "panel": 5, + }, + ], + "title": "Resource utilization", + }, + Object { + "id": 3, + "items": Array [ + Object { + "name": + monitoring_alert_license_expiration_label + , + "panel": 6, + }, + ], + "title": "Errors and exceptions", + }, + Object { + "content": , + "id": 4, + "title": "monitoring_alert_nodes_changed_label", + "width": 400, + }, + Object { + "content": , + "id": 5, + "title": "monitoring_alert_disk_usage_label", + "width": 400, + }, + Object { + "content": , + "id": 6, + "title": "monitoring_alert_license_expiration_label", + "width": 400, + }, +] +`; + +exports[`getAlertPanelsByCategory setup mode should still show alerts if none are firing 1`] = ` +Array [ + Object { + "id": 0, + "items": Array [ + Object { + "name": + Cluster health + , + "panel": 1, + }, + Object { + "name": + Resource utilization + , + "panel": 2, + }, + Object { + "name": + Errors and exceptions + , + "panel": 3, + }, + ], + "title": "Alerts", + }, + Object { + "id": 1, + "items": Array [ + Object { + "name": + monitoring_alert_logstash_version_mismatch_label + , + "panel": 4, + }, + ], + "title": "Cluster health", + }, + Object { + "id": 2, + "items": Array [ + Object { + "name": + monitoring_alert_cpu_usage_label + , + "panel": 5, + }, + ], + "title": "Resource utilization", + }, + Object { + "id": 3, + "items": Array [ + Object { + "name": + monitoring_alert_thread_pool_write_rejections_label + , + "panel": 6, + }, + ], + "title": "Errors and exceptions", + }, + Object { + "content": , + "id": 4, + "title": "monitoring_alert_logstash_version_mismatch_label", + "width": 400, + }, + Object { + "content": , + "id": 5, + "title": "monitoring_alert_cpu_usage_label", + "width": 400, + }, + Object { + "content": , + "id": 6, + "title": "monitoring_alert_thread_pool_write_rejections_label", + "width": 400, + }, +] +`; diff --git a/x-pack/plugins/monitoring/public/alerts/lib/__snapshots__/get_alert_panels_by_node.test.tsx.snap b/x-pack/plugins/monitoring/public/alerts/lib/__snapshots__/get_alert_panels_by_node.test.tsx.snap new file mode 100644 index 0000000000000..e9e89112a9758 --- /dev/null +++ b/x-pack/plugins/monitoring/public/alerts/lib/__snapshots__/get_alert_panels_by_node.test.tsx.snap @@ -0,0 +1,660 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`getAlertPanelsByNode should not show any alert if none are firing 1`] = ` +Array [ + Object { + "id": 0, + "items": Array [], + "title": "Alerts", + }, +] +`; + +exports[`getAlertPanelsByNode should properly group for alerts in a single category 1`] = ` +Array [ + Object { + "id": 0, + "items": Array [ + Object { + "name": + es_name_0 + ( + 1 + ) + , + "panel": 1, + }, + Object { + "name": + es_name_1 + ( + 1 + ) + , + "panel": 2, + }, + ], + "title": "Alerts", + }, + Object { + "id": 1, + "items": Array [ + Object { + "name": + + + triggered:0 + + + + monitoring_alert_jvm_memory_usage_label + + , + "panel": 3, + }, + ], + "title": "es_name_0", + }, + Object { + "id": 2, + "items": Array [ + Object { + "name": + + + triggered:0 + + + + monitoring_alert_jvm_memory_usage_label + + , + "panel": 4, + }, + ], + "title": "es_name_1", + }, + Object { + "content": , + "id": 3, + "title": "monitoring_alert_jvm_memory_usage_label", + "width": 400, + }, + Object { + "content": , + "id": 4, + "title": "monitoring_alert_jvm_memory_usage_label", + "width": 400, + }, +] +`; + +exports[`getAlertPanelsByNode should properly group for alerts in each category 1`] = ` +Array [ + Object { + "id": 0, + "items": Array [ + Object { + "name": + es_name_0 + ( + 3 + ) + , + "panel": 1, + }, + Object { + "name": + es_name_1 + ( + 2 + ) + , + "panel": 2, + }, + ], + "title": "Alerts", + }, + Object { + "id": 1, + "items": Array [ + Object { + "name": + + + triggered:0 + + + + monitoring_alert_nodes_changed_label + + , + "panel": 3, + }, + Object { + "name": + + + triggered:0 + + + + monitoring_alert_disk_usage_label + + , + "panel": 4, + }, + Object { + "name": + + + triggered:0 + + + + monitoring_alert_license_expiration_label + + , + "panel": 5, + }, + ], + "title": "es_name_0", + }, + Object { + "id": 2, + "items": Array [ + Object { + "name": + + + triggered:0 + + + + monitoring_alert_nodes_changed_label + + , + "panel": 6, + }, + Object { + "name": + + + triggered:0 + + + + monitoring_alert_license_expiration_label + + , + "panel": 7, + }, + ], + "title": "es_name_1", + }, + Object { + "content": , + "id": 3, + "title": "monitoring_alert_nodes_changed_label", + "width": 400, + }, + Object { + "content": , + "id": 4, + "title": "monitoring_alert_disk_usage_label", + "width": 400, + }, + Object { + "content": , + "id": 5, + "title": "monitoring_alert_license_expiration_label", + "width": 400, + }, + Object { + "content": , + "id": 6, + "title": "monitoring_alert_nodes_changed_label", + "width": 400, + }, + Object { + "content": , + "id": 7, + "title": "monitoring_alert_license_expiration_label", + "width": 400, + }, +] +`; diff --git a/x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_category.test.tsx b/x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_category.test.tsx new file mode 100644 index 0000000000000..16b20119c9607 --- /dev/null +++ b/x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_category.test.tsx @@ -0,0 +1,212 @@ +/* + * 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 { + ALERTS, + ALERT_CPU_USAGE, + ALERT_LOGSTASH_VERSION_MISMATCH, + ALERT_THREAD_POOL_WRITE_REJECTIONS, +} from '../../../common/constants'; +import { AlertSeverity } from '../../../common/enums'; +import { getAlertPanelsByCategory } from './get_alert_panels_by_category'; +import { + ALERT_LICENSE_EXPIRATION, + ALERT_NODES_CHANGED, + ALERT_DISK_USAGE, + ALERT_MEMORY_USAGE, +} from '../../../common/constants'; +import { AlertsByName } from '../types'; +import { AlertExecutionStatusValues } from '../../../../alerts/common'; +import { AlertState } from '../../../common/types/alerts'; + +jest.mock('../../legacy_shims', () => ({ + Legacy: { + shims: { + uiSettings: { + get: () => '', + }, + }, + }, +})); + +jest.mock('../../../common/formatting', () => ({ + getDateFromNow: (timestamp: number) => `triggered:${timestamp}`, + getCalendar: (timestamp: number) => `triggered:${timestamp}`, +})); + +const mockAlert = { + id: '', + enabled: true, + tags: [], + consumer: '', + schedule: { interval: '1m' }, + actions: [], + params: {}, + createdBy: null, + updatedBy: null, + createdAt: new Date('2020-12-08'), + updatedAt: new Date('2020-12-08'), + apiKey: null, + apiKeyOwner: null, + throttle: null, + muteAll: false, + mutedInstanceIds: [], + executionStatus: { + status: AlertExecutionStatusValues[0], + lastExecutionDate: new Date('2020-12-08'), + }, + notifyWhen: null, +}; + +function getAllAlerts() { + return ALERTS.reduce((accum: AlertsByName, alertType) => { + accum[alertType] = { + states: [], + rawAlert: { + alertTypeId: alertType, + name: `${alertType}_label`, + ...mockAlert, + }, + }; + return accum; + }, {}); +} + +describe('getAlertPanelsByCategory', () => { + const ui = { + isFiring: false, + severity: AlertSeverity.Danger, + message: { text: '' }, + resolvedMS: 0, + lastCheckedMS: 0, + triggeredMS: 0, + }; + + const cluster = { clusterUuid: '1', clusterName: 'one' }; + + function getAlert(type: string, firingCount: number) { + const states = []; + + for (let fi = 0; fi < firingCount; fi++) { + states.push({ + firing: true, + meta: {}, + state: { + cluster, + ui: { + ...ui, + triggeredMS: fi, + }, + nodeId: `es${fi}`, + nodeName: `es_name_${fi}`, + }, + }); + } + + return { + states, + rawAlert: { + alertTypeId: type, + name: `${type}_label`, + ...mockAlert, + }, + }; + } + + const alertsContext = { + allAlerts: getAllAlerts(), + }; + + const stateFilter = (state: AlertState) => true; + const panelTitle = 'Alerts'; + + describe('non setup mode', () => { + it('should properly group for alerts in each category', () => { + const alerts = [ + getAlert(ALERT_NODES_CHANGED, 2), + getAlert(ALERT_DISK_USAGE, 1), + getAlert(ALERT_LICENSE_EXPIRATION, 2), + ]; + const result = getAlertPanelsByCategory( + panelTitle, + false, + alerts, + alertsContext, + stateFilter + ); + expect(result).toMatchSnapshot(); + }); + + it('should properly group for alerts in a single category', () => { + const alerts = [getAlert(ALERT_MEMORY_USAGE, 2)]; + const result = getAlertPanelsByCategory( + panelTitle, + false, + alerts, + alertsContext, + stateFilter + ); + expect(result).toMatchSnapshot(); + }); + + it('should not show any alert if none are firing', () => { + const alerts = [ + getAlert(ALERT_LOGSTASH_VERSION_MISMATCH, 0), + getAlert(ALERT_CPU_USAGE, 0), + getAlert(ALERT_THREAD_POOL_WRITE_REJECTIONS, 0), + ]; + const result = getAlertPanelsByCategory( + panelTitle, + false, + alerts, + alertsContext, + stateFilter + ); + expect(result).toMatchSnapshot(); + }); + + it('should allow for state filtering', () => { + const alerts = [getAlert(ALERT_CPU_USAGE, 2)]; + const customStateFilter = (state: AlertState) => state.nodeName === 'es_name_0'; + const result = getAlertPanelsByCategory( + panelTitle, + false, + alerts, + alertsContext, + customStateFilter + ); + expect(result).toMatchSnapshot(); + }); + }); + + describe('setup mode', () => { + it('should properly group for alerts in each category', () => { + const alerts = [ + getAlert(ALERT_NODES_CHANGED, 2), + getAlert(ALERT_DISK_USAGE, 1), + getAlert(ALERT_LICENSE_EXPIRATION, 2), + ]; + const result = getAlertPanelsByCategory(panelTitle, true, alerts, alertsContext, stateFilter); + expect(result).toMatchSnapshot(); + }); + + it('should properly group for alerts in a single category', () => { + const alerts = [getAlert(ALERT_MEMORY_USAGE, 2)]; + const result = getAlertPanelsByCategory(panelTitle, true, alerts, alertsContext, stateFilter); + expect(result).toMatchSnapshot(); + }); + + it('should still show alerts if none are firing', () => { + const alerts = [ + getAlert(ALERT_LOGSTASH_VERSION_MISMATCH, 0), + getAlert(ALERT_CPU_USAGE, 0), + getAlert(ALERT_THREAD_POOL_WRITE_REJECTIONS, 0), + ]; + const result = getAlertPanelsByCategory(panelTitle, true, alerts, alertsContext, stateFilter); + expect(result).toMatchSnapshot(); + }); + }); +}); diff --git a/x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_category.tsx b/x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_category.tsx new file mode 100644 index 0000000000000..82a1a1f841a22 --- /dev/null +++ b/x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_category.tsx @@ -0,0 +1,228 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React, { Fragment } from 'react'; +import { EuiText, EuiToolTip } from '@elastic/eui'; +import { AlertPanel } from '../panel'; +import { ALERT_PANEL_MENU } from '../../../common/constants'; +import { getDateFromNow, getCalendar } from '../../../common/formatting'; +import { IAlertsContext } from '../context'; +import { AlertState, CommonAlertStatus } from '../../../common/types/alerts'; +import { PanelItem } from '../types'; +import { sortByNewestAlert } from './sort_by_newest_alert'; +import { Legacy } from '../../legacy_shims'; + +export function getAlertPanelsByCategory( + panelTitle: string, + inSetupMode: boolean, + alerts: CommonAlertStatus[], + alertsContext: IAlertsContext, + stateFilter: (state: AlertState) => boolean +) { + const menu = []; + for (const category of ALERT_PANEL_MENU) { + let categoryFiringAlertCount = 0; + if (inSetupMode) { + const alertsInCategory = []; + for (const categoryAlert of category.alerts) { + if ( + Boolean(alerts.find(({ rawAlert }) => rawAlert.alertTypeId === categoryAlert.alertName)) + ) { + alertsInCategory.push(categoryAlert); + } + } + if (alertsInCategory.length > 0) { + menu.push({ + ...category, + alerts: alertsInCategory.map(({ alertName }) => { + const alertStatus = alertsContext.allAlerts[alertName]; + return { + alert: alertStatus.rawAlert, + states: [], + alertName, + }; + }), + alertCount: 0, + }); + } + } else { + const firingAlertsInCategory = []; + for (const { alertName } of category.alerts) { + const foundAlert = alerts.find( + ({ rawAlert: { alertTypeId } }) => alertName === alertTypeId + ); + if (foundAlert && foundAlert.states.length > 0) { + const states = foundAlert.states.filter(({ state }) => stateFilter(state)); + if (states.length > 0) { + firingAlertsInCategory.push({ + alert: foundAlert.rawAlert, + states: foundAlert.states, + alertName, + }); + categoryFiringAlertCount += states.length; + } + } + } + + if (firingAlertsInCategory.length > 0) { + menu.push({ + ...category, + alertCount: categoryFiringAlertCount, + alerts: firingAlertsInCategory, + }); + } + } + } + + for (const item of menu) { + for (const alert of item.alerts) { + alert.states.sort(sortByNewestAlert); + } + } + + const panels: PanelItem[] = [ + { + id: 0, + title: panelTitle, + items: [ + ...menu.map((category, index) => { + const name = inSetupMode ? ( + {category.label} + ) : ( + + + {category.label} ({category.alertCount}) + + + ); + return { + name, + panel: index + 1, + }; + }), + ], + }, + ]; + + if (inSetupMode) { + let secondaryPanelIndex = menu.length; + let tertiaryPanelIndex = menu.length; + let nodeIndex = 0; + for (const category of menu) { + panels.push({ + id: nodeIndex + 1, + title: `${category.label}`, + items: category.alerts.map(({ alertName }) => { + const alertStatus = alertsContext.allAlerts[alertName]; + return { + name: {alertStatus.rawAlert.name}, + panel: ++secondaryPanelIndex, + }; + }), + }); + nodeIndex++; + } + + for (const category of menu) { + for (const { alert, alertName } of category.alerts) { + const alertStatus = alertsContext.allAlerts[alertName]; + panels.push({ + id: ++tertiaryPanelIndex, + title: `${alert.name}`, + width: 400, + content: , + }); + } + } + } else { + let primaryPanelIndex = menu.length; + let nodeIndex = 0; + for (const category of menu) { + panels.push({ + id: nodeIndex + 1, + title: `${category.label}`, + items: category.alerts.map(({ alertName, states }) => { + const filteredStates = states.filter(({ state }) => stateFilter(state)); + const alertStatus = alertsContext.allAlerts[alertName]; + const name = inSetupMode ? ( + {alertStatus.rawAlert.name} + ) : ( + + {alertStatus.rawAlert.name} ({filteredStates.length}) + + ); + return { + name, + panel: ++primaryPanelIndex, + }; + }), + }); + nodeIndex++; + } + + let secondaryPanelIndex = menu.length; + let tertiaryPanelIndex = menu.reduce((count, category) => { + count += category.alerts.length; + return count; + }, menu.length); + for (const category of menu) { + for (const { alert, states } of category.alerts) { + const items = []; + for (const alertState of states.filter(({ state }) => stateFilter(state))) { + items.push({ + name: ( + + + + {getDateFromNow( + alertState.state.ui.triggeredMS, + Legacy.shims.uiSettings.get('dateFormat:tz') + )} + + + {alertState.state.nodeName} + + ), + panel: ++tertiaryPanelIndex, + }); + items.push({ + isSeparator: true as const, + }); + } + + panels.push({ + id: ++secondaryPanelIndex, + title: `${alert.name}`, + items, + }); + } + } + + let tertiaryPanelIndex2 = menu.reduce((count, category) => { + count += category.alerts.length; + return count; + }, menu.length); + for (const category of menu) { + for (const { alert, states } of category.alerts) { + for (const state of states.filter(({ state: _state }) => stateFilter(_state))) { + panels.push({ + id: ++tertiaryPanelIndex2, + title: `${alert.name}`, + width: 400, + content: , + }); + } + } + } + } + + return panels; +} diff --git a/x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_node.test.tsx b/x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_node.test.tsx new file mode 100644 index 0000000000000..be6ccb1e0981b --- /dev/null +++ b/x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_node.test.tsx @@ -0,0 +1,128 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + ALERT_CPU_USAGE, + ALERT_LOGSTASH_VERSION_MISMATCH, + ALERT_THREAD_POOL_WRITE_REJECTIONS, +} from '../../../common/constants'; +import { AlertSeverity } from '../../../common/enums'; +import { getAlertPanelsByNode } from './get_alert_panels_by_node'; +import { + ALERT_LICENSE_EXPIRATION, + ALERT_NODES_CHANGED, + ALERT_DISK_USAGE, + ALERT_MEMORY_USAGE, +} from '../../../common/constants'; +import { AlertExecutionStatusValues } from '../../../../alerts/common'; +import { AlertState } from '../../../common/types/alerts'; + +jest.mock('../../legacy_shims', () => ({ + Legacy: { + shims: { + uiSettings: { + get: () => '', + }, + }, + }, +})); + +jest.mock('../../../common/formatting', () => ({ + getDateFromNow: (timestamp: number) => `triggered:${timestamp}`, + getCalendar: (timestamp: number) => `triggered:${timestamp}`, +})); + +const mockAlert = { + id: '', + enabled: true, + tags: [], + consumer: '', + schedule: { interval: '1m' }, + actions: [], + params: {}, + createdBy: null, + updatedBy: null, + createdAt: new Date('2020-12-08'), + updatedAt: new Date('2020-12-08'), + apiKey: null, + apiKeyOwner: null, + throttle: null, + muteAll: false, + mutedInstanceIds: [], + executionStatus: { + status: AlertExecutionStatusValues[0], + lastExecutionDate: new Date('2020-12-08'), + }, + notifyWhen: null, +}; + +describe('getAlertPanelsByNode', () => { + const ui = { + isFiring: false, + severity: AlertSeverity.Danger, + message: { text: '' }, + resolvedMS: 0, + lastCheckedMS: 0, + triggeredMS: 0, + }; + + const cluster = { clusterUuid: '1', clusterName: 'one' }; + + function getAlert(type: string, firingCount: number) { + const states = []; + + for (let fi = 0; fi < firingCount; fi++) { + states.push({ + firing: true, + meta: {}, + state: { + cluster, + ui, + nodeId: `es${fi}`, + nodeName: `es_name_${fi}`, + }, + }); + } + + return { + rawAlert: { + alertTypeId: type, + name: `${type}_label`, + ...mockAlert, + }, + states, + }; + } + + const panelTitle = 'Alerts'; + const stateFilter = (state: AlertState) => true; + + it('should properly group for alerts in each category', () => { + const alerts = [ + getAlert(ALERT_NODES_CHANGED, 2), + getAlert(ALERT_DISK_USAGE, 1), + getAlert(ALERT_LICENSE_EXPIRATION, 2), + ]; + const result = getAlertPanelsByNode(panelTitle, alerts, stateFilter); + expect(result).toMatchSnapshot(); + }); + + it('should properly group for alerts in a single category', () => { + const alerts = [getAlert(ALERT_MEMORY_USAGE, 2)]; + const result = getAlertPanelsByNode(panelTitle, alerts, stateFilter); + expect(result).toMatchSnapshot(); + }); + + it('should not show any alert if none are firing', () => { + const alerts = [ + getAlert(ALERT_LOGSTASH_VERSION_MISMATCH, 0), + getAlert(ALERT_CPU_USAGE, 0), + getAlert(ALERT_THREAD_POOL_WRITE_REJECTIONS, 0), + ]; + const result = getAlertPanelsByNode(panelTitle, alerts, stateFilter); + expect(result).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_node.tsx b/x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_node.tsx new file mode 100644 index 0000000000000..c48706f4edcb9 --- /dev/null +++ b/x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_node.tsx @@ -0,0 +1,138 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React, { Fragment } from 'react'; +import { EuiText, EuiToolTip } from '@elastic/eui'; +import { AlertPanel } from '../panel'; +import { + CommonAlertStatus, + CommonAlertState, + CommonAlert, + AlertState, +} from '../../../common/types/alerts'; +import { getDateFromNow, getCalendar } from '../../../common/formatting'; +import { PanelItem } from '../types'; +import { sortByNewestAlert } from './sort_by_newest_alert'; +import { Legacy } from '../../legacy_shims'; + +export function getAlertPanelsByNode( + panelTitle: string, + alerts: CommonAlertStatus[], + stateFilter: (state: AlertState) => boolean +) { + const alertsByNodes: { + [uuid: string]: { + [alertName: string]: { + alert: CommonAlert; + states: CommonAlertState[]; + count: number; + }; + }; + } = {}; + const statesByNodes: { + [uuid: string]: CommonAlertState[]; + } = {}; + + for (const { states, rawAlert } of alerts) { + const { alertTypeId } = rawAlert; + for (const alertState of states.filter(({ state: _state }) => stateFilter(_state))) { + const { state } = alertState; + statesByNodes[state.nodeId] = statesByNodes[state.nodeId] || []; + statesByNodes[state.nodeId].push(alertState); + + alertsByNodes[state.nodeId] = alertsByNodes[state.nodeId] || {}; + alertsByNodes[state.nodeId][alertTypeId] = alertsByNodes[alertState.state.nodeId][ + alertTypeId + ] || { alert: rawAlert, states: [], count: 0 }; + alertsByNodes[state.nodeId][alertTypeId].count++; + alertsByNodes[state.nodeId][alertTypeId].states.push(alertState); + } + } + + for (const types of Object.values(alertsByNodes)) { + for (const { states } of Object.values(types)) { + states.sort(sortByNewestAlert); + } + } + + const nodeCount = Object.keys(statesByNodes).length; + let secondaryPanelIndex = nodeCount; + let tertiaryPanelIndex = nodeCount; + const panels: PanelItem[] = [ + { + id: 0, + title: panelTitle, + items: [ + ...Object.keys(statesByNodes).map((nodeUuid, index) => { + const states = (statesByNodes[nodeUuid] as CommonAlertState[]).filter(({ state }) => + stateFilter(state) + ); + return { + name: ( + + {states[0].state.nodeName} ({states.length}) + + ), + panel: index + 1, + }; + }), + ], + }, + ...Object.keys(statesByNodes).reduce((accum: PanelItem[], nodeUuid, nodeIndex) => { + const alertsForNode = Object.values(alertsByNodes[nodeUuid]); + const panelItems = []; + let title = ''; + for (const { alert, states } of alertsForNode) { + for (const alertState of states) { + title = alertState.state.nodeName; + panelItems.push({ + name: ( + + + + {getDateFromNow( + alertState.state.ui.triggeredMS, + Legacy.shims.uiSettings.get('dateFormat:tz') + )} + + + {alert.name} + + ), + panel: ++secondaryPanelIndex, + }); + } + } + accum.push({ + id: nodeIndex + 1, + title, + items: panelItems, + }); + return accum; + }, []), + ...Object.keys(statesByNodes).reduce((accum: PanelItem[], nodeUuid, nodeIndex) => { + const alertsForNode = Object.values(alertsByNodes[nodeUuid]); + for (const { alert, states } of alertsForNode) { + for (const alertState of states) { + accum.push({ + id: ++tertiaryPanelIndex, + title: alert.name, + width: 400, + content: , + }); + } + } + return accum; + }, []), + ]; + + return panels; +} diff --git a/x-pack/plugins/monitoring/public/alerts/lib/sort_by_newest_alert.test.ts b/x-pack/plugins/monitoring/public/alerts/lib/sort_by_newest_alert.test.ts new file mode 100644 index 0000000000000..29981c3ed32fe --- /dev/null +++ b/x-pack/plugins/monitoring/public/alerts/lib/sort_by_newest_alert.test.ts @@ -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 { AlertSeverity } from '../../../common/enums'; +import { sortByNewestAlert } from './sort_by_newest_alert'; + +describe('sortByNewestAlert', () => { + const ui = { + isFiring: false, + severity: AlertSeverity.Danger, + message: { text: '' }, + resolvedMS: 0, + lastCheckedMS: 0, + triggeredMS: 0, + }; + + const cluster = { clusterUuid: '1', clusterName: 'one' }; + + it('should sort properly', () => { + const a = { + firing: false, + meta: {}, + state: { + cluster, + ui: { + ...ui, + triggeredMS: 2, + }, + nodeId: `es1`, + nodeName: `es_name_1`, + }, + }; + const b = { + firing: false, + meta: {}, + state: { + cluster, + ui: { + ...ui, + triggeredMS: 1, + }, + nodeId: `es1`, + nodeName: `es_name_1`, + }, + }; + expect(sortByNewestAlert(a, b)).toBe(-1); + }); +}); diff --git a/x-pack/plugins/monitoring/public/alerts/lib/sort_by_newest_alert.ts b/x-pack/plugins/monitoring/public/alerts/lib/sort_by_newest_alert.ts new file mode 100644 index 0000000000000..f5a20e0bae37e --- /dev/null +++ b/x-pack/plugins/monitoring/public/alerts/lib/sort_by_newest_alert.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 { CommonAlertState } from '../../../common/types/alerts'; + +export function sortByNewestAlert(a: CommonAlertState, b: CommonAlertState) { + if (a.state.ui.triggeredMS === b.state.ui.triggeredMS) { + return 0; + } + return a.state.ui.triggeredMS < b.state.ui.triggeredMS ? 1 : -1; +} diff --git a/x-pack/plugins/monitoring/public/alerts/panel.tsx b/x-pack/plugins/monitoring/public/alerts/panel.tsx index b480e46215108..139010a3d2446 100644 --- a/x-pack/plugins/monitoring/public/alerts/panel.tsx +++ b/x-pack/plugins/monitoring/public/alerts/panel.tsx @@ -3,186 +3,39 @@ * 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, useMemo } from 'react'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; +import React, { Fragment } from 'react'; import { EuiSpacer, - EuiButton, - EuiFlexGroup, - EuiFlexItem, - EuiSwitch, EuiTitle, EuiHorizontalRule, EuiListGroup, EuiListGroupItem, } from '@elastic/eui'; -import { CommonAlertStatus, CommonAlertState, AlertMessage } from '../../common/types/alerts'; -import { Legacy } from '../legacy_shims'; +import { CommonAlert, CommonAlertState, AlertMessage } from '../../common/types/alerts'; import { replaceTokens } from './lib/replace_tokens'; -import { isInSetupMode, hideBottomBar, showBottomBar } from '../lib/setup_mode'; -import { BASE_ALERT_API_PATH } from '../../../alerts/common'; +import { isInSetupMode } from '../lib/setup_mode'; import { SetupModeContext } from '../components/setup_mode/setup_mode_context'; +import { AlertConfiguration } from './configuration'; interface Props { - alert: CommonAlertStatus; + alert: CommonAlert; alertState?: CommonAlertState; } export const AlertPanel: React.FC = (props: Props) => { - const { - alert: { rawAlert }, - alertState, - } = props; - - const [showFlyout, setShowFlyout] = React.useState(false); - const [isEnabled, setIsEnabled] = React.useState(rawAlert?.enabled); - const [isMuted, setIsMuted] = React.useState(rawAlert?.muteAll); - const [isSaving, setIsSaving] = React.useState(false); + const { alert, alertState } = props; const inSetupMode = isInSetupMode(React.useContext(SetupModeContext)); - const flyoutUi = useMemo( - () => - showFlyout && - Legacy.shims.triggersActionsUi.getEditAlertFlyout({ - initialAlert: rawAlert, - onClose: () => { - setShowFlyout(false); - showBottomBar(); - }, - }), - // eslint-disable-next-line react-hooks/exhaustive-deps - [showFlyout] - ); - - if (!rawAlert) { + if (!alert) { return null; } - async function disableAlert() { - setIsSaving(true); - try { - await Legacy.shims.http.post(`${BASE_ALERT_API_PATH}/alert/${rawAlert.id}/_disable`); - } catch (err) { - Legacy.shims.toastNotifications.addDanger({ - title: i18n.translate('xpack.monitoring.alerts.panel.disableAlert.errorTitle', { - defaultMessage: `Unable to disable alert`, - }), - text: err.message, - }); - } - setIsSaving(false); - } - async function enableAlert() { - setIsSaving(true); - try { - await Legacy.shims.http.post(`${BASE_ALERT_API_PATH}/alert/${rawAlert.id}/_enable`); - } catch (err) { - Legacy.shims.toastNotifications.addDanger({ - title: i18n.translate('xpack.monitoring.alerts.panel.enableAlert.errorTitle', { - defaultMessage: `Unable to enable alert`, - }), - text: err.message, - }); - } - setIsSaving(false); - } - async function muteAlert() { - setIsSaving(true); - try { - await Legacy.shims.http.post(`${BASE_ALERT_API_PATH}/alert/${rawAlert.id}/_mute_all`); - } catch (err) { - Legacy.shims.toastNotifications.addDanger({ - title: i18n.translate('xpack.monitoring.alerts.panel.muteAlert.errorTitle', { - defaultMessage: `Unable to mute alert`, - }), - text: err.message, - }); - } - setIsSaving(false); - } - async function unmuteAlert() { - setIsSaving(true); - try { - await Legacy.shims.http.post(`${BASE_ALERT_API_PATH}/alert/${rawAlert.id}/_unmute_all`); - } catch (err) { - Legacy.shims.toastNotifications.addDanger({ - title: i18n.translate('xpack.monitoring.alerts.panel.ummuteAlert.errorTitle', { - defaultMessage: `Unable to unmute alert`, - }), - text: err.message, - }); - } - setIsSaving(false); - } - - const configurationUi = ( - - - - { - setShowFlyout(true); - hideBottomBar(); - }} - > - {i18n.translate('xpack.monitoring.alerts.panel.editAlert', { - defaultMessage: `Edit alert`, - })} - - - - { - if (isEnabled) { - setIsEnabled(false); - await disableAlert(); - } else { - setIsEnabled(true); - await enableAlert(); - } - }} - label={ - - } - /> - - - { - if (isMuted) { - setIsMuted(false); - await unmuteAlert(); - } else { - setIsMuted(true); - await muteAlert(); - } - }} - label={ - - } - /> - - - {flyoutUi} - - ); - if (inSetupMode || !alertState) { - return
{configurationUi}
; + return ( +
+ +
+ ); } const nextStepsUi = @@ -204,7 +57,9 @@ export const AlertPanel: React.FC = (props: Props) => { {nextStepsUi} -
{configurationUi}
+
+ +
); }; diff --git a/x-pack/plugins/monitoring/public/alerts/types.ts b/x-pack/plugins/monitoring/public/alerts/types.ts new file mode 100644 index 0000000000000..80918b5d8767a --- /dev/null +++ b/x-pack/plugins/monitoring/public/alerts/types.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 React from 'react'; +import { CommonAlertStatus } from '../../common/types/alerts'; + +export interface AlertsByName { + [name: string]: CommonAlertStatus; +} + +export interface PanelItem { + id: number; + title: string; + width?: number; + content?: React.ReactElement; + items?: Array; +} + +export interface ContextMenuItem { + name: React.ReactElement; + panel?: number; + onClick?: () => void; +} + +export interface ContextMenuItemSeparator { + isSeparator: true; +} diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/node/advanced.js b/x-pack/plugins/monitoring/public/components/elasticsearch/node/advanced.js index 86e8a7d9095f5..78b5a6f58e238 100644 --- a/x-pack/plugins/monitoring/public/components/elasticsearch/node/advanced.js +++ b/x-pack/plugins/monitoring/public/components/elasticsearch/node/advanced.js @@ -20,7 +20,7 @@ import { MonitoringTimeseriesContainer } from '../../chart'; import { FormattedMessage } from '@kbn/i18n/react'; import { AlertsCallout } from '../../../alerts/callout'; -export const AdvancedNode = ({ nodeSummary, metrics, alerts, nodeId, ...props }) => { +export const AdvancedNode = ({ nodeSummary, metrics, alerts, ...props }) => { const metricsToShow = [ metrics.node_gc, metrics.node_gc_time, @@ -54,7 +54,7 @@ export const AdvancedNode = ({ nodeSummary, metrics, alerts, nodeId, ...props }) - state.nodeId === nodeId} /> + {metricsToShow.map((metric, index) => ( diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js b/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js index 84e7e5f8b1547..364886516bbd7 100644 --- a/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js +++ b/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js @@ -130,7 +130,6 @@ const getColumns = (showCgroupMetricsElasticsearch, setupMode, clusterUuid, aler defaultMessage: 'Alerts', }), field: 'alerts', - // width: '175px', sortable: true, render: (_field, node) => { return ( diff --git a/x-pack/plugins/monitoring/public/views/base_controller.js b/x-pack/plugins/monitoring/public/views/base_controller.js index 18c3a59d6b9da..f9c6cfb6024da 100644 --- a/x-pack/plugins/monitoring/public/views/base_controller.js +++ b/x-pack/plugins/monitoring/public/views/base_controller.js @@ -13,6 +13,7 @@ import { Legacy } from '../legacy_shims'; import { PromiseWithCancel } from '../../common/cancel_promise'; import { SetupModeFeature } from '../../common/enums'; import { updateSetupModeData, isSetupModeFeatureEnabled } from '../lib/setup_mode'; +import { AlertsContext } from '../alerts/context'; import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public'; /** @@ -243,11 +244,13 @@ export class MonitoringViewBaseController { const wrappedComponent = ( - {!this._isDataInitialized ? ( - - ) : ( - component - )} + + {!this._isDataInitialized ? ( + + ) : ( + component + )} + ); diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/node/index.js b/x-pack/plugins/monitoring/public/views/elasticsearch/node/index.js index 586261eecb250..2f0aa67e4e16b 100644 --- a/x-pack/plugins/monitoring/public/views/elasticsearch/node/index.js +++ b/x-pack/plugins/monitoring/public/views/elasticsearch/node/index.js @@ -14,6 +14,7 @@ import { uiRoutes } from '../../../angular/helpers/routes'; import { routeInitProvider } from '../../../lib/route_init'; import { getPageData } from './get_page_data'; import template from './index.html'; +import { SetupModeRenderer } from '../../../components/renderers'; import { Node } from '../../../components/elasticsearch/node/node'; import { labels } from '../../../components/elasticsearch/shard_allocation/lib/labels'; import { nodesByIndices } from '../../../components/elasticsearch/shard_allocation/transformers/nodes_by_indices'; @@ -26,7 +27,9 @@ import { ALERT_MISSING_MONITORING_DATA, ALERT_DISK_USAGE, ALERT_MEMORY_USAGE, + ELASTICSEARCH_SYSTEM_ID, } from '../../../../common/constants'; +import { SetupModeContext } from '../../../components/setup_mode/setup_mode_context'; uiRoutes.when('/elasticsearch/nodes/:node', { template, @@ -122,14 +125,26 @@ uiRoutes.when('/elasticsearch/nodes/:node', { $scope.labels = labels.node; this.renderReact( - ( + + {flyoutComponent} + + {bottomBarComponent} + + )} /> ); } diff --git a/x-pack/plugins/monitoring/server/alerts/base_alert.ts b/x-pack/plugins/monitoring/server/alerts/base_alert.ts index 1d8de2bab015c..4f989b37421ef 100644 --- a/x-pack/plugins/monitoring/server/alerts/base_alert.ts +++ b/x-pack/plugins/monitoring/server/alerts/base_alert.ts @@ -42,6 +42,7 @@ import { mapLegacySeverity } from '../lib/alerts/map_legacy_severity'; interface LegacyOptions { watchName: string; + nodeNameLabel: string; changeDataValues?: Partial; } @@ -322,6 +323,7 @@ export class BaseAlert { shouldFire: !legacyAlert.resolved_timestamp, severity: mapLegacySeverity(legacyAlert.metadata.severity), meta: legacyAlert, + nodeName: this.alertOptions.legacy!.nodeNameLabel, ...this.alertOptions.legacy!.changeDataValues, }; }); @@ -394,6 +396,7 @@ export class BaseAlert { } const cluster = clusters.find((c: AlertCluster) => c.clusterUuid === item.clusterUuid); const alertState: AlertState = this.getDefaultAlertState(cluster!, item); + alertState.nodeName = item.nodeName; alertState.ui.triggeredMS = currentUTC; alertState.ui.isFiring = true; alertState.ui.severity = item.severity; diff --git a/x-pack/plugins/monitoring/server/alerts/cluster_health_alert.test.ts b/x-pack/plugins/monitoring/server/alerts/cluster_health_alert.test.ts index a4e9f56109698..fbf81bc3513f6 100644 --- a/x-pack/plugins/monitoring/server/alerts/cluster_health_alert.test.ts +++ b/x-pack/plugins/monitoring/server/alerts/cluster_health_alert.test.ts @@ -119,6 +119,7 @@ describe('ClusterHealthAlert', () => { { cluster: { clusterUuid: 'abc123', clusterName: 'testCluster' }, ccs: undefined, + nodeName: 'Elasticsearch cluster alert', ui: { isFiring: true, message: { diff --git a/x-pack/plugins/monitoring/server/alerts/cluster_health_alert.ts b/x-pack/plugins/monitoring/server/alerts/cluster_health_alert.ts index 3b375654548d8..d7c33ae85cc9d 100644 --- a/x-pack/plugins/monitoring/server/alerts/cluster_health_alert.ts +++ b/x-pack/plugins/monitoring/server/alerts/cluster_health_alert.ts @@ -38,6 +38,9 @@ export class ClusterHealthAlert extends BaseAlert { name: LEGACY_ALERT_DETAILS[ALERT_CLUSTER_HEALTH].label, legacy: { watchName: 'elasticsearch_cluster_status', + nodeNameLabel: i18n.translate('xpack.monitoring.alerts.clusterHealth.nodeNameLabel', { + defaultMessage: 'Elasticsearch cluster alert', + }), }, actionVariables: [ { diff --git a/x-pack/plugins/monitoring/server/alerts/cpu_usage_alert.ts b/x-pack/plugins/monitoring/server/alerts/cpu_usage_alert.ts index 7bdef1ee2c2c4..c587ca52f26ab 100644 --- a/x-pack/plugins/monitoring/server/alerts/cpu_usage_alert.ts +++ b/x-pack/plugins/monitoring/server/alerts/cpu_usage_alert.ts @@ -5,6 +5,7 @@ */ import { i18n } from '@kbn/i18n'; +import numeral from '@elastic/numeral'; import { BaseAlert } from './base_alert'; import { AlertData, @@ -16,8 +17,8 @@ import { AlertMessageTimeToken, AlertMessageLinkToken, AlertInstanceState, - CommonAlertFilter, CommonAlertParams, + CommonAlertFilter, } from '../../common/types/alerts'; import { AlertInstance } from '../../../alerts/server'; import { @@ -25,6 +26,8 @@ import { ALERT_CPU_USAGE, ALERT_DETAILS, } from '../../common/constants'; +// @ts-ignore +import { ROUNDED_FLOAT } from '../../common/formatting'; import { fetchCpuUsageNodeStats } from '../lib/alerts/fetch_cpu_usage_node_stats'; import { getCcsIndexPattern } from '../lib/alerts/get_ccs_index_pattern'; import { AlertMessageTokenType, AlertSeverity } from '../../common/enums'; @@ -121,7 +124,7 @@ export class CpuUsageAlert extends BaseAlert { defaultMessage: `Node #start_link{nodeName}#end_link is reporting cpu usage of {cpuUsage}% at #absolute`, values: { nodeName: stat.nodeName, - cpuUsage: stat.cpuUsage, + cpuUsage: numeral(stat.cpuUsage).format(ROUNDED_FLOAT), }, }), nextSteps: [ diff --git a/x-pack/plugins/monitoring/server/alerts/disk_usage_alert.ts b/x-pack/plugins/monitoring/server/alerts/disk_usage_alert.ts index 133fe261d0791..4a736f27320eb 100644 --- a/x-pack/plugins/monitoring/server/alerts/disk_usage_alert.ts +++ b/x-pack/plugins/monitoring/server/alerts/disk_usage_alert.ts @@ -5,6 +5,7 @@ */ import { i18n } from '@kbn/i18n'; +import numeral from '@elastic/numeral'; import { BaseAlert } from './base_alert'; import { AlertData, @@ -15,8 +16,9 @@ import { AlertMessageTimeToken, AlertMessageLinkToken, AlertInstanceState, - CommonAlertFilter, CommonAlertParams, + AlertDiskUsageNodeStats, + CommonAlertFilter, } from '../../common/types/alerts'; import { AlertInstance } from '../../../alerts/server'; import { @@ -24,6 +26,8 @@ import { ALERT_DISK_USAGE, ALERT_DETAILS, } from '../../common/constants'; +// @ts-ignore +import { ROUNDED_FLOAT } from '../../common/formatting'; import { fetchDiskUsageNodeStats } from '../lib/alerts/fetch_disk_usage_node_stats'; import { getCcsIndexPattern } from '../lib/alerts/get_ccs_index_pattern'; import { AlertMessageTokenType, AlertSeverity } from '../../common/enums'; @@ -102,13 +106,13 @@ export class DiskUsageAlert extends BaseAlert { } protected getUiMessage(alertState: AlertState, item: AlertData): AlertMessage { - const stat = item.meta as AlertDiskUsageState; + const stat = item.meta as AlertDiskUsageNodeStats; return { text: i18n.translate('xpack.monitoring.alerts.diskUsage.ui.firingMessage', { defaultMessage: `Node #start_link{nodeName}#end_link is reporting disk usage of {diskUsage}% at #absolute`, values: { nodeName: stat.nodeName, - diskUsage: stat.diskUsage, + diskUsage: numeral(stat.diskUsage).format(ROUNDED_FLOAT), }, }), nextSteps: [ diff --git a/x-pack/plugins/monitoring/server/alerts/elasticsearch_version_mismatch_alert.test.ts b/x-pack/plugins/monitoring/server/alerts/elasticsearch_version_mismatch_alert.test.ts index 46fdd1fa98563..ed39cbea3381c 100644 --- a/x-pack/plugins/monitoring/server/alerts/elasticsearch_version_mismatch_alert.test.ts +++ b/x-pack/plugins/monitoring/server/alerts/elasticsearch_version_mismatch_alert.test.ts @@ -124,6 +124,7 @@ describe('ElasticsearchVersionMismatchAlert', () => { { cluster: { clusterUuid: 'abc123', clusterName: 'testCluster' }, ccs: undefined, + nodeName: 'Elasticsearch node alert', ui: { isFiring: true, message: { diff --git a/x-pack/plugins/monitoring/server/alerts/elasticsearch_version_mismatch_alert.ts b/x-pack/plugins/monitoring/server/alerts/elasticsearch_version_mismatch_alert.ts index 88b5b708d41f3..9002fb54fcf23 100644 --- a/x-pack/plugins/monitoring/server/alerts/elasticsearch_version_mismatch_alert.ts +++ b/x-pack/plugins/monitoring/server/alerts/elasticsearch_version_mismatch_alert.ts @@ -26,6 +26,12 @@ export class ElasticsearchVersionMismatchAlert extends BaseAlert { name: LEGACY_ALERT_DETAILS[ALERT_ELASTICSEARCH_VERSION_MISMATCH].label, legacy: { watchName: 'elasticsearch_version_mismatch', + nodeNameLabel: i18n.translate( + 'xpack.monitoring.alerts.elasticsearchVersionMismatch.nodeNameLabel', + { + defaultMessage: 'Elasticsearch node alert', + } + ), changeDataValues: { severity: AlertSeverity.Warning }, }, interval: '1d', diff --git a/x-pack/plugins/monitoring/server/alerts/kibana_version_mismatch_alert.test.ts b/x-pack/plugins/monitoring/server/alerts/kibana_version_mismatch_alert.test.ts index 2367b53330ec5..96464e16f8c72 100644 --- a/x-pack/plugins/monitoring/server/alerts/kibana_version_mismatch_alert.test.ts +++ b/x-pack/plugins/monitoring/server/alerts/kibana_version_mismatch_alert.test.ts @@ -126,6 +126,7 @@ describe('KibanaVersionMismatchAlert', () => { { cluster: { clusterUuid: 'abc123', clusterName: 'testCluster' }, ccs: undefined, + nodeName: 'Kibana instance alert', ui: { isFiring: true, message: { diff --git a/x-pack/plugins/monitoring/server/alerts/kibana_version_mismatch_alert.ts b/x-pack/plugins/monitoring/server/alerts/kibana_version_mismatch_alert.ts index c9e5786484899..0d394b8f45ecc 100644 --- a/x-pack/plugins/monitoring/server/alerts/kibana_version_mismatch_alert.ts +++ b/x-pack/plugins/monitoring/server/alerts/kibana_version_mismatch_alert.ts @@ -26,6 +26,12 @@ export class KibanaVersionMismatchAlert extends BaseAlert { name: LEGACY_ALERT_DETAILS[ALERT_KIBANA_VERSION_MISMATCH].label, legacy: { watchName: 'kibana_version_mismatch', + nodeNameLabel: i18n.translate( + 'xpack.monitoring.alerts.kibanaVersionMismatch.nodeNameLabel', + { + defaultMessage: 'Kibana instance alert', + } + ), changeDataValues: { severity: AlertSeverity.Warning }, }, interval: '1d', diff --git a/x-pack/plugins/monitoring/server/alerts/license_expiration_alert.test.ts b/x-pack/plugins/monitoring/server/alerts/license_expiration_alert.test.ts index f7a3d321b960b..c64b6e4b92984 100644 --- a/x-pack/plugins/monitoring/server/alerts/license_expiration_alert.test.ts +++ b/x-pack/plugins/monitoring/server/alerts/license_expiration_alert.test.ts @@ -130,6 +130,7 @@ describe('LicenseExpirationAlert', () => { { cluster: { clusterUuid, clusterName }, ccs: undefined, + nodeName: 'Elasticsearch cluster alert', ui: { isFiring: true, message: { diff --git a/x-pack/plugins/monitoring/server/alerts/license_expiration_alert.ts b/x-pack/plugins/monitoring/server/alerts/license_expiration_alert.ts index 80479023a3a60..9dacba1dfe0ec 100644 --- a/x-pack/plugins/monitoring/server/alerts/license_expiration_alert.ts +++ b/x-pack/plugins/monitoring/server/alerts/license_expiration_alert.ts @@ -34,6 +34,9 @@ export class LicenseExpirationAlert extends BaseAlert { name: LEGACY_ALERT_DETAILS[ALERT_LICENSE_EXPIRATION].label, legacy: { watchName: 'xpack_license_expiration', + nodeNameLabel: i18n.translate('xpack.monitoring.alerts.licenseExpiration.nodeNameLabel', { + defaultMessage: 'Elasticsearch cluster alert', + }), }, interval: '1d', actionVariables: [ diff --git a/x-pack/plugins/monitoring/server/alerts/logstash_version_mismatch_alert.test.ts b/x-pack/plugins/monitoring/server/alerts/logstash_version_mismatch_alert.test.ts index a021a0e6fe179..dd23cfc76dc6d 100644 --- a/x-pack/plugins/monitoring/server/alerts/logstash_version_mismatch_alert.test.ts +++ b/x-pack/plugins/monitoring/server/alerts/logstash_version_mismatch_alert.test.ts @@ -125,6 +125,7 @@ describe('LogstashVersionMismatchAlert', () => { { cluster: { clusterUuid: 'abc123', clusterName: 'testCluster' }, ccs: undefined, + nodeName: 'Logstash node alert', ui: { isFiring: true, message: { diff --git a/x-pack/plugins/monitoring/server/alerts/logstash_version_mismatch_alert.ts b/x-pack/plugins/monitoring/server/alerts/logstash_version_mismatch_alert.ts index 98640fb6e183a..4eae3cd12eed4 100644 --- a/x-pack/plugins/monitoring/server/alerts/logstash_version_mismatch_alert.ts +++ b/x-pack/plugins/monitoring/server/alerts/logstash_version_mismatch_alert.ts @@ -26,6 +26,12 @@ export class LogstashVersionMismatchAlert extends BaseAlert { name: LEGACY_ALERT_DETAILS[ALERT_LOGSTASH_VERSION_MISMATCH].label, legacy: { watchName: 'logstash_version_mismatch', + nodeNameLabel: i18n.translate( + 'xpack.monitoring.alerts.logstashVersionMismatch.nodeNameLabel', + { + defaultMessage: 'Logstash node alert', + } + ), changeDataValues: { severity: AlertSeverity.Warning }, }, interval: '1d', diff --git a/x-pack/plugins/monitoring/server/alerts/memory_usage_alert.ts b/x-pack/plugins/monitoring/server/alerts/memory_usage_alert.ts index 860cd41f9057d..d5ea291aa52ed 100644 --- a/x-pack/plugins/monitoring/server/alerts/memory_usage_alert.ts +++ b/x-pack/plugins/monitoring/server/alerts/memory_usage_alert.ts @@ -5,6 +5,7 @@ */ import { i18n } from '@kbn/i18n'; +import numeral from '@elastic/numeral'; import { BaseAlert } from './base_alert'; import { AlertData, @@ -15,8 +16,9 @@ import { AlertMessageTimeToken, AlertMessageLinkToken, AlertInstanceState, - CommonAlertFilter, CommonAlertParams, + AlertMemoryUsageNodeStats, + CommonAlertFilter, } from '../../common/types/alerts'; import { AlertInstance } from '../../../alerts/server'; import { @@ -24,6 +26,8 @@ import { ALERT_MEMORY_USAGE, ALERT_DETAILS, } from '../../common/constants'; +// @ts-ignore +import { ROUNDED_FLOAT } from '../../common/formatting'; import { fetchMemoryUsageNodeStats } from '../lib/alerts/fetch_memory_usage_node_stats'; import { getCcsIndexPattern } from '../lib/alerts/get_ccs_index_pattern'; import { AlertMessageTokenType, AlertSeverity } from '../../common/enums'; @@ -108,13 +112,13 @@ export class MemoryUsageAlert extends BaseAlert { } protected getUiMessage(alertState: AlertState, item: AlertData): AlertMessage { - const stat = item.meta as AlertMemoryUsageState; + const stat = item.meta as AlertMemoryUsageNodeStats; return { text: i18n.translate('xpack.monitoring.alerts.memoryUsage.ui.firingMessage', { defaultMessage: `Node #start_link{nodeName}#end_link is reporting JVM memory usage of {memoryUsage}% at #absolute`, values: { nodeName: stat.nodeName, - memoryUsage: stat.memoryUsage, + memoryUsage: numeral(stat.memoryUsage).format(ROUNDED_FLOAT), }, }), nextSteps: [ diff --git a/x-pack/plugins/monitoring/server/alerts/missing_monitoring_data_alert.test.ts b/x-pack/plugins/monitoring/server/alerts/missing_monitoring_data_alert.test.ts index 12bb27ce132d0..6ba4333309f00 100644 --- a/x-pack/plugins/monitoring/server/alerts/missing_monitoring_data_alert.test.ts +++ b/x-pack/plugins/monitoring/server/alerts/missing_monitoring_data_alert.test.ts @@ -128,9 +128,9 @@ describe('MissingMonitoringDataAlert', () => { { ccs: undefined, cluster: { clusterUuid, clusterName }, - gapDuration, - nodeName, nodeId, + nodeName, + gapDuration, ui: { isFiring: true, message: { diff --git a/x-pack/plugins/monitoring/server/alerts/missing_monitoring_data_alert.ts b/x-pack/plugins/monitoring/server/alerts/missing_monitoring_data_alert.ts index 1c93ff4a28719..b4c8a667a1ce8 100644 --- a/x-pack/plugins/monitoring/server/alerts/missing_monitoring_data_alert.ts +++ b/x-pack/plugins/monitoring/server/alerts/missing_monitoring_data_alert.ts @@ -12,10 +12,9 @@ import { AlertCluster, AlertState, AlertMessage, - AlertNodeState, AlertMessageTimeToken, - CommonAlertFilter, CommonAlertParams, + CommonAlertFilter, } from '../../common/types/alerts'; import { AlertInstance } from '../../../alerts/server'; import { @@ -40,12 +39,12 @@ export class MissingMonitoringDataAlert extends BaseAlert { super(rawAlert, { id: ALERT_MISSING_MONITORING_DATA, name: ALERT_DETAILS[ALERT_MISSING_MONITORING_DATA].label, + accessorKey: 'gapDuration', defaultParams: { duration: '15m', limit: '1d', }, throttle: '6h', - accessorKey: 'gapDuration', actionVariables: [ { name: 'nodes', @@ -153,7 +152,7 @@ export class MissingMonitoringDataAlert extends BaseAlert { protected executeActions( instance: AlertInstance, - { alertStates }: { alertStates: AlertNodeState[] }, + { alertStates }: { alertStates: AlertState[] }, item: AlertData | null, cluster: AlertCluster ) { diff --git a/x-pack/plugins/monitoring/server/alerts/nodes_changed_alert.test.ts b/x-pack/plugins/monitoring/server/alerts/nodes_changed_alert.test.ts index 99be91dc293cb..e6017e799cba8 100644 --- a/x-pack/plugins/monitoring/server/alerts/nodes_changed_alert.test.ts +++ b/x-pack/plugins/monitoring/server/alerts/nodes_changed_alert.test.ts @@ -137,6 +137,7 @@ describe('NodesChangedAlert', () => { { cluster: { clusterUuid, clusterName }, ccs: undefined, + nodeName: 'Elasticsearch nodes alert', ui: { isFiring: true, message: { diff --git a/x-pack/plugins/monitoring/server/alerts/nodes_changed_alert.ts b/x-pack/plugins/monitoring/server/alerts/nodes_changed_alert.ts index 47d5c5ac2c241..09c12d345d930 100644 --- a/x-pack/plugins/monitoring/server/alerts/nodes_changed_alert.ts +++ b/x-pack/plugins/monitoring/server/alerts/nodes_changed_alert.ts @@ -26,6 +26,9 @@ export class NodesChangedAlert extends BaseAlert { name: LEGACY_ALERT_DETAILS[ALERT_NODES_CHANGED].label, legacy: { watchName: 'elasticsearch_nodes', + nodeNameLabel: i18n.translate('xpack.monitoring.alerts.nodesChanged.nodeNameLabel', { + defaultMessage: 'Elasticsearch nodes alert', + }), changeDataValues: { shouldFire: true }, }, actionVariables: [ diff --git a/x-pack/plugins/monitoring/server/alerts/thread_pool_rejections_alert_base.ts b/x-pack/plugins/monitoring/server/alerts/thread_pool_rejections_alert_base.ts index 2d8ccabaac853..1e539c52eeedc 100644 --- a/x-pack/plugins/monitoring/server/alerts/thread_pool_rejections_alert_base.ts +++ b/x-pack/plugins/monitoring/server/alerts/thread_pool_rejections_alert_base.ts @@ -13,8 +13,8 @@ import { AlertThreadPoolRejectionsState, AlertMessageTimeToken, AlertMessageLinkToken, - CommonAlertFilter, ThreadPoolRejectionsAlertParams, + CommonAlertFilter, } from '../../common/types/alerts'; import { AlertInstance } from '../../../alerts/server'; import { INDEX_PATTERN_ELASTICSEARCH } from '../../common/constants'; diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_legacy_alerts.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_legacy_alerts.test.ts index a3743a8ff206f..5055017051816 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_legacy_alerts.test.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_legacy_alerts.test.ts @@ -47,6 +47,7 @@ describe('fetchLegacyAlerts', () => { message, metadata, nodes, + nodeName: '', prefix, }, ]); diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_legacy_alerts.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_legacy_alerts.ts index fbf7608a737ba..0ea37b4ac4daa 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_legacy_alerts.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_legacy_alerts.ts @@ -86,6 +86,7 @@ export async function fetchLegacyAlerts( message: get(hit, '_source.message'), resolved_timestamp: get(hit, '_source.resolved_timestamp'), nodes: get(hit, '_source.nodes'), + nodeName: '', // This is set by BaseAlert metadata: get(hit, '_source.metadata') as LegacyAlertMetadata, }; return legacyAlert; diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/__test__/__snapshots__/handle_response.test.js.snap b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/__test__/__snapshots__/handle_response.test.js.snap index db74cc5e330a1..ba8506bfd0087 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/__test__/__snapshots__/handle_response.test.js.snap +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/__test__/__snapshots__/handle_response.test.js.snap @@ -11,6 +11,7 @@ Array [ "shardCount": 6, "transport_address": "127.0.0.1:9300", "type": "node", + "uuid": "_x_V2YzPQU-a9KRRBxUxZQ", }, Object { "isOnline": false, @@ -21,6 +22,7 @@ Array [ "shardCount": 6, "transport_address": "127.0.0.1:9301", "type": "node", + "uuid": "DAiX7fFjS3Wii7g2HYKrOg", }, ] `; @@ -156,6 +158,7 @@ Array [ "shardCount": 0, "transport_address": "127.0.0.1:9300", "type": "master", + "uuid": "_x_V2YzPQU-a9KRRBxUxZQ", }, Object { "isOnline": true, @@ -265,6 +268,7 @@ Array [ "shardCount": 0, "transport_address": "127.0.0.1:9301", "type": "node", + "uuid": "DAiX7fFjS3Wii7g2HYKrOg", }, ] `; @@ -286,6 +290,7 @@ Array [ "shardCount": 6, "transport_address": "127.0.0.1:9300", "type": "master", + "uuid": "_x_V2YzPQU-a9KRRBxUxZQ", }, Object { "isOnline": true, @@ -302,6 +307,7 @@ Array [ "shardCount": 6, "transport_address": "127.0.0.1:9301", "type": "node", + "uuid": "DAiX7fFjS3Wii7g2HYKrOg", }, ] `; @@ -435,6 +441,7 @@ Array [ "shardCount": 6, "transport_address": "127.0.0.1:9300", "type": "master", + "uuid": "_x_V2YzPQU-a9KRRBxUxZQ", }, Object { "isOnline": true, @@ -544,6 +551,7 @@ Array [ "shardCount": 6, "transport_address": "127.0.0.1:9301", "type": "node", + "uuid": "DAiX7fFjS3Wii7g2HYKrOg", }, ] `; diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_nodes.js b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_nodes.js index 3766845d39b4f..ac4fcea6150a0 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_nodes.js +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_nodes.js @@ -25,8 +25,9 @@ import { LISTING_METRICS_NAMES, LISTING_METRICS_PATHS } from './nodes_listing_me * * @param {Object} req: server request object * @param {String} esIndexPattern: index pattern for elasticsearch data in monitoring indices + * @param {Object} pageOfNodes: server-side paginated current page of ES nodes * @param {Object} clusterStats: cluster stats from cluster state document - * @param {Object} shardStats: per-node information about shards + * @param {Object} nodesShardCount: per-node information about shards * @return {Array} node info combined with metrics for each node from handle_response */ export async function getNodes(req, esIndexPattern, pageOfNodes, clusterStats, nodesShardCount) { diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/handle_response.js b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/handle_response.js index 62cf138c99506..3f82e8ec3e646 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/handle_response.js +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/handle_response.js @@ -47,6 +47,7 @@ export function handleResponse( // nodesInfo is the source of truth for the nodeIds, where nodesMetrics will lack metrics for offline nodes const nodes = pageOfNodes.map((node) => ({ + ...node, ...nodesInfo[node.uuid], ...nodesMetrics[node.uuid], resolver: node.uuid, diff --git a/x-pack/plugins/monitoring/server/lib/pagination/filter.js b/x-pack/plugins/monitoring/server/lib/pagination/filter.js index 7592f2bb3afde..e906081a8eb5a 100644 --- a/x-pack/plugins/monitoring/server/lib/pagination/filter.js +++ b/x-pack/plugins/monitoring/server/lib/pagination/filter.js @@ -15,7 +15,7 @@ function defaultFilterFn(value, query) { export function filter(data, queryText, fields, filterFn = defaultFilterFn) { return data.filter((item) => { for (const field of fields) { - if (filterFn(get(item, field), queryText)) { + if (filterFn(get(item, field, ''), queryText)) { return true; } } diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 7845a003b59ee..4ec88a500b42a 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -13564,8 +13564,6 @@ "xpack.monitoring.alerts.actionVariables.internalShortMessage": "内部メッセージ(省略あり)はElasticで生成されました。", "xpack.monitoring.alerts.actionVariables.state": "現在のアラートの状態。", "xpack.monitoring.alerts.badge.panelTitle": "アラート", - "xpack.monitoring.alerts.callout.dangerLabel": "危険アラート", - "xpack.monitoring.alerts.callout.warningLabel": "警告アラート", "xpack.monitoring.alerts.clusterHealth.action.danger": "見つからないプライマリおよびレプリカシャードを割り当てます。", "xpack.monitoring.alerts.clusterHealth.action.warning": "見つからないレプリカシャードを割り当てます。", "xpack.monitoring.alerts.clusterHealth.actionVariables.clusterHealth": "クラスターの正常性。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 83c98b794e002..c3a63660ad057 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -13581,8 +13581,6 @@ "xpack.monitoring.alerts.actionVariables.internalShortMessage": "Elastic 生成的简短内部消息。", "xpack.monitoring.alerts.actionVariables.state": "告警的当前状态。", "xpack.monitoring.alerts.badge.panelTitle": "告警", - "xpack.monitoring.alerts.callout.dangerLabel": "危险告警", - "xpack.monitoring.alerts.callout.warningLabel": "警告告警", "xpack.monitoring.alerts.clusterHealth.action.danger": "分配缺失的主分片和副本分片。", "xpack.monitoring.alerts.clusterHealth.action.warning": "分配缺失的副本分片。", "xpack.monitoring.alerts.clusterHealth.actionVariables.clusterHealth": "集群的运行状况。", diff --git a/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/nodes_listing_cgroup.json b/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/nodes_listing_cgroup.json index 2e71a6a1551e4..b533480292e2f 100644 --- a/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/nodes_listing_cgroup.json +++ b/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/nodes_listing_cgroup.json @@ -139,7 +139,8 @@ "slope": -1 } }, - "resolver": "_x_V2YzPQU-a9KRRBxUxZQ" + "resolver": "_x_V2YzPQU-a9KRRBxUxZQ", + "uuid": "_x_V2YzPQU-a9KRRBxUxZQ" }, { "name": "hello02", @@ -247,7 +248,8 @@ "slope": -1 } }, - "resolver": "DAiX7fFjS3Wii7g2HYKrOg" + "resolver": "DAiX7fFjS3Wii7g2HYKrOg", + "uuid": "DAiX7fFjS3Wii7g2HYKrOg" } ], "totalNodeCount": 2 diff --git a/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/nodes_listing_green.json b/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/nodes_listing_green.json index 0a18664faf445..ef9fb46909715 100644 --- a/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/nodes_listing_green.json +++ b/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/nodes_listing_green.json @@ -97,6 +97,7 @@ } }, "resolver": "ENVgDIKRSdCVJo-YqY4kUQ", + "uuid": "ENVgDIKRSdCVJo-YqY4kUQ", "shardCount": 54, "transport_address": "127.0.0.1:9300", "type": "master" @@ -185,6 +186,7 @@ } }, "resolver": "t9J9jvHpQ2yDw9c1LJ0tHA", + "uuid": "t9J9jvHpQ2yDw9c1LJ0tHA", "shardCount": 54, "transport_address": "127.0.0.1:9301", "type": "node" diff --git a/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/nodes_listing_red.json b/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/nodes_listing_red.json index d9c04838fab10..f2e129cca32d1 100644 --- a/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/nodes_listing_red.json +++ b/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/nodes_listing_red.json @@ -97,6 +97,7 @@ } }, "resolver": "_WmX0plYQwm2z6tfCPyCQw", + "uuid": "_WmX0plYQwm2z6tfCPyCQw", "shardCount": 23, "transport_address": "127.0.0.1:9300", "type": "master" @@ -107,6 +108,7 @@ "nodeTypeClass": "storage", "nodeTypeLabel": "Node", "resolver": "1jxg5T33TWub-jJL4qP0Wg", + "uuid": "1jxg5T33TWub-jJL4qP0Wg", "shardCount": 0, "transport_address": "127.0.0.1:9302", "type": "node" From 96bb72f68d4a239df4fc160a3011f11d495aa609 Mon Sep 17 00:00:00 2001 From: Larry Gregory Date: Sun, 13 Dec 2020 12:40:50 -0500 Subject: [PATCH 02/95] Fix fleet route protections (#85626) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/plugins/fleet/server/routes/security.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/fleet/server/routes/security.ts b/x-pack/plugins/fleet/server/routes/security.ts index c2348c313e583..ec89668111860 100644 --- a/x-pack/plugins/fleet/server/routes/security.ts +++ b/x-pack/plugins/fleet/server/routes/security.ts @@ -14,7 +14,12 @@ export function enforceSuperUser( const security = appContextService.getSecurity(); const user = security.authc.getCurrentUser(req); if (!user) { - return res.unauthorized(); + return res.forbidden({ + body: { + message: + 'Access to Fleet API require the superuser role, and for stack security features to be enabled.', + }, + }); } const userRoles = user.roles || []; From 7c9bc47bf824986b9ac3194edc2ee363d28b2f85 Mon Sep 17 00:00:00 2001 From: Joe Portner <5295965+jportner@users.noreply.github.com> Date: Sun, 13 Dec 2020 15:36:21 -0500 Subject: [PATCH 03/95] ini `1.3.5` -> `1.3.7` (#85707) --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 9f29f8ed94a00..cc7edd2aaeccb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16104,9 +16104,9 @@ inherits@2.0.3: integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= ini@^1.2.0, ini@^1.3.4, ini@^1.3.5, ini@~1.3.0: - version "1.3.5" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" - integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== + version "1.3.7" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.7.tgz#a09363e1911972ea16d7a8851005d84cf09a9a84" + integrity sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ== inline-source-map@~0.6.0: version "0.6.2" From ae64fc259222f1147c1500104d7dcb4cfa263b63 Mon Sep 17 00:00:00 2001 From: Andrew Wilkins Date: Mon, 14 Dec 2020 13:47:35 +0800 Subject: [PATCH 04/95] [APM] enable 'log_level' for Go (#85511) https://github.com/elastic/apm-agent-go/pull/859 adds central config support for 'log_level' to the Go agent, so we can now enable it in the UI too. Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../agent_configuration/setting_definitions/general_settings.ts | 2 +- .../agent_configuration/setting_definitions/index.test.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/general_settings.ts b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/general_settings.ts index f243bcc0c694e..d3063c9715992 100644 --- a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/general_settings.ts +++ b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/general_settings.ts @@ -110,7 +110,7 @@ export const generalSettings: RawSettingDefinition[] = [ { text: 'critical', value: 'critical' }, { text: 'off', value: 'off' }, ], - includeAgents: ['dotnet', 'ruby', 'java', 'python', 'nodejs'], + includeAgents: ['dotnet', 'ruby', 'java', 'python', 'nodejs', 'go'], }, // Recording diff --git a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/index.test.ts b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/index.test.ts index ac0820309e77c..e0e7e84810090 100644 --- a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/index.test.ts +++ b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/index.test.ts @@ -45,6 +45,7 @@ describe('filterByAgent', () => { expect(getSettingKeysForAgent('go')).toEqual([ 'capture_body', 'capture_headers', + 'log_level', 'recording', 'sanitize_field_names', 'span_frames_min_duration', From a719990616da8237e726afdd1d41401182453493 Mon Sep 17 00:00:00 2001 From: MadameSheema Date: Mon, 14 Dec 2020 09:08:34 +0100 Subject: [PATCH 05/95] fixes EQL tests (#85712) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../security_solution/cypress/screens/shared.ts | 2 +- .../cypress/tasks/create_new_rule.ts | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/security_solution/cypress/screens/shared.ts b/x-pack/plugins/security_solution/cypress/screens/shared.ts index ccfe0f97c732c..eae8de6d5ee8b 100644 --- a/x-pack/plugins/security_solution/cypress/screens/shared.ts +++ b/x-pack/plugins/security_solution/cypress/screens/shared.ts @@ -6,4 +6,4 @@ export const NOTIFICATION_TOASTS = '[data-test-subj="globalToastList"]'; -export const TOAST_ERROR_CLASS = 'euiToast--danger'; +export const TOAST_ERROR = '.euiToast--danger'; diff --git a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts index cb099ea26d37b..026813e4a11cf 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts @@ -71,7 +71,7 @@ import { MITRE_ATTACK_ADD_SUBTECHNIQUE_BUTTON, MITRE_ATTACK_ADD_TECHNIQUE_BUTTON, } from '../screens/create_new_rule'; -import { NOTIFICATION_TOASTS, TOAST_ERROR_CLASS } from '../screens/shared'; +import { TOAST_ERROR } from '../screens/shared'; import { SERVER_SIDE_EVENT_COUNT } from '../screens/timeline'; import { TIMELINE } from '../screens/timelines'; import { refreshPage } from './security_header'; @@ -262,11 +262,20 @@ export const fillDefineThresholdRuleAndContinue = (rule: ThresholdRule) => { }; export const fillDefineEqlRuleAndContinue = (rule: CustomRule) => { + cy.get(EQL_QUERY_INPUT).should('exist'); + cy.get(EQL_QUERY_INPUT).should('be.visible'); cy.get(EQL_QUERY_INPUT).type(rule.customQuery!); cy.get(EQL_QUERY_VALIDATION_SPINNER).should('not.exist'); cy.get(QUERY_PREVIEW_BUTTON).should('not.be.disabled').click({ force: true }); - cy.get(EQL_QUERY_PREVIEW_HISTOGRAM).should('contain.text', 'Hits'); - cy.get(NOTIFICATION_TOASTS).children().should('not.have.class', TOAST_ERROR_CLASS); // asserts no error toast on page + cy.get(EQL_QUERY_PREVIEW_HISTOGRAM) + .invoke('text') + .then((text) => { + if (text !== 'Hits') { + cy.get(QUERY_PREVIEW_BUTTON).click({ force: true }); + cy.get(EQL_QUERY_PREVIEW_HISTOGRAM).should('contain.text', 'Hits'); + } + }); + cy.get(TOAST_ERROR).should('not.exist'); cy.get(DEFINE_CONTINUE_BUTTON).should('exist').click({ force: true }); cy.get(EQL_QUERY_INPUT).should('not.exist'); From acdca75563da8e4d4a5194d904f673dd341892a7 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Mon, 14 Dec 2020 11:02:08 +0200 Subject: [PATCH 06/95] [Visualize] Removes the external link icon from OSS badges (#85580) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../public/wizard/group_selection/group_selection.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plugins/visualizations/public/wizard/group_selection/group_selection.tsx b/src/plugins/visualizations/public/wizard/group_selection/group_selection.tsx index 8520b84cc42ad..dcf885a817e91 100644 --- a/src/plugins/visualizations/public/wizard/group_selection/group_selection.tsx +++ b/src/plugins/visualizations/public/wizard/group_selection/group_selection.tsx @@ -204,6 +204,7 @@ const VisGroup = ({ visType, onVisTypeSelected }: VisCardProps) => { target="_blank" color="text" className="visNewVisDialog__groupsCardLink" + external={false} > Date: Mon, 14 Dec 2020 14:20:52 +0300 Subject: [PATCH 07/95] [TSVB] Fields lists do not populate all the times (#85530) --- .../application/components/vis_editor.js | 38 +++++++++---------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_editor.js b/src/plugins/vis_type_timeseries/public/application/components/vis_editor.js index 520ad281576cd..89e7a50ab79b0 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/vis_editor.js +++ b/src/plugins/vis_type_timeseries/public/application/components/vis_editor.js @@ -74,20 +74,26 @@ export class VisEditor extends Component { this.props.eventEmitter.emit('dirtyStateChange', { isDirty: false, }); + + const extractedIndexPatterns = extractIndexPatterns(this.state.model); + if (!isEqual(this.state.extractedIndexPatterns, extractedIndexPatterns)) { + this.abortableFetchFields(extractedIndexPatterns).then((visFields) => { + this.setState({ + visFields, + extractedIndexPatterns, + }); + }); + } }, VIS_STATE_DEBOUNCE_DELAY); - debouncedFetchFields = debounce( - (extractedIndexPatterns) => { - if (this.abortControllerFetchFields) { - this.abortControllerFetchFields.abort(); - } - this.abortControllerFetchFields = new AbortController(); + abortableFetchFields = (extractedIndexPatterns) => { + if (this.abortControllerFetchFields) { + this.abortControllerFetchFields.abort(); + } + this.abortControllerFetchFields = new AbortController(); - return fetchFields(extractedIndexPatterns, this.abortControllerFetchFields.signal); - }, - VIS_STATE_DEBOUNCE_DELAY, - { leading: true } - ); + return fetchFields(extractedIndexPatterns, this.abortControllerFetchFields.signal); + }; handleChange = (partialModel) => { if (isEmpty(partialModel)) { @@ -105,16 +111,6 @@ export class VisEditor extends Component { dirty = false; } - const extractedIndexPatterns = extractIndexPatterns(nextModel); - if (!isEqual(this.state.extractedIndexPatterns, extractedIndexPatterns)) { - this.debouncedFetchFields(extractedIndexPatterns).then((visFields) => - this.setState({ - visFields, - extractedIndexPatterns, - }) - ); - } - this.setState({ dirty, model: nextModel, From 5a8a5bfd4c96438d10a7a23b9247d54e21233382 Mon Sep 17 00:00:00 2001 From: Thom Heymann <190132+thomheymann@users.noreply.github.com> Date: Mon, 14 Dec 2020 11:34:52 +0000 Subject: [PATCH 08/95] Add session id to audit log (#85451) * Add session id to audit log * fix naming Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../security/server/audit/audit_events.ts | 40 ++++++++++++++++--- .../server/audit/audit_service.test.ts | 20 ++++++++-- .../security/server/audit/audit_service.ts | 15 ++++--- x-pack/plugins/security/server/plugin.ts | 17 ++++---- .../server/session_management/session.mock.ts | 1 + .../server/session_management/session.test.ts | 14 +++++++ .../server/session_management/session.ts | 11 +++++ 7 files changed, 92 insertions(+), 26 deletions(-) diff --git a/x-pack/plugins/security/server/audit/audit_events.ts b/x-pack/plugins/security/server/audit/audit_events.ts index 0bb2f8ba1a246..59184562b67ff 100644 --- a/x-pack/plugins/security/server/audit/audit_events.ts +++ b/x-pack/plugins/security/server/audit/audit_events.ts @@ -8,8 +8,11 @@ import { KibanaRequest } from 'src/core/server'; import { AuthenticationResult } from '../authentication/authentication_result'; /** - * Audit event schema using ECS format. - * https://www.elastic.co/guide/en/ecs/1.6/index.html + * Audit event schema using ECS format: https://www.elastic.co/guide/en/ecs/1.6/index.html + * + * If you add additional fields to the schema ensure you update the Kibana Filebeat module: + * https://github.com/elastic/beats/tree/master/filebeat/module/kibana + * * @public */ export interface AuditEvent { @@ -37,20 +40,45 @@ export interface AuditEvent { }; kibana?: { /** - * Current space id of the request. + * The ID of the space associated with this event. */ space_id?: string; /** - * Saved object that was created, changed, deleted or accessed as part of the action. + * The ID of the user session associated with this event. Each login attempt + * results in a unique session id. + */ + session_id?: string; + /** + * Saved object that was created, changed, deleted or accessed as part of this event. */ saved_object?: { type: string; id: string; }; /** - * Any additional event specific fields. + * Name of authentication provider associated with a login event. + */ + authentication_provider?: string; + /** + * Type of authentication provider associated with a login event. + */ + authentication_type?: string; + /** + * Name of Elasticsearch realm that has authenticated the user. + */ + authentication_realm?: string; + /** + * Name of Elasticsearch realm where the user details were retrieved from. + */ + lookup_realm?: string; + /** + * Set of space IDs that a saved object was shared to. + */ + add_to_spaces?: readonly string[]; + /** + * Set of space IDs that a saved object was removed from. */ - [x: string]: any; + delete_from_spaces?: readonly string[]; }; error?: { code?: string; diff --git a/x-pack/plugins/security/server/audit/audit_service.test.ts b/x-pack/plugins/security/server/audit/audit_service.test.ts index a9c7668871248..91c656ad69f18 100644 --- a/x-pack/plugins/security/server/audit/audit_service.test.ts +++ b/x-pack/plugins/security/server/audit/audit_service.test.ts @@ -27,6 +27,7 @@ const { logging } = coreMock.createSetup(); const http = httpServiceMock.createSetupContract(); const getCurrentUser = jest.fn().mockReturnValue({ username: 'jdoe', roles: ['admin'] }); const getSpaceId = jest.fn().mockReturnValue('default'); +const getSID = jest.fn().mockResolvedValue('SESSION_ID'); beforeEach(() => { logger.info.mockClear(); @@ -45,6 +46,7 @@ describe('#setup', () => { http, getCurrentUser, getSpaceId, + getSID, }) ).toMatchInlineSnapshot(` Object { @@ -70,6 +72,7 @@ describe('#setup', () => { http, getCurrentUser, getSpaceId, + getSID, }); expect(logging.configure).toHaveBeenCalledWith(expect.any(Observable)); }); @@ -82,6 +85,7 @@ describe('#setup', () => { http, getCurrentUser, getSpaceId, + getSID, }); expect(http.registerOnPostAuth).toHaveBeenCalledWith(expect.any(Function)); }); @@ -96,16 +100,17 @@ describe('#asScoped', () => { http, getCurrentUser, getSpaceId, + getSID, }); const request = httpServerMock.createKibanaRequest({ kibanaRequestState: { requestId: 'REQUEST_ID', requestUuid: 'REQUEST_UUID' }, }); - audit.asScoped(request).log({ message: 'MESSAGE', event: { action: 'ACTION' } }); + await audit.asScoped(request).log({ message: 'MESSAGE', event: { action: 'ACTION' } }); expect(logger.info).toHaveBeenCalledWith('MESSAGE', { ecs: { version: '1.6.0' }, event: { action: 'ACTION' }, - kibana: { space_id: 'default' }, + kibana: { space_id: 'default', session_id: 'SESSION_ID' }, message: 'MESSAGE', trace: { id: 'REQUEST_ID' }, user: { name: 'jdoe', roles: ['admin'] }, @@ -123,12 +128,13 @@ describe('#asScoped', () => { http, getCurrentUser, getSpaceId, + getSID, }); const request = httpServerMock.createKibanaRequest({ kibanaRequestState: { requestId: 'REQUEST_ID', requestUuid: 'REQUEST_UUID' }, }); - audit.asScoped(request).log({ message: 'MESSAGE', event: { action: 'ACTION' } }); + await audit.asScoped(request).log({ message: 'MESSAGE', event: { action: 'ACTION' } }); expect(logger.info).not.toHaveBeenCalled(); }); @@ -143,12 +149,13 @@ describe('#asScoped', () => { http, getCurrentUser, getSpaceId, + getSID, }); const request = httpServerMock.createKibanaRequest({ kibanaRequestState: { requestId: 'REQUEST_ID', requestUuid: 'REQUEST_UUID' }, }); - audit.asScoped(request).log(undefined); + await audit.asScoped(request).log(undefined); expect(logger.info).not.toHaveBeenCalled(); }); }); @@ -368,6 +375,7 @@ describe('#getLogger', () => { http, getCurrentUser, getSpaceId, + getSID, }); const auditLogger = auditService.getLogger(pluginId); @@ -398,6 +406,7 @@ describe('#getLogger', () => { http, getCurrentUser, getSpaceId, + getSID, }); const auditLogger = auditService.getLogger(pluginId); @@ -436,6 +445,7 @@ describe('#getLogger', () => { http, getCurrentUser, getSpaceId, + getSID, }); const auditLogger = auditService.getLogger(pluginId); @@ -464,6 +474,7 @@ describe('#getLogger', () => { http, getCurrentUser, getSpaceId, + getSID, }); const auditLogger = auditService.getLogger(pluginId); @@ -493,6 +504,7 @@ describe('#getLogger', () => { http, getCurrentUser, getSpaceId, + getSID, }); const auditLogger = auditService.getLogger(pluginId); diff --git a/x-pack/plugins/security/server/audit/audit_service.ts b/x-pack/plugins/security/server/audit/audit_service.ts index 8dbdc48c7dee9..4ad1f873581c9 100644 --- a/x-pack/plugins/security/server/audit/audit_service.ts +++ b/x-pack/plugins/security/server/audit/audit_service.ts @@ -36,9 +36,6 @@ interface AuditLogMeta extends AuditEvent { ecs: { version: string; }; - session?: { - id: string; - }; trace: { id: string; }; @@ -57,6 +54,7 @@ interface AuditServiceSetupParams { getCurrentUser( request: KibanaRequest ): ReturnType | undefined; + getSID(request: KibanaRequest): Promise; getSpaceId( request: KibanaRequest ): ReturnType | undefined; @@ -84,6 +82,7 @@ export class AuditService { logging, http, getCurrentUser, + getSID, getSpaceId, }: AuditServiceSetupParams): AuditServiceSetup { if (config.enabled && !config.appender) { @@ -134,12 +133,13 @@ export class AuditService { * }); * ``` */ - const log: AuditLogger['log'] = (event) => { + const log: AuditLogger['log'] = async (event) => { if (!event) { return; } - const user = getCurrentUser(request); const spaceId = getSpaceId(request); + const user = getCurrentUser(request); + const sessionId = await getSID(request); const meta: AuditLogMeta = { ecs: { version: ECS_VERSION }, ...event, @@ -151,11 +151,10 @@ export class AuditService { event.user, kibana: { space_id: spaceId, + session_id: sessionId, ...event.kibana, }, - trace: { - id: request.id, - }, + trace: { id: request.id }, }; if (filterEvent(meta, config.ignore_filters)) { this.ecsLogger.info(event.message!, meta); diff --git a/x-pack/plugins/security/server/plugin.ts b/x-pack/plugins/security/server/plugin.ts index 4016b78b6d998..070e187e869b1 100644 --- a/x-pack/plugins/security/server/plugin.ts +++ b/x-pack/plugins/security/server/plugin.ts @@ -188,24 +188,25 @@ export class Plugin { registerSecurityUsageCollector({ usageCollection, config, license }); + const { session } = this.sessionManagementService.setup({ + config, + clusterClient, + http: core.http, + kibanaIndexName: legacyConfig.kibana.index, + taskManager, + }); + const audit = this.auditService.setup({ license, config: config.audit, logging: core.logging, http: core.http, getSpaceId: (request) => spaces?.spacesService.getSpaceId(request), + getSID: (request) => session.getSID(request), getCurrentUser: (request) => authenticationSetup.getCurrentUser(request), }); const legacyAuditLogger = new SecurityAuditLogger(audit.getLogger()); - const { session } = this.sessionManagementService.setup({ - config, - clusterClient, - http: core.http, - kibanaIndexName: legacyConfig.kibana.index, - taskManager, - }); - const authenticationSetup = this.authenticationService.setup({ legacyAuditLogger, audit, diff --git a/x-pack/plugins/security/server/session_management/session.mock.ts b/x-pack/plugins/security/server/session_management/session.mock.ts index 973341acbfce3..b740249180407 100644 --- a/x-pack/plugins/security/server/session_management/session.mock.ts +++ b/x-pack/plugins/security/server/session_management/session.mock.ts @@ -10,6 +10,7 @@ import { sessionIndexMock } from './session_index.mock'; export const sessionMock = { create: (): jest.Mocked> => ({ + getSID: jest.fn(), get: jest.fn(), create: jest.fn(), update: jest.fn(), diff --git a/x-pack/plugins/security/server/session_management/session.test.ts b/x-pack/plugins/security/server/session_management/session.test.ts index 3010e70c31421..47e391ed57925 100644 --- a/x-pack/plugins/security/server/session_management/session.test.ts +++ b/x-pack/plugins/security/server/session_management/session.test.ts @@ -56,6 +56,20 @@ describe('Session', () => { }); }); + describe('#getSID', () => { + const mockRequest = httpServerMock.createKibanaRequest(); + + it('returns `undefined` if session cookie does not exist', async () => { + mockSessionCookie.get.mockResolvedValue(null); + await expect(session.getSID(mockRequest)).resolves.toBeUndefined(); + }); + + it('returns session id', async () => { + mockSessionCookie.get.mockResolvedValue(sessionCookieMock.createValue()); + await expect(session.getSID(mockRequest)).resolves.toEqual('some-long-sid'); + }); + }); + describe('#get', () => { const mockAAD = Buffer.from([2, ...Array(255).keys()]).toString('base64'); diff --git a/x-pack/plugins/security/server/session_management/session.ts b/x-pack/plugins/security/server/session_management/session.ts index 4dc83a1abe4af..3c97c13c2d41d 100644 --- a/x-pack/plugins/security/server/session_management/session.ts +++ b/x-pack/plugins/security/server/session_management/session.ts @@ -99,6 +99,17 @@ export class Session { this.crypto = nodeCrypto({ encryptionKey: this.options.config.encryptionKey }); } + /** + * Extracts session id for the specified request. + * @param request Request instance to get session value for. + */ + async getSID(request: KibanaRequest) { + const sessionCookieValue = await this.options.sessionCookie.get(request); + if (sessionCookieValue) { + return sessionCookieValue.sid; + } + } + /** * Extracts session value for the specified request. Under the hood it can clear session if it is * invalid or created by the legacy versions of Kibana. From b01a3270769dd33309b25a91ae28e0db74f6c3ed Mon Sep 17 00:00:00 2001 From: Vadim Dalecky Date: Mon, 14 Dec 2020 13:28:23 +0100 Subject: [PATCH 09/95] Row trigger 2 (#83167) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 🎸 add ROW_CLICK_TRIGGER * feat: 🎸 wire row click event to UI Actions trigger in Lens * feat: 🎸 add row click trigger to url drilldown * feat: 🎸 add datatable to row click context * feat: 🎸 pass in row index in row click trigger context * feat: 🎸 add columns to row click trigger context * feat: 🎸 fill values and keys event scope array * feat: 🎸 generate correct row scope variables * fix: 🐛 report triggers from lens embeddable * feat: 🎸 add sample preview for row click trigger * feat: 🎸 remove url drilldown preview box * chore: 🤖 remove mock variable generation functions * feat: 🎸 generate context and global variable lists * feat: 🎸 preview event variable list * feat: 🎸 show empty url error on blur * feat: 🎸 add ability to always show popup for executed actions * refactor: 💡 rename multiple action execution method * fix: 🐛 don't add separator befor group on no main items * feat: 🎸 wire in uiActions service into datatable renderer * feat: 🎸 check each row if it has compatible row click actions * feat: 🎸 allow passing data to expression renderer * feat: 🎸 add isEmbeddable helper * feat: 🎸 pass embeddable to lens table renderer * feat: 🎸 hide lens table row actions which are empty * feat: 🎸 re-render lens embeddable when dynamic actions chagne * feat: 🎸 hide actions column if there are no row actions * feat: 🎸 re-render lens embeddable on view mode chagne * fix: 🐛 fix TypeScript errors * chore: 🤖 fix TypeScript errors * docs: ✏️ update auto-generated docs * feat: 🎸 add hasCompatibleActions to expression layer * feat: 🎸 remove "data" from expression renderer handlers * fix: 🐛 fix TypeScript errors * test: 💍 fix Jest tests * docs: ✏️ update autogenerated docs * fix: 🐛 wrap event payload into data * test: 💍 add "alwaysShowPopup" test * chore: 🤖 add comment requested in review https://github.com/elastic/kibana/pull/83167#discussion_r537340216 * test: 💍 add hasCompatibleActions test * test: 💍 add datatable renderer test * test: 💍 add Lens embeddable input change tests * test: 💍 add embeddable row click test * fix: 🐛 add url validation * test: 💍 add url drilldown tests * docs: ✏️ remove url drilldown preview from docs * docs: ✏️ remove preview from url templating * docs: ✏️ add row click description * chore: 🤖 move 36.5 KB bundle balance to url_drilldown * test: 💍 simplify test case * style: 💄 change types places * refactor: 💡 clean up panel variable generation * test: 💍 add getPanelVariables() tests * fix: 🐛 generate runtime variables correctly * fix: 🐛 improve getVariableList() and add tests for it * feat: 🎸 add translation, improve types --- ...ns-embeddable-public.chartactioncontext.md | 2 +- ...-plugins-embeddable-public.isembeddable.md | 11 + ...eddable-public.isrowclicktriggercontext.md | 11 + ...kibana-plugin-plugins-embeddable-public.md | 2 + ...c.expressionrenderhandler._constructor_.md | 4 +- ...ressions-public.expressionrenderhandler.md | 4 +- ...s-public.expressionrenderhandler.render.md | 2 +- ...essionloaderparams.hascompatibleactions.md | 11 + ...ressions-public.iexpressionloaderparams.md | 1 + ...eterrenderhandlers.hascompatibleactions.md | 11 + ...sions-public.iinterpreterrenderhandlers.md | 1 + ...eterrenderhandlers.hascompatibleactions.md | 11 + ...sions-server.iinterpreterrenderhandlers.md | 1 + ...kibana-plugin-plugins-ui_actions-public.md | 3 + ...ins-ui_actions-public.row_click_trigger.md | 11 + ...-ui_actions-public.rowclickcontext.data.md | 15 + ...tions-public.rowclickcontext.embeddable.md | 11 + ...ugins-ui_actions-public.rowclickcontext.md | 19 ++ ...ugins-ui_actions-public.rowclicktrigger.md | 11 + ...ui_actions-public.triggercontextmapping.md | 1 + ...triggercontextmapping.row_click_trigger.md | 11 + ...ublic.uiactionsservice.addtriggeraction.md | 2 +- ...ns-public.uiactionsservice.attachaction.md | 2 +- ....uiactionsservice.executetriggeractions.md | 2 +- ...ions-public.uiactionsservice.gettrigger.md | 2 +- ...blic.uiactionsservice.gettriggeractions.md | 2 +- ...ionsservice.gettriggercompatibleactions.md | 2 +- ...gins-ui_actions-public.uiactionsservice.md | 12 +- docs/user/dashboard/drilldowns.asciidoc | 2 +- .../images/url_drilldown_url_template.png | Bin 21025 -> 17667 bytes docs/user/dashboard/url-drilldown.asciidoc | 28 +- packages/kbn-optimizer/limits.yml | 4 +- src/plugins/embeddable/public/index.ts | 2 + .../public/lib/embeddables/i_embeddable.ts | 2 +- .../public/lib/embeddables/index.ts | 1 + .../public/lib/embeddables/is_embeddable.ts | 29 ++ .../public/lib/triggers/triggers.ts | 10 +- src/plugins/embeddable/public/public.api.md | 13 +- .../common/expression_renderers/types.ts | 1 + src/plugins/expressions/public/loader.ts | 1 + src/plugins/expressions/public/public.api.md | 8 +- src/plugins/expressions/public/render.test.ts | 25 ++ src/plugins/expressions/public/render.ts | 32 +- src/plugins/expressions/public/types/index.ts | 2 + src/plugins/expressions/server/server.api.md | 2 + src/plugins/ui_actions/public/index.ts | 3 + src/plugins/ui_actions/public/plugin.ts | 2 + src/plugins/ui_actions/public/public.api.md | 44 ++- .../service/ui_actions_execution_service.ts | 47 ++- .../tests/execute_trigger_actions.test.ts | 27 +- .../ui_actions/public/triggers/index.ts | 1 + .../public/triggers/row_click_trigger.ts | 53 ++++ .../public/triggers/trigger_contract.ts | 4 +- .../public/triggers/trigger_internal.ts | 15 +- src/plugins/ui_actions/public/types.ts | 3 + .../public/embeddable/events.ts | 5 +- .../url_drilldown/public/lib/test/data.ts | 173 +++++++++++ .../public/lib/url_drilldown.test.ts | 203 +++++++++++- .../public/lib/url_drilldown.tsx | 67 ++-- .../public/lib/url_drilldown_scope.test.ts | 294 +++++++++++++----- .../public/lib/url_drilldown_scope.ts | 201 +++++++----- .../__snapshots__/expression.test.tsx.snap | 56 ++++ .../expression.test.tsx | 18 ++ .../datatable_visualization/expression.tsx | 257 ++++++++++----- .../public/datatable_visualization/index.ts | 3 +- .../embeddable/embeddable.test.tsx | 101 +++++- .../embeddable/embeddable.tsx | 70 ++++- .../embeddable/embeddable_factory.ts | 1 + .../embeddable/expression_wrapper.tsx | 4 + x-pack/plugins/lens/public/types.ts | 15 +- .../test_samples/demo.tsx | 32 +- .../url_drilldown_collect_config.test.tsx | 47 --- .../url_drilldown_collect_config.tsx | 87 ++---- .../public/drilldowns/url_drilldown/index.ts | 4 - .../url_drilldown/url_drilldown_scope.test.ts | 52 ---- .../url_drilldown/url_drilldown_scope.ts | 39 --- 76 files changed, 1684 insertions(+), 584 deletions(-) create mode 100644 docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.isembeddable.md create mode 100644 docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.isrowclicktriggercontext.md create mode 100644 docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.hascompatibleactions.md create mode 100644 docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.hascompatibleactions.md create mode 100644 docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.hascompatibleactions.md create mode 100644 docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.row_click_trigger.md create mode 100644 docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.rowclickcontext.data.md create mode 100644 docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.rowclickcontext.embeddable.md create mode 100644 docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.rowclickcontext.md create mode 100644 docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.rowclicktrigger.md create mode 100644 docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.triggercontextmapping.row_click_trigger.md create mode 100644 src/plugins/embeddable/public/lib/embeddables/is_embeddable.ts create mode 100644 src/plugins/ui_actions/public/triggers/row_click_trigger.ts create mode 100644 x-pack/plugins/drilldowns/url_drilldown/public/lib/test/data.ts delete mode 100644 x-pack/plugins/ui_actions_enhanced/public/drilldowns/url_drilldown/components/url_drilldown_collect_config/url_drilldown_collect_config.test.tsx delete mode 100644 x-pack/plugins/ui_actions_enhanced/public/drilldowns/url_drilldown/url_drilldown_scope.test.ts delete mode 100644 x-pack/plugins/ui_actions_enhanced/public/drilldowns/url_drilldown/url_drilldown_scope.ts diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.chartactioncontext.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.chartactioncontext.md index 1c9fc27d53f19..9447c8a4e50a7 100644 --- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.chartactioncontext.md +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.chartactioncontext.md @@ -7,5 +7,5 @@ Signature: ```typescript -export declare type ChartActionContext = ValueClickContext | RangeSelectContext; +export declare type ChartActionContext = ValueClickContext | RangeSelectContext | RowClickContext; ``` diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.isembeddable.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.isembeddable.md new file mode 100644 index 0000000000000..ea8d3870dc055 --- /dev/null +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.isembeddable.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-embeddable-public](./kibana-plugin-plugins-embeddable-public.md) > [isEmbeddable](./kibana-plugin-plugins-embeddable-public.isembeddable.md) + +## isEmbeddable variable + +Signature: + +```typescript +isEmbeddable: (x: unknown) => x is IEmbeddable +``` diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.isrowclicktriggercontext.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.isrowclicktriggercontext.md new file mode 100644 index 0000000000000..91e0f988db69c --- /dev/null +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.isrowclicktriggercontext.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-embeddable-public](./kibana-plugin-plugins-embeddable-public.md) > [isRowClickTriggerContext](./kibana-plugin-plugins-embeddable-public.isrowclicktriggercontext.md) + +## isRowClickTriggerContext variable + +Signature: + +```typescript +isRowClickTriggerContext: (context: ChartActionContext) => context is RowClickContext +``` diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.md index 06f792837e4fe..f1ea605703e59 100644 --- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.md +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.md @@ -78,7 +78,9 @@ | [defaultEmbeddableFactoryProvider](./kibana-plugin-plugins-embeddable-public.defaultembeddablefactoryprovider.md) | | | [EmbeddableRenderer](./kibana-plugin-plugins-embeddable-public.embeddablerenderer.md) | Helper react component to render an embeddable Can be used if you have an embeddable object or an embeddable factory Supports updating input by passing input prop | | [isContextMenuTriggerContext](./kibana-plugin-plugins-embeddable-public.iscontextmenutriggercontext.md) | | +| [isEmbeddable](./kibana-plugin-plugins-embeddable-public.isembeddable.md) | | | [isRangeSelectTriggerContext](./kibana-plugin-plugins-embeddable-public.israngeselecttriggercontext.md) | | +| [isRowClickTriggerContext](./kibana-plugin-plugins-embeddable-public.isrowclicktriggercontext.md) | | | [isValueClickTriggerContext](./kibana-plugin-plugins-embeddable-public.isvalueclicktriggercontext.md) | | | [PANEL\_BADGE\_TRIGGER](./kibana-plugin-plugins-embeddable-public.panel_badge_trigger.md) | | | [PANEL\_NOTIFICATION\_TRIGGER](./kibana-plugin-plugins-embeddable-public.panel_notification_trigger.md) | | diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionrenderhandler._constructor_.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionrenderhandler._constructor_.md index fcccd3f6b9618..1565202e84674 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionrenderhandler._constructor_.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionrenderhandler._constructor_.md @@ -9,7 +9,7 @@ Constructs a new instance of the `ExpressionRenderHandler` class Signature: ```typescript -constructor(element: HTMLElement, { onRenderError, renderMode }?: Partial); +constructor(element: HTMLElement, { onRenderError, renderMode, hasCompatibleActions, }?: ExpressionRenderHandlerParams); ``` ## Parameters @@ -17,5 +17,5 @@ constructor(element: HTMLElement, { onRenderError, renderMode }?: PartialHTMLElement | | -| { onRenderError, renderMode } | Partial<ExpressionRenderHandlerParams> | | +| { onRenderError, renderMode, hasCompatibleActions, } | ExpressionRenderHandlerParams | | diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionrenderhandler.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionrenderhandler.md index 12c663273bd8c..d65c06bdaed83 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionrenderhandler.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionrenderhandler.md @@ -14,7 +14,7 @@ export declare class ExpressionRenderHandler | Constructor | Modifiers | Description | | --- | --- | --- | -| [(constructor)(element, { onRenderError, renderMode })](./kibana-plugin-plugins-expressions-public.expressionrenderhandler._constructor_.md) | | Constructs a new instance of the ExpressionRenderHandler class | +| [(constructor)(element, { onRenderError, renderMode, hasCompatibleActions, })](./kibana-plugin-plugins-expressions-public.expressionrenderhandler._constructor_.md) | | Constructs a new instance of the ExpressionRenderHandler class | ## Properties @@ -24,7 +24,7 @@ export declare class ExpressionRenderHandler | [events$](./kibana-plugin-plugins-expressions-public.expressionrenderhandler.events_.md) | | Observable<ExpressionRendererEvent> | | | [getElement](./kibana-plugin-plugins-expressions-public.expressionrenderhandler.getelement.md) | | () => HTMLElement | | | [handleRenderError](./kibana-plugin-plugins-expressions-public.expressionrenderhandler.handlerendererror.md) | | (error: ExpressionRenderError) => void | | -| [render](./kibana-plugin-plugins-expressions-public.expressionrenderhandler.render.md) | | (data: any, uiState?: any) => Promise<void> | | +| [render](./kibana-plugin-plugins-expressions-public.expressionrenderhandler.render.md) | | (value: any, uiState?: any) => Promise<void> | | | [render$](./kibana-plugin-plugins-expressions-public.expressionrenderhandler.render_.md) | | Observable<number> | | | [update$](./kibana-plugin-plugins-expressions-public.expressionrenderhandler.update_.md) | | Observable<UpdateValue | null> | | diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionrenderhandler.render.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionrenderhandler.render.md index dec17d60ffd14..87f378fd58344 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionrenderhandler.render.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionrenderhandler.render.md @@ -7,5 +7,5 @@ Signature: ```typescript -render: (data: any, uiState?: any) => Promise; +render: (value: any, uiState?: any) => Promise; ``` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.hascompatibleactions.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.hascompatibleactions.md new file mode 100644 index 0000000000000..4d2b76cb323fb --- /dev/null +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.hascompatibleactions.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-expressions-public](./kibana-plugin-plugins-expressions-public.md) > [IExpressionLoaderParams](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.md) > [hasCompatibleActions](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.hascompatibleactions.md) + +## IExpressionLoaderParams.hasCompatibleActions property + +Signature: + +```typescript +hasCompatibleActions?: ExpressionRenderHandlerParams['hasCompatibleActions']; +``` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.md index 54eecad0deb50..22a73fff039e6 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.md @@ -19,6 +19,7 @@ export interface IExpressionLoaderParams | [customRenderers](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.customrenderers.md) | [] | | | [debug](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.debug.md) | boolean | | | [disableCaching](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.disablecaching.md) | boolean | | +| [hasCompatibleActions](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.hascompatibleactions.md) | ExpressionRenderHandlerParams['hasCompatibleActions'] | | | [inspectorAdapters](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.inspectoradapters.md) | Adapters | | | [onRenderError](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.onrendererror.md) | RenderErrorHandlerFnType | | | [renderMode](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.rendermode.md) | RenderMode | | diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.hascompatibleactions.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.hascompatibleactions.md new file mode 100644 index 0000000000000..d178af55ae2d9 --- /dev/null +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.hascompatibleactions.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-expressions-public](./kibana-plugin-plugins-expressions-public.md) > [IInterpreterRenderHandlers](./kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.md) > [hasCompatibleActions](./kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.hascompatibleactions.md) + +## IInterpreterRenderHandlers.hasCompatibleActions property + +Signature: + +```typescript +hasCompatibleActions?: (event: any) => Promise; +``` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.md index a65e025451636..931e474a41006 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.md @@ -17,6 +17,7 @@ export interface IInterpreterRenderHandlers | [done](./kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.done.md) | () => void | Done increments the number of rendering successes | | [event](./kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.event.md) | (event: any) => void | | | [getRenderMode](./kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.getrendermode.md) | () => RenderMode | | +| [hasCompatibleActions](./kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.hascompatibleactions.md) | (event: any) => Promise<boolean> | | | [onDestroy](./kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.ondestroy.md) | (fn: () => void) => void | | | [reload](./kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.reload.md) | () => void | | | [uiState](./kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.uistate.md) | PersistedState | | diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.hascompatibleactions.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.hascompatibleactions.md new file mode 100644 index 0000000000000..55419279f5d21 --- /dev/null +++ b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.hascompatibleactions.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-expressions-server](./kibana-plugin-plugins-expressions-server.md) > [IInterpreterRenderHandlers](./kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.md) > [hasCompatibleActions](./kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.hascompatibleactions.md) + +## IInterpreterRenderHandlers.hasCompatibleActions property + +Signature: + +```typescript +hasCompatibleActions?: (event: any) => Promise; +``` diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.md index b1496386944fa..273703cacca06 100644 --- a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.md +++ b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.md @@ -17,6 +17,7 @@ export interface IInterpreterRenderHandlers | [done](./kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.done.md) | () => void | Done increments the number of rendering successes | | [event](./kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.event.md) | (event: any) => void | | | [getRenderMode](./kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.getrendermode.md) | () => RenderMode | | +| [hasCompatibleActions](./kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.hascompatibleactions.md) | (event: any) => Promise<boolean> | | | [onDestroy](./kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.ondestroy.md) | (fn: () => void) => void | | | [reload](./kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.reload.md) | () => void | | | [uiState](./kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.uistate.md) | PersistedState | | diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.md index 5e10de4e0f2a5..fd1ea7df4fb74 100644 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.md +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.md @@ -26,6 +26,7 @@ | [Action](./kibana-plugin-plugins-ui_actions-public.action.md) | | | [ActionContextMapping](./kibana-plugin-plugins-ui_actions-public.actioncontextmapping.md) | | | [ActionExecutionMeta](./kibana-plugin-plugins-ui_actions-public.actionexecutionmeta.md) | During action execution we can provide additional information, for example, trigger, that caused the action execution | +| [RowClickContext](./kibana-plugin-plugins-ui_actions-public.rowclickcontext.md) | | | [Trigger](./kibana-plugin-plugins-ui_actions-public.trigger.md) | This is a convenience interface used to register a \*trigger\*.Trigger specifies a named anchor to which Action can be attached. When Trigger is being \*called\* it creates a Context object and passes it to the execute method of an Action.More than one action can be attached to a single trigger, in which case when trigger is \*called\* it first displays a context menu for user to pick a single action to execute. | | [TriggerContextMapping](./kibana-plugin-plugins-ui_actions-public.triggercontextmapping.md) | | | [UiActionsActionDefinition](./kibana-plugin-plugins-ui_actions-public.uiactionsactiondefinition.md) | A convenience interface used to register an action. | @@ -42,6 +43,8 @@ | [ACTION\_VISUALIZE\_LENS\_FIELD](./kibana-plugin-plugins-ui_actions-public.action_visualize_lens_field.md) | | | [APPLY\_FILTER\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.apply_filter_trigger.md) | | | [applyFilterTrigger](./kibana-plugin-plugins-ui_actions-public.applyfiltertrigger.md) | | +| [ROW\_CLICK\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.row_click_trigger.md) | | +| [rowClickTrigger](./kibana-plugin-plugins-ui_actions-public.rowclicktrigger.md) | | | [SELECT\_RANGE\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.select_range_trigger.md) | | | [selectRangeTrigger](./kibana-plugin-plugins-ui_actions-public.selectrangetrigger.md) | | | [VALUE\_CLICK\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.value_click_trigger.md) | | diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.row_click_trigger.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.row_click_trigger.md new file mode 100644 index 0000000000000..3541b53ab1d61 --- /dev/null +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.row_click_trigger.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-ui\_actions-public](./kibana-plugin-plugins-ui_actions-public.md) > [ROW\_CLICK\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.row_click_trigger.md) + +## ROW\_CLICK\_TRIGGER variable + +Signature: + +```typescript +ROW_CLICK_TRIGGER = "ROW_CLICK_TRIGGER" +``` diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.rowclickcontext.data.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.rowclickcontext.data.md new file mode 100644 index 0000000000000..1068cc9146893 --- /dev/null +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.rowclickcontext.data.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-ui\_actions-public](./kibana-plugin-plugins-ui_actions-public.md) > [RowClickContext](./kibana-plugin-plugins-ui_actions-public.rowclickcontext.md) > [data](./kibana-plugin-plugins-ui_actions-public.rowclickcontext.data.md) + +## RowClickContext.data property + +Signature: + +```typescript +data: { + rowIndex: number; + table: Datatable; + columns?: string[]; + }; +``` diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.rowclickcontext.embeddable.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.rowclickcontext.embeddable.md new file mode 100644 index 0000000000000..e8baf44ff9cbc --- /dev/null +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.rowclickcontext.embeddable.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-ui\_actions-public](./kibana-plugin-plugins-ui_actions-public.md) > [RowClickContext](./kibana-plugin-plugins-ui_actions-public.rowclickcontext.md) > [embeddable](./kibana-plugin-plugins-ui_actions-public.rowclickcontext.embeddable.md) + +## RowClickContext.embeddable property + +Signature: + +```typescript +embeddable?: IEmbeddable; +``` diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.rowclickcontext.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.rowclickcontext.md new file mode 100644 index 0000000000000..74b55d85f10e3 --- /dev/null +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.rowclickcontext.md @@ -0,0 +1,19 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-ui\_actions-public](./kibana-plugin-plugins-ui_actions-public.md) > [RowClickContext](./kibana-plugin-plugins-ui_actions-public.rowclickcontext.md) + +## RowClickContext interface + +Signature: + +```typescript +export interface RowClickContext +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [data](./kibana-plugin-plugins-ui_actions-public.rowclickcontext.data.md) | {
rowIndex: number;
table: Datatable;
columns?: string[];
} | | +| [embeddable](./kibana-plugin-plugins-ui_actions-public.rowclickcontext.embeddable.md) | IEmbeddable | | + diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.rowclicktrigger.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.rowclicktrigger.md new file mode 100644 index 0000000000000..aa1097d8c0864 --- /dev/null +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.rowclicktrigger.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-ui\_actions-public](./kibana-plugin-plugins-ui_actions-public.md) > [rowClickTrigger](./kibana-plugin-plugins-ui_actions-public.rowclicktrigger.md) + +## rowClickTrigger variable + +Signature: + +```typescript +rowClickTrigger: Trigger<'ROW_CLICK_TRIGGER'> +``` diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.triggercontextmapping.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.triggercontextmapping.md index 9db44d4dc7b05..2f0d22cf6dd74 100644 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.triggercontextmapping.md +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.triggercontextmapping.md @@ -16,6 +16,7 @@ export interface TriggerContextMapping | --- | --- | --- | | [""](./kibana-plugin-plugins-ui_actions-public.triggercontextmapping.__.md) | TriggerContext | | | [FILTER\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.triggercontextmapping.filter_trigger.md) | ApplyGlobalFilterActionContext | | +| [ROW\_CLICK\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.triggercontextmapping.row_click_trigger.md) | RowClickContext | | | [SELECT\_RANGE\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.triggercontextmapping.select_range_trigger.md) | RangeSelectContext | | | [VALUE\_CLICK\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.triggercontextmapping.value_click_trigger.md) | ValueClickContext | | | [VISUALIZE\_FIELD\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.triggercontextmapping.visualize_field_trigger.md) | VisualizeFieldContext | | diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.triggercontextmapping.row_click_trigger.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.triggercontextmapping.row_click_trigger.md new file mode 100644 index 0000000000000..cf253df337378 --- /dev/null +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.triggercontextmapping.row_click_trigger.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-ui\_actions-public](./kibana-plugin-plugins-ui_actions-public.md) > [TriggerContextMapping](./kibana-plugin-plugins-ui_actions-public.triggercontextmapping.md) > [ROW\_CLICK\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.triggercontextmapping.row_click_trigger.md) + +## TriggerContextMapping.ROW\_CLICK\_TRIGGER property + +Signature: + +```typescript +[ROW_CLICK_TRIGGER]: RowClickContext; +``` diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.addtriggeraction.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.addtriggeraction.md index fd6ade88479af..ca999322b7a56 100644 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.addtriggeraction.md +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.addtriggeraction.md @@ -11,5 +11,5 @@ Signature: ```typescript -readonly addTriggerAction: (triggerId: T, action: ActionDefinition | Action) => void; +readonly addTriggerAction: (triggerId: T, action: ActionDefinition | Action) => void; ``` diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.attachaction.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.attachaction.md index 19f215a96b23b..e95e7e1eb38b6 100644 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.attachaction.md +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.attachaction.md @@ -7,5 +7,5 @@ Signature: ```typescript -readonly attachAction: (triggerId: T, actionId: string) => void; +readonly attachAction: (triggerId: T, actionId: string) => void; ``` diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.executetriggeractions.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.executetriggeractions.md index 1bb6ca1115248..8e7fb8b8bbf29 100644 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.executetriggeractions.md +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.executetriggeractions.md @@ -12,5 +12,5 @@ Signature: ```typescript -readonly executeTriggerActions: (triggerId: T, context: TriggerContext) => Promise; +readonly executeTriggerActions: (triggerId: T, context: TriggerContext) => Promise; ``` diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettrigger.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettrigger.md index d44dc4e43a52e..b996620686a28 100644 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettrigger.md +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettrigger.md @@ -7,5 +7,5 @@ Signature: ```typescript -readonly getTrigger: (triggerId: T) => TriggerContract; +readonly getTrigger: (triggerId: T) => TriggerContract; ``` diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggeractions.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggeractions.md index 0a9b674a45de2..f94b34ecc2d90 100644 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggeractions.md +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggeractions.md @@ -7,5 +7,5 @@ Signature: ```typescript -readonly getTriggerActions: (triggerId: T) => Action[]; +readonly getTriggerActions: (triggerId: T) => Action[]; ``` diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggercompatibleactions.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggercompatibleactions.md index faed81236342d..dff958608ef9e 100644 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggercompatibleactions.md +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggercompatibleactions.md @@ -7,5 +7,5 @@ Signature: ```typescript -readonly getTriggerCompatibleActions: (triggerId: T, context: TriggerContextMapping[T]) => Promise[]>; +readonly getTriggerCompatibleActions: (triggerId: T, context: TriggerContextMapping[T]) => Promise[]>; ``` diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.md index e3c5dbb92ae90..e35eb503ab62b 100644 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.md +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.md @@ -21,17 +21,17 @@ export declare class UiActionsService | Property | Modifiers | Type | Description | | --- | --- | --- | --- | | [actions](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.actions.md) | | ActionRegistry | | -| [addTriggerAction](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.addtriggeraction.md) | | <T extends "" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER" | "FILTER_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER">(triggerId: T, action: ActionDefinition<TriggerContextMapping[T]> | Action<TriggerContextMapping[T], "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_EXPORT_CSV">) => void | addTriggerAction is similar to attachAction as it attaches action to a trigger, but it also registers the action, if it has not been registered, yet.addTriggerAction also infers better typing of the action argument. | -| [attachAction](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.attachaction.md) | | <T extends "" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER" | "FILTER_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER">(triggerId: T, actionId: string) => void | | +| [addTriggerAction](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.addtriggeraction.md) | | <T extends "" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER" | "ROW_CLICK_TRIGGER" | "FILTER_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER">(triggerId: T, action: ActionDefinition<TriggerContextMapping[T]> | Action<TriggerContextMapping[T], "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_EXPORT_CSV">) => void | addTriggerAction is similar to attachAction as it attaches action to a trigger, but it also registers the action, if it has not been registered, yet.addTriggerAction also infers better typing of the action argument. | +| [attachAction](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.attachaction.md) | | <T extends "" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER" | "ROW_CLICK_TRIGGER" | "FILTER_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER">(triggerId: T, actionId: string) => void | | | [clear](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.clear.md) | | () => void | Removes all registered triggers and actions. | | [detachAction](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.detachaction.md) | | (triggerId: TriggerId, actionId: string) => void | | -| [executeTriggerActions](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.executetriggeractions.md) | | <T extends "" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER" | "FILTER_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER">(triggerId: T, context: TriggerContext<T>) => Promise<void> | | +| [executeTriggerActions](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.executetriggeractions.md) | | <T extends "" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER" | "ROW_CLICK_TRIGGER" | "FILTER_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER">(triggerId: T, context: TriggerContext<T>) => Promise<void> | | | [executionService](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.executionservice.md) | | UiActionsExecutionService | | | [fork](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.fork.md) | | () => UiActionsService | "Fork" a separate instance of UiActionsService that inherits all existing triggers and actions, but going forward all new triggers and actions added to this instance of UiActionsService are only available within this instance. | | [getAction](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.getaction.md) | | <T extends ActionDefinition<{}>>(id: string) => Action<ActionContext<T>, "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_EXPORT_CSV"> | | -| [getTrigger](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettrigger.md) | | <T extends "" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER" | "FILTER_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER">(triggerId: T) => TriggerContract<T> | | -| [getTriggerActions](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggeractions.md) | | <T extends "" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER" | "FILTER_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER">(triggerId: T) => Action<TriggerContextMapping[T], "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_EXPORT_CSV">[] | | -| [getTriggerCompatibleActions](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggercompatibleactions.md) | | <T extends "" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER" | "FILTER_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER">(triggerId: T, context: TriggerContextMapping[T]) => Promise<Action<TriggerContextMapping[T], "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_EXPORT_CSV">[]> | | +| [getTrigger](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettrigger.md) | | <T extends "" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER" | "ROW_CLICK_TRIGGER" | "FILTER_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER">(triggerId: T) => TriggerContract<T> | | +| [getTriggerActions](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggeractions.md) | | <T extends "" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER" | "ROW_CLICK_TRIGGER" | "FILTER_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER">(triggerId: T) => Action<TriggerContextMapping[T], "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_EXPORT_CSV">[] | | +| [getTriggerCompatibleActions](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggercompatibleactions.md) | | <T extends "" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER" | "ROW_CLICK_TRIGGER" | "FILTER_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER">(triggerId: T, context: TriggerContextMapping[T]) => Promise<Action<TriggerContextMapping[T], "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_EXPORT_CSV">[]> | | | [hasAction](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.hasaction.md) | | (actionId: string) => boolean | | | [registerAction](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.registeraction.md) | | <A extends ActionDefinition<{}>>(definition: A) => Action<ActionContext<A>, "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_EXPORT_CSV"> | | | [registerTrigger](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.registertrigger.md) | | (trigger: Trigger) => void | | diff --git a/docs/user/dashboard/drilldowns.asciidoc b/docs/user/dashboard/drilldowns.asciidoc index ff2c321f667c8..3db5bd6d97ff0 100644 --- a/docs/user/dashboard/drilldowns.asciidoc +++ b/docs/user/dashboard/drilldowns.asciidoc @@ -233,7 +233,7 @@ image:images/url_drilldown_go_to_github.gif[Drilldown on pie chart that navigate https://github.com/elastic/kibana/issues?q=is:issue+is:open+{{event.value}} ---- + -The example URL navigates to {kib} issues on Github. `{{event.value}}` is substituted with a value associated with a selected pie slice. In *URL preview*, `{{event.value}}` is substituted with a <> value. +The example URL navigates to {kib} issues on Github. `{{event.value}}` is substituted with a value associated with a selected pie slice. + [role="screenshot"] image:images/url_drilldown_url_template.png[URL template input] diff --git a/docs/user/dashboard/images/url_drilldown_url_template.png b/docs/user/dashboard/images/url_drilldown_url_template.png index d8515afe66a80beb0d63c862b954249c89683d2e..746ce62733618c6e3fc75223a02fe9c364dc2264 100644 GIT binary patch literal 17667 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 { + if (!x) return false; + if (typeof x !== 'object') return false; + if (typeof (x as IEmbeddable).id !== 'string') return false; + if (typeof (x as IEmbeddable).getInput !== 'function') return false; + if (typeof (x as IEmbeddable).supportedTriggers !== 'function') return false; + return true; +}; diff --git a/src/plugins/embeddable/public/lib/triggers/triggers.ts b/src/plugins/embeddable/public/lib/triggers/triggers.ts index b2965b55dbdfa..c3b1496b8eca8 100644 --- a/src/plugins/embeddable/public/lib/triggers/triggers.ts +++ b/src/plugins/embeddable/public/lib/triggers/triggers.ts @@ -19,7 +19,7 @@ import { i18n } from '@kbn/i18n'; import { Datatable } from '../../../../expressions'; -import { Trigger } from '../../../../ui_actions/public'; +import { Trigger, RowClickContext } from '../../../../ui_actions/public'; import { IEmbeddable } from '..'; export interface EmbeddableContext { @@ -52,7 +52,8 @@ export interface RangeSelectContext { export type ChartActionContext = | ValueClickContext - | RangeSelectContext; + | RangeSelectContext + | RowClickContext; export const CONTEXT_MENU_TRIGGER = 'CONTEXT_MENU_TRIGGER'; export const contextMenuTrigger: Trigger<'CONTEXT_MENU_TRIGGER'> = { @@ -95,6 +96,11 @@ export const isRangeSelectTriggerContext = ( context: ChartActionContext ): context is RangeSelectContext => context.data && 'range' in context.data; +export const isRowClickTriggerContext = (context: ChartActionContext): context is RowClickContext => + !!context.data && + typeof context.data === 'object' && + typeof (context as RowClickContext).data.rowIndex === 'number'; + export const isContextMenuTriggerContext = (context: unknown): context is EmbeddableContext => !!context && typeof context === 'object' && diff --git a/src/plugins/embeddable/public/public.api.md b/src/plugins/embeddable/public/public.api.md index e42eaaf86bdf3..4b7d60b4dc9ec 100644 --- a/src/plugins/embeddable/public/public.api.md +++ b/src/plugins/embeddable/public/public.api.md @@ -176,10 +176,11 @@ export class AttributeService>; } +// Warning: (ae-forgotten-export) The symbol "RowClickContext" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "ChartActionContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export type ChartActionContext = ValueClickContext | RangeSelectContext; +export type ChartActionContext = ValueClickContext | RangeSelectContext | RowClickContext; // Warning: (ae-missing-release-tag) "Container" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -726,6 +727,11 @@ export interface IEmbeddable context is EmbeddableContext; +// Warning: (ae-missing-release-tag) "isEmbeddable" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const isEmbeddable: (x: unknown) => x is IEmbeddable; + // Warning: (ae-missing-release-tag) "isErrorEmbeddable" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -741,6 +747,11 @@ export const isRangeSelectTriggerContext: (context: ChartActionContext) => conte // @public (undocumented) export function isReferenceOrValueEmbeddable(incoming: unknown): incoming is ReferenceOrValueEmbeddable; +// Warning: (ae-missing-release-tag) "isRowClickTriggerContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const isRowClickTriggerContext: (context: ChartActionContext) => context is RowClickContext; + // Warning: (ae-missing-release-tag) "isSavedObjectEmbeddableInput" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) diff --git a/src/plugins/expressions/common/expression_renderers/types.ts b/src/plugins/expressions/common/expression_renderers/types.ts index dd3124c7d17ee..88aca4c07ee31 100644 --- a/src/plugins/expressions/common/expression_renderers/types.ts +++ b/src/plugins/expressions/common/expression_renderers/types.ts @@ -82,6 +82,7 @@ export interface IInterpreterRenderHandlers { reload: () => void; update: (params: any) => void; event: (event: any) => void; + hasCompatibleActions?: (event: any) => Promise; getRenderMode: () => RenderMode; uiState?: PersistedState; } diff --git a/src/plugins/expressions/public/loader.ts b/src/plugins/expressions/public/loader.ts index 983a344c0e1a1..e9e0fa18af6c3 100644 --- a/src/plugins/expressions/public/loader.ts +++ b/src/plugins/expressions/public/loader.ts @@ -64,6 +64,7 @@ export class ExpressionLoader { this.renderHandler = new ExpressionRenderHandler(element, { onRenderError: params && params.onRenderError, renderMode: params?.renderMode, + hasCompatibleActions: params?.hasCompatibleActions, }); this.render$ = this.renderHandler.render$; this.update$ = this.renderHandler.update$; diff --git a/src/plugins/expressions/public/public.api.md b/src/plugins/expressions/public/public.api.md index 97ff00db0966c..6eb0e71c58e3f 100644 --- a/src/plugins/expressions/public/public.api.md +++ b/src/plugins/expressions/public/public.api.md @@ -532,7 +532,7 @@ export interface ExpressionRenderError extends Error { // @public (undocumented) export class ExpressionRenderHandler { // Warning: (ae-forgotten-export) The symbol "ExpressionRenderHandlerParams" needs to be exported by the entry point index.d.ts - constructor(element: HTMLElement, { onRenderError, renderMode }?: Partial); + constructor(element: HTMLElement, { onRenderError, renderMode, hasCompatibleActions, }?: ExpressionRenderHandlerParams); // (undocumented) destroy: () => void; // (undocumented) @@ -544,7 +544,7 @@ export class ExpressionRenderHandler { // (undocumented) render$: Observable; // (undocumented) - render: (data: any, uiState?: any) => Promise; + render: (value: any, uiState?: any) => Promise; // Warning: (ae-forgotten-export) The symbol "UpdateValue" needs to be exported by the entry point index.d.ts // // (undocumented) @@ -888,6 +888,8 @@ export interface IExpressionLoaderParams { // (undocumented) disableCaching?: boolean; // (undocumented) + hasCompatibleActions?: ExpressionRenderHandlerParams['hasCompatibleActions']; + // (undocumented) inspectorAdapters?: Adapters; // Warning: (ae-forgotten-export) The symbol "RenderErrorHandlerFnType" needs to be exported by the entry point index.d.ts // @@ -917,6 +919,8 @@ export interface IInterpreterRenderHandlers { // (undocumented) getRenderMode: () => RenderMode; // (undocumented) + hasCompatibleActions?: (event: any) => Promise; + // (undocumented) onDestroy: (fn: () => void) => void; // (undocumented) reload: () => void; diff --git a/src/plugins/expressions/public/render.test.ts b/src/plugins/expressions/public/render.test.ts index c44683f6779c0..3fc0100db489d 100644 --- a/src/plugins/expressions/public/render.test.ts +++ b/src/plugins/expressions/public/render.test.ts @@ -126,6 +126,31 @@ describe('ExpressionRenderHandler', () => { expect(getHandledError()!.message).toEqual('renderer error'); }); + it('should pass through provided "hasCompatibleActions" to the expression renderer', async () => { + const hasCompatibleActions = jest.fn(); + (getRenderersRegistry as jest.Mock).mockReturnValueOnce({ get: () => true }); + (getRenderersRegistry as jest.Mock).mockReturnValueOnce({ + get: () => ({ + render: (domNode: HTMLElement, config: unknown, handlers: IInterpreterRenderHandlers) => { + handlers.hasCompatibleActions!({ + foo: 'bar', + }); + }, + }), + }); + + const expressionRenderHandler = new ExpressionRenderHandler(element, { + onRenderError: mockMockErrorRenderFunction, + hasCompatibleActions, + }); + expect(hasCompatibleActions).toHaveBeenCalledTimes(0); + await expressionRenderHandler.render({ type: 'render', as: 'something' }); + expect(hasCompatibleActions).toHaveBeenCalledTimes(1); + expect(hasCompatibleActions.mock.calls[0][0]).toEqual({ + foo: 'bar', + }); + }); + it('sends a next observable once rendering is complete', () => { const expressionRenderHandler = new ExpressionRenderHandler(element); expect.assertions(1); diff --git a/src/plugins/expressions/public/render.ts b/src/plugins/expressions/public/render.ts index 4390033b5be60..717776a2861b4 100644 --- a/src/plugins/expressions/public/render.ts +++ b/src/plugins/expressions/public/render.ts @@ -29,8 +29,9 @@ import { getRenderersRegistry } from './services'; export type IExpressionRendererExtraHandlers = Record; export interface ExpressionRenderHandlerParams { - onRenderError: RenderErrorHandlerFnType; - renderMode: RenderMode; + onRenderError?: RenderErrorHandlerFnType; + renderMode?: RenderMode; + hasCompatibleActions?: (event: ExpressionRendererEvent) => Promise; } export interface ExpressionRendererEvent { @@ -59,7 +60,11 @@ export class ExpressionRenderHandler { constructor( element: HTMLElement, - { onRenderError, renderMode }: Partial = {} + { + onRenderError, + renderMode, + hasCompatibleActions = async () => false, + }: ExpressionRenderHandlerParams = {} ) { this.element = element; @@ -96,17 +101,18 @@ export class ExpressionRenderHandler { getRenderMode: () => { return renderMode || 'display'; }, + hasCompatibleActions, }; } - render = async (data: any, uiState: any = {}) => { - if (!data || typeof data !== 'object') { + render = async (value: any, uiState: any = {}) => { + if (!value || typeof value !== 'object') { return this.handleRenderError(new Error('invalid data provided to the expression renderer')); } - if (data.type !== 'render' || !data.as) { - if (data.type === 'error') { - return this.handleRenderError(data.error); + if (value.type !== 'render' || !value.as) { + if (value.type === 'error') { + return this.handleRenderError(value.error); } else { return this.handleRenderError( new Error('invalid data provided to the expression renderer') @@ -114,15 +120,15 @@ export class ExpressionRenderHandler { } } - if (!getRenderersRegistry().get(data.as)) { - return this.handleRenderError(new Error(`invalid renderer id '${data.as}'`)); + if (!getRenderersRegistry().get(value.as)) { + return this.handleRenderError(new Error(`invalid renderer id '${value.as}'`)); } try { // Rendering is asynchronous, completed by handlers.done() await getRenderersRegistry() - .get(data.as)! - .render(this.element, data.value, { + .get(value.as)! + .render(this.element, value.value, { ...this.handlers, uiState, } as any); @@ -152,7 +158,7 @@ export class ExpressionRenderHandler { export function render( element: HTMLElement, data: any, - options?: Partial + options?: ExpressionRenderHandlerParams ): ExpressionRenderHandler { const handler = new ExpressionRenderHandler(element, options); handler.render(data); diff --git a/src/plugins/expressions/public/types/index.ts b/src/plugins/expressions/public/types/index.ts index 5bae985699476..f37107abbb716 100644 --- a/src/plugins/expressions/public/types/index.ts +++ b/src/plugins/expressions/public/types/index.ts @@ -25,6 +25,7 @@ import { SerializableState, RenderMode, } from '../../common'; +import { ExpressionRenderHandlerParams } from '../render'; /** * @deprecated @@ -56,6 +57,7 @@ export interface IExpressionLoaderParams { onRenderError?: RenderErrorHandlerFnType; searchSessionId?: string; renderMode?: RenderMode; + hasCompatibleActions?: ExpressionRenderHandlerParams['hasCompatibleActions']; } export interface ExpressionRenderError extends Error { diff --git a/src/plugins/expressions/server/server.api.md b/src/plugins/expressions/server/server.api.md index 761ddba8f9270..7c1ab11f75027 100644 --- a/src/plugins/expressions/server/server.api.md +++ b/src/plugins/expressions/server/server.api.md @@ -736,6 +736,8 @@ export interface IInterpreterRenderHandlers { // (undocumented) getRenderMode: () => RenderMode; // (undocumented) + hasCompatibleActions?: (event: any) => Promise; + // (undocumented) onDestroy: (fn: () => void) => void; // (undocumented) reload: () => void; diff --git a/src/plugins/ui_actions/public/index.ts b/src/plugins/ui_actions/public/index.ts index b9f4a4a0426bf..d223c0abcccb7 100644 --- a/src/plugins/ui_actions/public/index.ts +++ b/src/plugins/ui_actions/public/index.ts @@ -50,6 +50,9 @@ export { visualizeFieldTrigger, VISUALIZE_GEO_FIELD_TRIGGER, visualizeGeoFieldTrigger, + ROW_CLICK_TRIGGER, + rowClickTrigger, + RowClickContext, } from './triggers'; export { TriggerContextMapping, diff --git a/src/plugins/ui_actions/public/plugin.ts b/src/plugins/ui_actions/public/plugin.ts index 87a1df959ccbd..fdb75e9a426e9 100644 --- a/src/plugins/ui_actions/public/plugin.ts +++ b/src/plugins/ui_actions/public/plugin.ts @@ -23,6 +23,7 @@ import { UiActionsService } from './service'; import { selectRangeTrigger, valueClickTrigger, + rowClickTrigger, applyFilterTrigger, visualizeFieldTrigger, visualizeGeoFieldTrigger, @@ -48,6 +49,7 @@ export class UiActionsPlugin implements Plugin { public setup(core: CoreSetup): UiActionsSetup { this.service.registerTrigger(selectRangeTrigger); this.service.registerTrigger(valueClickTrigger); + this.service.registerTrigger(rowClickTrigger); this.service.registerTrigger(applyFilterTrigger); this.service.registerTrigger(visualizeFieldTrigger); this.service.registerTrigger(visualizeGeoFieldTrigger); diff --git a/src/plugins/ui_actions/public/public.api.md b/src/plugins/ui_actions/public/public.api.md index ca27e19b247c2..2384dfab13c8c 100644 --- a/src/plugins/ui_actions/public/public.api.md +++ b/src/plugins/ui_actions/public/public.api.md @@ -133,6 +133,32 @@ export class IncompatibleActionError extends Error { // @public (undocumented) export function plugin(initializerContext: PluginInitializerContext): UiActionsPlugin; +// Warning: (ae-missing-release-tag) "ROW_CLICK_TRIGGER" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const ROW_CLICK_TRIGGER = "ROW_CLICK_TRIGGER"; + +// Warning: (ae-missing-release-tag) "RowClickContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface RowClickContext { + // (undocumented) + data: { + rowIndex: number; + table: Datatable; + columns?: string[]; + }; + // Warning: (ae-forgotten-export) The symbol "IEmbeddable" needs to be exported by the entry point index.d.ts + // + // (undocumented) + embeddable?: IEmbeddable; +} + +// Warning: (ae-missing-release-tag) "rowClickTrigger" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const rowClickTrigger: Trigger<'ROW_CLICK_TRIGGER'>; + // Warning: (ae-missing-release-tag) "SELECT_RANGE_TRIGGER" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -170,6 +196,8 @@ export interface TriggerContextMapping { // // (undocumented) [APPLY_FILTER_TRIGGER]: ApplyGlobalFilterActionContext; + // (undocumented) + [ROW_CLICK_TRIGGER]: RowClickContext; // Warning: (ae-forgotten-export) The symbol "RangeSelectContext" needs to be exported by the entry point index.d.ts // // (undocumented) @@ -234,14 +262,14 @@ export class UiActionsService { // // (undocumented) protected readonly actions: ActionRegistry; - readonly addTriggerAction: (triggerId: T, action: UiActionsActionDefinition | Action) => void; + readonly addTriggerAction: (triggerId: T, action: UiActionsActionDefinition | Action) => void; // (undocumented) - readonly attachAction: (triggerId: T, actionId: string) => void; + readonly attachAction: (triggerId: T, actionId: string) => void; readonly clear: () => void; // (undocumented) readonly detachAction: (triggerId: TriggerId, actionId: string) => void; // @deprecated (undocumented) - readonly executeTriggerActions: (triggerId: T, context: TriggerContext) => Promise; + readonly executeTriggerActions: (triggerId: T, context: TriggerContext) => Promise; // Warning: (ae-forgotten-export) The symbol "UiActionsExecutionService" needs to be exported by the entry point index.d.ts // // (undocumented) @@ -252,11 +280,11 @@ export class UiActionsService { // Warning: (ae-forgotten-export) The symbol "TriggerContract" needs to be exported by the entry point index.d.ts // // (undocumented) - readonly getTrigger: (triggerId: T) => TriggerContract; + readonly getTrigger: (triggerId: T) => TriggerContract; // (undocumented) - readonly getTriggerActions: (triggerId: T) => Action[]; + readonly getTriggerActions: (triggerId: T) => Action[]; // (undocumented) - readonly getTriggerCompatibleActions: (triggerId: T, context: TriggerContextMapping[T]) => Promise[]>; + readonly getTriggerCompatibleActions: (triggerId: T, context: TriggerContextMapping[T]) => Promise[]>; // (undocumented) readonly hasAction: (actionId: string) => boolean; // Warning: (ae-forgotten-export) The symbol "ActionContext" needs to be exported by the entry point index.d.ts @@ -341,6 +369,10 @@ export const visualizeFieldTrigger: Trigger<'VISUALIZE_FIELD_TRIGGER'>; export const visualizeGeoFieldTrigger: Trigger<'VISUALIZE_GEO_FIELD_TRIGGER'>; +// Warnings were encountered during analysis: +// +// src/plugins/ui_actions/public/triggers/row_click_trigger.ts:45:5 - (ae-forgotten-export) The symbol "Datatable" needs to be exported by the entry point index.d.ts + // (No @packageDocumentation comment for this package) ``` diff --git a/src/plugins/ui_actions/public/service/ui_actions_execution_service.ts b/src/plugins/ui_actions/public/service/ui_actions_execution_service.ts index 4f0ab52501a95..59616dcf3f38d 100644 --- a/src/plugins/ui_actions/public/service/ui_actions_execution_service.ts +++ b/src/plugins/ui_actions/public/service/ui_actions_execution_service.ts @@ -29,6 +29,7 @@ interface ExecuteActionTask { context: BaseContext; trigger: Trigger; defer: Defer; + alwaysShowPopup?: boolean; } export class UiActionsExecutionService { @@ -37,21 +38,25 @@ export class UiActionsExecutionService { constructor() {} - async execute({ - action, - context, - trigger, - }: { - action: Action; - context: BaseContext; - trigger: Trigger; - }): Promise { + async execute( + { + action, + context, + trigger, + }: { + action: Action; + context: BaseContext; + trigger: Trigger; + }, + alwaysShowPopup?: boolean + ): Promise { const shouldBatch = !(await action.shouldAutoExecute?.({ ...context, trigger })) ?? false; const task: ExecuteActionTask = { action, context, trigger, defer: createDefer(), + alwaysShowPopup: !!alwaysShowPopup, }; if (shouldBatch) { @@ -84,11 +89,23 @@ export class UiActionsExecutionService { setTimeout(() => { if (this.pendingTasks.size === 0) { const tasks = uniqBy(this.batchingQueue, (t) => t.action.id); - if (tasks.length === 1) { - this.executeSingleTask(tasks[0]); - } - if (tasks.length > 1) { - this.executeMultipleActions(tasks); + if (tasks.length > 0) { + let alwaysShowPopup = false; + for (const task of tasks) { + if (task.alwaysShowPopup) { + alwaysShowPopup = true; + break; + } + } + if (alwaysShowPopup) { + this.showActionPopupMenu(tasks); + } else { + if (tasks.length === 1) { + this.executeSingleTask(tasks[0]); + } else if (tasks.length > 1) { + this.showActionPopupMenu(tasks); + } + } } this.batchingQueue.splice(0, this.batchingQueue.length); @@ -108,7 +125,7 @@ export class UiActionsExecutionService { } } - private async executeMultipleActions(tasks: ExecuteActionTask[]) { + private async showActionPopupMenu(tasks: ExecuteActionTask[]) { const panels = await buildContextMenuForActions({ actions: tasks.map(({ action, context, trigger }) => ({ action, diff --git a/src/plugins/ui_actions/public/tests/execute_trigger_actions.test.ts b/src/plugins/ui_actions/public/tests/execute_trigger_actions.test.ts index af2510467ba87..51ba165ba730b 100644 --- a/src/plugins/ui_actions/public/tests/execute_trigger_actions.test.ts +++ b/src/plugins/ui_actions/public/tests/execute_trigger_actions.test.ts @@ -143,7 +143,32 @@ test('shows a context menu when more than one action is mapped to a trigger', as const start = doStart(); const context = {}; - await start.executeTriggerActions('MY-TRIGGER' as TriggerId, context); + await start.getTrigger('MY-TRIGGER' as TriggerId)!.exec(context); + + jest.runAllTimers(); + + await waitFor(() => { + expect(executeFn).toBeCalledTimes(0); + expect(openContextMenu).toHaveBeenCalledTimes(1); + }); +}); + +test('shows a context menu when there is only one action mapped to a trigger and "alwaysShowPopup" is set', async () => { + const { setup, doStart } = uiActions; + const trigger: Trigger = { + id: 'MY-TRIGGER' as TriggerId, + title: 'My trigger', + }; + const action1 = createTestAction('test1', () => true); + + setup.registerTrigger(trigger); + setup.addTriggerAction(trigger.id, action1); + + expect(openContextMenu).toHaveBeenCalledTimes(0); + + const start = doStart(); + const context = {}; + await start.getTrigger('MY-TRIGGER' as TriggerId)!.exec(context, true); jest.runAllTimers(); diff --git a/src/plugins/ui_actions/public/triggers/index.ts b/src/plugins/ui_actions/public/triggers/index.ts index b7039d287c6e2..ecbf4d1f7b988 100644 --- a/src/plugins/ui_actions/public/triggers/index.ts +++ b/src/plugins/ui_actions/public/triggers/index.ts @@ -22,6 +22,7 @@ export * from './trigger_contract'; export * from './trigger_internal'; export * from './select_range_trigger'; export * from './value_click_trigger'; +export * from './row_click_trigger'; export * from './apply_filter_trigger'; export * from './visualize_field_trigger'; export * from './visualize_geo_field_trigger'; diff --git a/src/plugins/ui_actions/public/triggers/row_click_trigger.ts b/src/plugins/ui_actions/public/triggers/row_click_trigger.ts new file mode 100644 index 0000000000000..87bca03f8c3ba --- /dev/null +++ b/src/plugins/ui_actions/public/triggers/row_click_trigger.ts @@ -0,0 +1,53 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import { IEmbeddable } from '../../../embeddable/public'; +import { Trigger } from '.'; +import { Datatable } from '../../../expressions'; + +export const ROW_CLICK_TRIGGER = 'ROW_CLICK_TRIGGER'; + +export const rowClickTrigger: Trigger<'ROW_CLICK_TRIGGER'> = { + id: ROW_CLICK_TRIGGER, + title: i18n.translate('uiActions.triggers.rowClickTitle', { + defaultMessage: 'Table row click', + }), + description: i18n.translate('uiActions.triggers.rowClickkDescription', { + defaultMessage: 'A click on a table row', + }), +}; + +export interface RowClickContext { + embeddable?: IEmbeddable; + data: { + /** + * Row index, starting from 0, where user clicked. + */ + rowIndex: number; + + table: Datatable; + + /** + * Sorted list column IDs that were visible to the user. Useful when only + * a subset of datatable columns should be used. + */ + columns?: string[]; + }; +} diff --git a/src/plugins/ui_actions/public/triggers/trigger_contract.ts b/src/plugins/ui_actions/public/triggers/trigger_contract.ts index ba1c5a693f937..04a75cb3a53d0 100644 --- a/src/plugins/ui_actions/public/triggers/trigger_contract.ts +++ b/src/plugins/ui_actions/public/triggers/trigger_contract.ts @@ -49,7 +49,7 @@ export class TriggerContract { /** * Use this method to execute action attached to this trigger. */ - public readonly exec = async (context: TriggerContextMapping[T]) => { - await this.internal.execute(context); + public readonly exec = async (context: TriggerContextMapping[T], alwaysShowPopup?: boolean) => { + await this.internal.execute(context, alwaysShowPopup); }; } diff --git a/src/plugins/ui_actions/public/triggers/trigger_internal.ts b/src/plugins/ui_actions/public/triggers/trigger_internal.ts index c766b5c798ecb..fd43a020504c0 100644 --- a/src/plugins/ui_actions/public/triggers/trigger_internal.ts +++ b/src/plugins/ui_actions/public/triggers/trigger_internal.ts @@ -31,17 +31,20 @@ export class TriggerInternal { constructor(public readonly service: UiActionsService, public readonly trigger: Trigger) {} - public async execute(context: TriggerContextMapping[T]) { + public async execute(context: TriggerContextMapping[T], alwaysShowPopup?: boolean) { const triggerId = this.trigger.id; const actions = await this.service.getTriggerCompatibleActions!(triggerId, context); await Promise.all([ actions.map((action) => - this.service.executionService.execute({ - action, - context, - trigger: this.trigger, - }) + this.service.executionService.execute( + { + action, + context, + trigger: this.trigger, + }, + alwaysShowPopup + ) ), ]); } diff --git a/src/plugins/ui_actions/public/types.ts b/src/plugins/ui_actions/public/types.ts index 0be3c19fc1c4d..0266a755be926 100644 --- a/src/plugins/ui_actions/public/types.ts +++ b/src/plugins/ui_actions/public/types.ts @@ -22,10 +22,12 @@ import { TriggerInternal } from './triggers/trigger_internal'; import { SELECT_RANGE_TRIGGER, VALUE_CLICK_TRIGGER, + ROW_CLICK_TRIGGER, APPLY_FILTER_TRIGGER, VISUALIZE_FIELD_TRIGGER, VISUALIZE_GEO_FIELD_TRIGGER, DEFAULT_TRIGGER, + RowClickContext, } from './triggers'; import type { RangeSelectContext, ValueClickContext } from '../../embeddable/public'; import type { ApplyGlobalFilterActionContext } from '../../data/public'; @@ -49,6 +51,7 @@ export interface TriggerContextMapping { [DEFAULT_TRIGGER]: TriggerContext; [SELECT_RANGE_TRIGGER]: RangeSelectContext; [VALUE_CLICK_TRIGGER]: ValueClickContext; + [ROW_CLICK_TRIGGER]: RowClickContext; [APPLY_FILTER_TRIGGER]: ApplyGlobalFilterActionContext; [VISUALIZE_FIELD_TRIGGER]: VisualizeFieldContext; [VISUALIZE_GEO_FIELD_TRIGGER]: VisualizeFieldContext; diff --git a/src/plugins/visualizations/public/embeddable/events.ts b/src/plugins/visualizations/public/embeddable/events.ts index 52cac59fbffaa..41e52c3ac1327 100644 --- a/src/plugins/visualizations/public/embeddable/events.ts +++ b/src/plugins/visualizations/public/embeddable/events.ts @@ -21,16 +21,19 @@ import { APPLY_FILTER_TRIGGER, SELECT_RANGE_TRIGGER, VALUE_CLICK_TRIGGER, -} from '../../../../plugins/ui_actions/public'; + ROW_CLICK_TRIGGER, +} from '../../../ui_actions/public'; export interface VisEventToTrigger { ['applyFilter']: typeof APPLY_FILTER_TRIGGER; ['brush']: typeof SELECT_RANGE_TRIGGER; ['filter']: typeof VALUE_CLICK_TRIGGER; + ['tableRowContextMenuClick']: typeof ROW_CLICK_TRIGGER; } export const VIS_EVENT_TO_TRIGGER: VisEventToTrigger = { applyFilter: APPLY_FILTER_TRIGGER, brush: SELECT_RANGE_TRIGGER, filter: VALUE_CLICK_TRIGGER, + tableRowContextMenuClick: ROW_CLICK_TRIGGER, }; diff --git a/x-pack/plugins/drilldowns/url_drilldown/public/lib/test/data.ts b/x-pack/plugins/drilldowns/url_drilldown/public/lib/test/data.ts new file mode 100644 index 0000000000000..e0627c521bb79 --- /dev/null +++ b/x-pack/plugins/drilldowns/url_drilldown/public/lib/test/data.ts @@ -0,0 +1,173 @@ +/* + * 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 { DatatableColumnType } from '../../../../../../../src/plugins/expressions/common'; +import { + Embeddable, + EmbeddableInput, + EmbeddableOutput, +} from '../../../../../../../src/plugins/embeddable/public'; + +export const createPoint = ({ + field, + value, +}: { + field: string; + value: string | null | number | boolean; +}) => ({ + table: { + columns: [ + { + name: field, + id: '1-1', + meta: { + type: 'date' as DatatableColumnType, + field, + source: 'esaggs', + sourceParams: { + type: 'histogram', + indexPatternId: 'logstash-*', + interval: 30, + otherBucket: true, + }, + }, + }, + ], + rows: [ + { + '1-1': '2048', + }, + ], + }, + column: 0, + row: 0, + value, +}); + +export const rowClickData = { + rowIndex: 1, + table: { + type: 'datatable', + rows: [ + { + '6ced5344-2596-4545-b626-8b449924e2d4': 'IT', + '6890e417-c5f1-4565-a45c-92f55380e14c': '0', + '93b8ef16-2483-45b8-ad27-6cc1f790578b': 13, + 'b0c5dcc2-4012-4d7e-b983-0e089badc43c': 0, + 'e0719f1a-04fb-4036-a63c-c25deac3f011': 7, + }, + { + '6ced5344-2596-4545-b626-8b449924e2d4': 'IT', + '6890e417-c5f1-4565-a45c-92f55380e14c': '2.25', + '93b8ef16-2483-45b8-ad27-6cc1f790578b': 3, + 'b0c5dcc2-4012-4d7e-b983-0e089badc43c': 0, + 'e0719f1a-04fb-4036-a63c-c25deac3f011': 2, + }, + { + '6ced5344-2596-4545-b626-8b449924e2d4': 'IT', + '6890e417-c5f1-4565-a45c-92f55380e14c': '0.020939215995129826', + '93b8ef16-2483-45b8-ad27-6cc1f790578b': 2, + 'b0c5dcc2-4012-4d7e-b983-0e089badc43c': 12.490584373474121, + 'e0719f1a-04fb-4036-a63c-c25deac3f011': 1, + }, + ], + columns: [ + { + id: '6ced5344-2596-4545-b626-8b449924e2d4', + name: 'Top values of DestCountry', + meta: { + type: 'string', + field: 'DestCountry', + index: 'kibana_sample_data_flights', + params: { + id: 'terms', + params: { + id: 'string', + otherBucketLabel: 'Other', + missingBucketLabel: '(missing value)', + }, + }, + source: 'esaggs', + }, + }, + { + id: '6890e417-c5f1-4565-a45c-92f55380e14c', + name: 'Top values of FlightTimeHour', + meta: { + type: 'string', + field: 'FlightTimeHour', + index: 'kibana_sample_data_flights', + params: { + id: 'terms', + params: { + id: 'string', + otherBucketLabel: 'Other', + missingBucketLabel: '(missing value)', + }, + }, + source: 'esaggs', + }, + }, + { + id: '93b8ef16-2483-45b8-ad27-6cc1f790578b', + name: 'Count of records', + meta: { + type: 'number', + index: 'kibana_sample_data_flights', + params: { + id: 'number', + }, + }, + }, + { + id: 'b0c5dcc2-4012-4d7e-b983-0e089badc43c', + name: 'Average of DistanceMiles', + meta: { + type: 'number', + field: 'DistanceMiles', + index: 'kibana_sample_data_flights', + params: { + id: 'number', + }, + }, + }, + { + id: 'e0719f1a-04fb-4036-a63c-c25deac3f011', + name: 'Unique count of OriginAirportID', + meta: { + type: 'string', + field: 'OriginAirportID', + index: 'kibana_sample_data_flights', + params: { + id: 'number', + }, + }, + }, + ], + }, + columns: [ + '6ced5344-2596-4545-b626-8b449924e2d4', + '6890e417-c5f1-4565-a45c-92f55380e14c', + '93b8ef16-2483-45b8-ad27-6cc1f790578b', + 'b0c5dcc2-4012-4d7e-b983-0e089badc43c', + 'e0719f1a-04fb-4036-a63c-c25deac3f011', + ], +}; + +interface TestInput extends EmbeddableInput { + savedObjectId?: string; +} + +interface TestOutput extends EmbeddableOutput { + indexPatterns?: Array<{ id: string }>; +} + +export class TestEmbeddable extends Embeddable { + type = 'test'; + + destroy() {} + reload() {} +} diff --git a/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.test.ts b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.test.ts index 79d380991f5fd..d9f63f233e1c2 100644 --- a/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.test.ts +++ b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.test.ts @@ -7,6 +7,11 @@ import { UrlDrilldown, ActionContext, Config } from './url_drilldown'; import { IEmbeddable } from '../../../../../../src/plugins/embeddable/public/lib/embeddables'; import { DatatableColumnType } from '../../../../../../src/plugins/expressions/common'; +import { createPoint, rowClickData, TestEmbeddable } from './test/data'; +import { + VALUE_CLICK_TRIGGER, + ROW_CLICK_TRIGGER, +} from '../../../../../../src/plugins/ui_actions/public'; const mockDataPoints = [ { @@ -99,7 +104,8 @@ describe('UrlDrilldown', () => { embeddable: mockEmbeddable, }; - await expect(urlDrilldown.isCompatible(config, context)).resolves.toBe(true); + const result = urlDrilldown.isCompatible(config, context); + await expect(result).resolves.toBe(true); }); test('not compatible if url is invalid', async () => { @@ -168,4 +174,199 @@ describe('UrlDrilldown', () => { expect(mockNavigateToUrl).not.toBeCalled(); }); }); + + describe('variables', () => { + const embeddable1 = new TestEmbeddable( + { + id: 'test', + title: 'The Title', + savedObjectId: 'SAVED_OBJECT_IDxx', + }, + { + indexPatterns: [{ id: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' }], + } + ); + const data: any = { + data: [ + createPoint({ field: 'field0', value: 'value0' }), + createPoint({ field: 'field1', value: 'value1' }), + createPoint({ field: 'field2', value: 'value2' }), + ], + }; + + const embeddable2 = new TestEmbeddable( + { + id: 'the-id', + query: { + language: 'C++', + query: 'std::cout << 123;', + }, + timeRange: { + from: 'FROM', + to: 'TO', + }, + filters: [ + { + meta: { + alias: 'asdf', + disabled: false, + negate: false, + }, + }, + ], + savedObjectId: 'SAVED_OBJECT_ID', + }, + { + title: 'The Title', + indexPatterns: [ + { id: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' }, + { id: 'yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy' }, + ], + } + ); + + describe('getRuntimeVariables()', () => { + test('builds runtime variables for VALUE_CLICK_TRIGGER trigger', () => { + const variables = urlDrilldown.getRuntimeVariables({ + embeddable: embeddable1, + data, + }); + + expect(variables).toMatchObject({ + kibanaUrl: 'http://localhost:5601/', + context: { + panel: { + id: 'test', + title: 'The Title', + savedObjectId: 'SAVED_OBJECT_IDxx', + indexPatternId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', + }, + }, + event: { + key: 'field0', + value: 'value0', + negate: false, + points: [ + { + value: 'value0', + key: 'field0', + }, + { + value: 'value1', + key: 'field1', + }, + { + value: 'value2', + key: 'field2', + }, + ], + }, + }); + }); + + test('builds runtime variables for ROW_CLICK_TRIGGER trigger', () => { + const variables = urlDrilldown.getRuntimeVariables({ + embeddable: embeddable2, + data: rowClickData as any, + }); + + expect(variables).toMatchObject({ + kibanaUrl: 'http://localhost:5601/', + context: { + panel: { + id: 'the-id', + title: 'The Title', + savedObjectId: 'SAVED_OBJECT_ID', + query: { + language: 'C++', + query: 'std::cout << 123;', + }, + timeRange: { + from: 'FROM', + to: 'TO', + }, + filters: [ + { + meta: { + alias: 'asdf', + disabled: false, + negate: false, + }, + }, + ], + }, + }, + event: { + rowIndex: 1, + values: ['IT', '2.25', 3, 0, 2], + keys: ['DestCountry', 'FlightTimeHour', '', 'DistanceMiles', 'OriginAirportID'], + columnNames: [ + 'Top values of DestCountry', + 'Top values of FlightTimeHour', + 'Count of records', + 'Average of DistanceMiles', + 'Unique count of OriginAirportID', + ], + }, + }); + }); + }); + + describe('getVariableList()', () => { + test('builds variable list for VALUE_CLICK_TRIGGER trigger', () => { + const list = urlDrilldown.getVariableList({ + triggers: [VALUE_CLICK_TRIGGER], + embeddable: embeddable1, + }); + + const expectedList = [ + 'event.key', + 'event.value', + 'event.negate', + 'event.points', + + 'context.panel.id', + 'context.panel.title', + 'context.panel.indexPatternId', + 'context.panel.savedObjectId', + + 'kibanaUrl', + ]; + + for (const expectedItem of expectedList) { + expect(list.includes(expectedItem)).toBe(true); + } + }); + + test('builds variable list for ROW_CLICK_TRIGGER trigger', () => { + const list = urlDrilldown.getVariableList({ + triggers: [ROW_CLICK_TRIGGER], + embeddable: embeddable2, + }); + + const expectedList = [ + 'event.columnNames', + 'event.keys', + 'event.rowIndex', + 'event.values', + + 'context.panel.id', + 'context.panel.title', + 'context.panel.filters', + 'context.panel.query.language', + 'context.panel.query.query', + 'context.panel.indexPatternIds', + 'context.panel.savedObjectId', + 'context.panel.timeRange.from', + 'context.panel.timeRange.to', + + 'kibanaUrl', + ]; + + for (const expectedItem of expectedList) { + expect(list.includes(expectedItem)).toBe(true); + } + }); + }); + }); }); diff --git a/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.tsx b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.tsx index 807dfeed21d1f..3a989c1b0b4cd 100644 --- a/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.tsx +++ b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.tsx @@ -5,6 +5,7 @@ */ import React from 'react'; +import { getFlattenedObject } from '@kbn/std'; import { reactToUiComponent } from '../../../../../../src/plugins/kibana_react/public'; import { ChartActionContext, @@ -13,6 +14,7 @@ import { } from '../../../../../../src/plugins/embeddable/public'; import { CollectConfigProps as CollectConfigPropsBase } from '../../../../../../src/plugins/kibana_utils/public'; import { + ROW_CLICK_TRIGGER, SELECT_RANGE_TRIGGER, VALUE_CLICK_TRIGGER, } from '../../../../../../src/plugins/ui_actions/public'; @@ -22,11 +24,10 @@ import { UrlDrilldownConfig, UrlDrilldownCollectConfig, urlDrilldownValidateUrlTemplate, - urlDrilldownBuildScope, urlDrilldownCompileUrl, UiActionsEnhancedBaseActionFactoryContext as BaseActionFactoryContext, } from '../../../../ui_actions_enhanced/public'; -import { getContextScope, getEventScope, getMockEventScope } from './url_drilldown_scope'; +import { getPanelVariables, getEventScope, getEventVariableList } from './url_drilldown_scope'; import { txtUrlDrilldownDisplayName } from './i18n'; interface UrlDrilldownDeps { @@ -39,9 +40,11 @@ interface UrlDrilldownDeps { export type ActionContext = ChartActionContext; export type Config = UrlDrilldownConfig; export type UrlTrigger = - | typeof CONTEXT_MENU_TRIGGER | typeof VALUE_CLICK_TRIGGER - | typeof SELECT_RANGE_TRIGGER; + | typeof SELECT_RANGE_TRIGGER + | typeof ROW_CLICK_TRIGGER + | typeof CONTEXT_MENU_TRIGGER; + export interface ActionFactoryContext extends BaseActionFactoryContext { embeddable?: IEmbeddable; } @@ -65,7 +68,7 @@ export class UrlDrilldown implements Drilldown = ({ @@ -74,12 +77,12 @@ export class UrlDrilldown implements Drilldown { // eslint-disable-next-line react-hooks/rules-of-hooks - const scope = React.useMemo(() => this.buildEditorScope(context), [context]); + const variables = React.useMemo(() => this.getVariableList(context), [context]); return ( @@ -93,19 +96,13 @@ export class UrlDrilldown implements Drilldown { - const { isValid } = urlDrilldownValidateUrlTemplate(config.url, this.buildEditorScope(context)); - return isValid; + public readonly isConfigValid = (config: Config): config is Config => { + return !!config.url.template; }; public readonly isCompatible = async (config: Config, context: ActionContext) => { - const { isValid, error } = urlDrilldownValidateUrlTemplate( - config.url, - await this.buildRuntimeScope(context) - ); + const scope = this.getRuntimeVariables(context); + const { isValid, error } = urlDrilldownValidateUrlTemplate(config.url, scope); if (!isValid) { // eslint-disable-next-line no-console @@ -117,11 +114,13 @@ export class UrlDrilldown implements Drilldown - urlDrilldownCompileUrl(config.url.template, this.buildRuntimeScope(context)); + public readonly getHref = async (config: Config, context: ActionContext) => { + const scope = this.getRuntimeVariables(context); + return urlDrilldownCompileUrl(config.url.template, scope); + }; public readonly execute = async (config: Config, context: ActionContext) => { - const url = urlDrilldownCompileUrl(config.url.template, this.buildRuntimeScope(context)); + const url = urlDrilldownCompileUrl(config.url.template, this.getRuntimeVariables(context)); if (config.openInNewTab) { window.open(url, '_blank', 'noopener'); } else { @@ -129,19 +128,23 @@ export class UrlDrilldown implements Drilldown { - return urlDrilldownBuildScope({ - globalScope: this.deps.getGlobalScope(), - contextScope: getContextScope(context), - eventScope: getMockEventScope(context.triggers), - }); + public readonly getRuntimeVariables = (context: ActionContext) => { + return { + ...this.deps.getGlobalScope(), + context: { + panel: getPanelVariables(context), + }, + event: getEventScope(context), + }; }; - private buildRuntimeScope = (context: ActionContext) => { - return urlDrilldownBuildScope({ - globalScope: this.deps.getGlobalScope(), - contextScope: getContextScope(context), - eventScope: getEventScope(context), - }); + public readonly getVariableList = (context: ActionFactoryContext): string[] => { + const eventVariables = getEventVariableList(context); + const contextVariables = Object.keys(getFlattenedObject(getPanelVariables(context))).map( + (key) => 'context.panel.' + key + ); + const globalVariables = Object.keys(getFlattenedObject(this.deps.getGlobalScope())); + + return [...eventVariables.sort(), ...contextVariables.sort(), ...globalVariables.sort()]; }; } diff --git a/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.test.ts b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.test.ts index a93e150deee8f..5917737d15eda 100644 --- a/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.test.ts +++ b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.test.ts @@ -6,46 +6,15 @@ import { getEventScope, - getMockEventScope, ValueClickTriggerEventScope, + getEventVariableList, + getPanelVariables, } from './url_drilldown_scope'; -import { DatatableColumnType } from '../../../../../../src/plugins/expressions/common'; - -const createPoint = ({ - field, - value, -}: { - field: string; - value: string | null | number | boolean; -}) => ({ - table: { - columns: [ - { - name: field, - id: '1-1', - meta: { - type: 'date' as DatatableColumnType, - field, - source: 'esaggs', - sourceParams: { - type: 'histogram', - indexPatternId: 'logstash-*', - interval: 30, - otherBucket: true, - }, - }, - }, - ], - rows: [ - { - '1-1': '2048', - }, - ], - }, - column: 0, - row: 0, - value, -}); +import { + RowClickContext, + ROW_CLICK_TRIGGER, +} from '../../../../../../src/plugins/ui_actions/public'; +import { createPoint, rowClickData, TestEmbeddable } from './test/data'; describe('VALUE_CLICK_TRIGGER', () => { describe('supports `points[]`', () => { @@ -80,33 +49,6 @@ describe('VALUE_CLICK_TRIGGER', () => { ] `); }); - - test('getMockEventScope()', () => { - const mockEventScope = getMockEventScope([ - 'VALUE_CLICK_TRIGGER', - ]) as ValueClickTriggerEventScope; - expect(mockEventScope.points.length).toBeGreaterThan(3); - expect(mockEventScope.points).toMatchInlineSnapshot(` - Array [ - Object { - "key": "event.points.0.key", - "value": "event.points.0.value", - }, - Object { - "key": "event.points.1.key", - "value": "event.points.1.value", - }, - Object { - "key": "event.points.2.key", - "value": "event.points.2.value", - }, - Object { - "key": "event.points.3.key", - "value": "event.points.3.value", - }, - ] - `); - }); }); describe('handles undefined, null or missing values', () => { @@ -131,11 +73,221 @@ describe('VALUE_CLICK_TRIGGER', () => { }); }); -describe('CONTEXT_MENU_TRIGGER', () => { - test('getMockEventScope() results in empty scope', () => { - const mockEventScope = getMockEventScope([ - 'CONTEXT_MENU_TRIGGER', - ]) as ValueClickTriggerEventScope; - expect(mockEventScope).toEqual({}); +describe('ROW_CLICK_TRIGGER', () => { + test('getEventVariableList() returns correct list of runtime variables', () => { + const vars = getEventVariableList({ + triggers: [ROW_CLICK_TRIGGER], + }); + expect(vars).toEqual(['event.rowIndex', 'event.values', 'event.keys', 'event.columnNames']); + }); + + test('getEventScope() returns correct variables for row click trigger', () => { + const context = ({ + embeddable: {}, + data: rowClickData as any, + } as unknown) as RowClickContext; + const res = getEventScope(context); + + expect(res).toEqual({ + rowIndex: 1, + values: ['IT', '2.25', 3, 0, 2], + keys: ['DestCountry', 'FlightTimeHour', '', 'DistanceMiles', 'OriginAirportID'], + columnNames: [ + 'Top values of DestCountry', + 'Top values of FlightTimeHour', + 'Count of records', + 'Average of DistanceMiles', + 'Unique count of OriginAirportID', + ], + }); + }); +}); + +describe('getPanelVariables()', () => { + test('returns only ID for empty embeddable', () => { + const embeddable = new TestEmbeddable( + { + id: 'test', + }, + {} + ); + const vars = getPanelVariables({ embeddable }); + + expect(vars).toEqual({ + id: 'test', + }); + }); + + test('returns title as specified in input', () => { + const embeddable = new TestEmbeddable( + { + id: 'test', + title: 'title1', + }, + {} + ); + const vars = getPanelVariables({ embeddable }); + + expect(vars).toEqual({ + id: 'test', + title: 'title1', + }); + }); + + test('returns output title if input and output titles are specified', () => { + const embeddable = new TestEmbeddable( + { + id: 'test', + title: 'title1', + }, + { + title: 'title2', + } + ); + const vars = getPanelVariables({ embeddable }); + + expect(vars).toEqual({ + id: 'test', + title: 'title2', + }); + }); + + test('returns title from output if title in input is missing', () => { + const embeddable = new TestEmbeddable( + { + id: 'test', + }, + { + title: 'title2', + } + ); + const vars = getPanelVariables({ embeddable }); + + expect(vars).toEqual({ + id: 'test', + title: 'title2', + }); + }); + + test('returns saved object ID from output', () => { + const embeddable = new TestEmbeddable( + { + id: 'test', + savedObjectId: '5678', + }, + { + savedObjectId: '1234', + } + ); + const vars = getPanelVariables({ embeddable }); + + expect(vars).toEqual({ + id: 'test', + savedObjectId: '1234', + }); + }); + + test('returns saved object ID from input if it is not set on output', () => { + const embeddable = new TestEmbeddable( + { + id: 'test', + savedObjectId: '5678', + }, + {} + ); + const vars = getPanelVariables({ embeddable }); + + expect(vars).toEqual({ + id: 'test', + savedObjectId: '5678', + }); + }); + + test('returns query, timeRange and filters from input', () => { + const embeddable = new TestEmbeddable( + { + id: 'test', + query: { + language: 'C++', + query: 'std::cout << 123;', + }, + timeRange: { + from: 'FROM', + to: 'TO', + }, + filters: [ + { + meta: { + alias: 'asdf', + disabled: false, + negate: false, + }, + }, + ], + }, + {} + ); + const vars = getPanelVariables({ embeddable }); + + expect(vars).toEqual({ + id: 'test', + query: { + language: 'C++', + query: 'std::cout << 123;', + }, + timeRange: { + from: 'FROM', + to: 'TO', + }, + filters: [ + { + meta: { + alias: 'asdf', + disabled: false, + negate: false, + }, + }, + ], + }); + }); + + test('returns a single index pattern from output', () => { + const embeddable = new TestEmbeddable( + { + id: 'test', + }, + { + indexPatterns: [{ id: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' }], + } + ); + const vars = getPanelVariables({ embeddable }); + + expect(vars).toEqual({ + id: 'test', + indexPatternId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', + }); + }); + + test('returns multiple index patterns from output', () => { + const embeddable = new TestEmbeddable( + { + id: 'test', + }, + { + indexPatterns: [ + { id: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' }, + { id: 'yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy' }, + ], + } + ); + const vars = getPanelVariables({ embeddable }); + + expect(vars).toEqual({ + id: 'test', + indexPatternIds: [ + 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', + 'yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy', + ], + }); }); }); diff --git a/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.ts b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.ts index 234af380689e9..3e5fc0a968d39 100644 --- a/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.ts +++ b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.ts @@ -14,48 +14,54 @@ import { IEmbeddable, isRangeSelectTriggerContext, isValueClickTriggerContext, + isRowClickTriggerContext, isContextMenuTriggerContext, RangeSelectContext, ValueClickContext, + EmbeddableOutput, } from '../../../../../../src/plugins/embeddable/public'; -import type { ActionContext, ActionFactoryContext, UrlTrigger } from './url_drilldown'; +import type { ActionContext, ActionFactoryContext } from './url_drilldown'; import { SELECT_RANGE_TRIGGER, + RowClickContext, VALUE_CLICK_TRIGGER, + ROW_CLICK_TRIGGER, } from '../../../../../../src/plugins/ui_actions/public'; -type ContextScopeInput = ActionContext | ActionFactoryContext; - /** * Part of context scope extracted from an embeddable * Expose on the scope as: `{{context.panel.id}}`, `{{context.panel.filters.[0]}}` */ interface EmbeddableUrlDrilldownContextScope { + /** + * ID of the embeddable panel. + */ id: string; + + /** + * Title of the embeddable panel. + */ title?: string; - query?: Query; - filters?: Filter[]; - timeRange?: TimeRange; - savedObjectId?: string; + /** - * In case panel supports only 1 index patterns + * In case panel supports only 1 index pattern. */ indexPatternId?: string; + /** - * In case panel supports more then 1 index patterns + * In case panel supports more then 1 index pattern. */ indexPatternIds?: string[]; -} -/** - * Url drilldown context scope - * `{{context.$}}` - */ -interface UrlDrilldownContextScope { - panel?: EmbeddableUrlDrilldownContextScope; + query?: Query; + filters?: Filter[]; + timeRange?: TimeRange; + savedObjectId?: string; } -export function getContextScope(contextScopeInput: ContextScopeInput): UrlDrilldownContextScope { +export function getPanelVariables(contextScopeInput: { + embeddable?: IEmbeddable; +}): EmbeddableUrlDrilldownContextScope { function hasEmbeddable(val: unknown): val is { embeddable: IEmbeddable } { if (val && typeof val === 'object' && 'embeddable' in val) return true; return false; @@ -64,41 +70,52 @@ export function getContextScope(contextScopeInput: ContextScopeInput): UrlDrilld throw new Error( "UrlDrilldown [getContextScope] can't build scope because embeddable object is missing in context" ); - const embeddable = contextScopeInput.embeddable; + + return getEmbeddableVariables(embeddable); +} + +function hasSavedObjectId(obj: Record): obj is { savedObjectId: string } { + return 'savedObjectId' in obj && typeof obj.savedObjectId === 'string'; +} + +/** + * @todo Same functionality is implemented in x-pack/plugins/discover_enhanced/public/actions/explore_data/shared.ts, + * combine both implementations into a common approach. + */ +function getIndexPatternIds(output: EmbeddableOutput): string[] { + function hasIndexPatterns( + _output: Record + ): _output is { indexPatterns: Array<{ id?: string }> } { + return ( + 'indexPatterns' in _output && + Array.isArray(_output.indexPatterns) && + _output.indexPatterns.length > 0 + ); + } + return hasIndexPatterns(output) + ? (output.indexPatterns.map((ip) => ip.id).filter(Boolean) as string[]) + : []; +} + +export function getEmbeddableVariables( + embeddable: IEmbeddable +): EmbeddableUrlDrilldownContextScope { const input = embeddable.getInput(); const output = embeddable.getOutput(); - function hasSavedObjectId(obj: Record): obj is { savedObjectId: string } { - return 'savedObjectId' in obj && typeof obj.savedObjectId === 'string'; - } - function getIndexPatternIds(): string[] { - function hasIndexPatterns( - _output: Record - ): _output is { indexPatterns: Array<{ id?: string }> } { - return ( - 'indexPatterns' in _output && - Array.isArray(_output.indexPatterns) && - _output.indexPatterns.length > 0 - ); - } - return hasIndexPatterns(output) - ? (output.indexPatterns.map((ip) => ip.id).filter(Boolean) as string[]) - : []; - } - const indexPatternsIds = getIndexPatternIds(); - return { - panel: cleanEmptyKeys({ - id: input.id, - title: output.title ?? input.title, - savedObjectId: - output.savedObjectId ?? (hasSavedObjectId(input) ? input.savedObjectId : undefined), - query: input.query, - timeRange: input.timeRange, - filters: input.filters, - indexPatternIds: indexPatternsIds.length > 1 ? indexPatternsIds : undefined, - indexPatternId: indexPatternsIds.length === 1 ? indexPatternsIds[0] : undefined, - }), - }; + const indexPatternsIds = getIndexPatternIds(output); + + return deleteUndefinedKeys({ + id: input.id, + title: output.title ?? input.title, + savedObjectId: + output.savedObjectId ?? (hasSavedObjectId(input) ? input.savedObjectId : undefined), + query: input.query, + timeRange: input.timeRange, + filters: input.filters, + indexPatternIds: indexPatternsIds.length > 1 ? indexPatternsIds : undefined, + indexPatternId: indexPatternsIds.length === 1 ? indexPatternsIds[0] : undefined, + }); } /** @@ -108,7 +125,9 @@ export function getContextScope(contextScopeInput: ContextScopeInput): UrlDrilld export type UrlDrilldownEventScope = | ValueClickTriggerEventScope | RangeSelectTriggerEventScope + | RowClickTriggerEventScope | ContextMenuTriggerEventScope; + export type EventScopeInput = ActionContext; export interface ValueClickTriggerEventScope { key?: string; @@ -122,6 +141,12 @@ export interface RangeSelectTriggerEventScope { to?: string | number; } +export interface RowClickTriggerEventScope { + rowIndex: number; + values: Primitive[]; + keys: string[]; + columnNames: string[]; +} export type ContextMenuTriggerEventScope = object; export function getEventScope(eventScopeInput: EventScopeInput): UrlDrilldownEventScope { @@ -129,6 +154,8 @@ export function getEventScope(eventScopeInput: EventScopeInput): UrlDrilldownEve return getEventScopeFromRangeSelectTriggerContext(eventScopeInput); } else if (isValueClickTriggerContext(eventScopeInput)) { return getEventScopeFromValueClickTriggerContext(eventScopeInput); + } else if (isRowClickTriggerContext(eventScopeInput)) { + return getEventScopeFromRowClickTriggerContext(eventScopeInput); } else if (isContextMenuTriggerContext(eventScopeInput)) { return {}; } else { @@ -141,7 +168,7 @@ function getEventScopeFromRangeSelectTriggerContext( ): RangeSelectTriggerEventScope { const { table, column: columnIndex, range } = eventScopeInput.data; const column = table.columns[columnIndex]; - return cleanEmptyKeys({ + return deleteUndefinedKeys({ key: toPrimitiveOrUndefined(column?.meta.field) as string, from: toPrimitiveOrUndefined(range[0]) as string | number | undefined, to: toPrimitiveOrUndefined(range[range.length - 1]) as string | number | undefined, @@ -160,7 +187,7 @@ function getEventScopeFromValueClickTriggerContext( }; }); - return cleanEmptyKeys({ + return deleteUndefinedKeys({ key: points[0]?.key, value: points[0]?.value, negate, @@ -168,37 +195,53 @@ function getEventScopeFromValueClickTriggerContext( }); } -/** - * @remarks - * Difference between `event` and `context` variables, is that real `context` variables are available during drilldown creation (e.g. embeddable panel) - * `event` variables are mapped from trigger context. Since there is no trigger context during drilldown creation, we have to provide some _mock_ variables for validating and previewing the URL - */ -export function getMockEventScope([trigger]: UrlTrigger[]): UrlDrilldownEventScope { - if (trigger === SELECT_RANGE_TRIGGER) { - return { - key: 'event.key', - from: new Date(Date.now() - 15 * 60 * 1000).toISOString(), // 15 minutes ago - to: new Date().toISOString(), - }; +function getEventScopeFromRowClickTriggerContext({ + embeddable, + data, +}: RowClickContext): RowClickTriggerEventScope { + const { rowIndex } = data; + const columns = data.columns || data.table.columns.map(({ id }) => id); + const values: Primitive[] = []; + const keys: string[] = []; + const columnNames: string[] = []; + const row = data.table.rows[rowIndex]; + + for (const columnId of columns) { + const column = data.table.columns.find(({ id }) => id === columnId); + if (!column) { + // This should never happe, but in case it does we log data necessary for debugging. + // eslint-disable-next-line no-console + console.error(data, embeddable ? `Embeddable [${embeddable.getTitle()}]` : null); + throw new Error('Could not find a datatable column.'); + } + values.push(row[columnId]); + keys.push(column.meta.field || ''); + columnNames.push(column.name || column.meta.field || ''); } - if (trigger === VALUE_CLICK_TRIGGER) { - // number of mock points to generate - // should be larger or equal of any possible data points length emitted by VALUE_CLICK_TRIGGER - const nPoints = 4; - const points = new Array(nPoints).fill(0).map((_, index) => ({ - key: `event.points.${index}.key`, - value: `event.points.${index}.value`, - })); - return { - key: `event.key`, - value: `event.value`, - negate: false, - points, - }; + const scope: RowClickTriggerEventScope = { + rowIndex, + values, + keys, + columnNames, + }; + + return scope; +} + +export function getEventVariableList(context: ActionFactoryContext): string[] { + const [trigger] = context.triggers; + + switch (trigger) { + case SELECT_RANGE_TRIGGER: + return ['event.key', 'event.from', 'event.to']; + case VALUE_CLICK_TRIGGER: + return ['event.key', 'event.value', 'event.negate', 'event.points']; + case ROW_CLICK_TRIGGER: + return ['event.rowIndex', 'event.values', 'event.keys', 'event.columnNames']; } - return {}; + return []; } type Primitive = string | number | boolean | null; @@ -210,7 +253,7 @@ function toPrimitiveOrUndefined(v: unknown): Primitive | undefined { return String(v); } -function cleanEmptyKeys>(obj: T): T { +function deleteUndefinedKeys>(obj: T): T { Object.keys(obj).forEach((key) => { if (obj[key] === undefined) { delete obj[key]; diff --git a/x-pack/plugins/lens/public/datatable_visualization/__snapshots__/expression.test.tsx.snap b/x-pack/plugins/lens/public/datatable_visualization/__snapshots__/expression.test.tsx.snap index 9c7bdc3397f9c..d340d002b242b 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/__snapshots__/expression.test.tsx.snap +++ b/x-pack/plugins/lens/public/datatable_visualization/__snapshots__/expression.test.tsx.snap @@ -1,5 +1,60 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`datatable_expression DatatableComponent it renders actions column when there are row actions 1`] = ` + + + +`; + exports[`datatable_expression DatatableComponent it renders the title and value 1`] = ` { ).toMatchSnapshot(); }); + test('it renders actions column when there are row actions', () => { + const { data, args } = sampleArgs(); + + expect( + shallow( + x as IFieldFormat} + onClickValue={onClickValue} + getType={jest.fn()} + onRowContextMenuClick={() => undefined} + rowHasRowClickTriggerActions={[true, true, true]} + /> + ) + ).toMatchSnapshot(); + }); + test('it invokes executeTriggerActions with correct context on click on top value', () => { const { args, data } = sampleArgs(); diff --git a/x-pack/plugins/lens/public/datatable_visualization/expression.tsx b/x-pack/plugins/lens/public/datatable_visualization/expression.tsx index 6502e07697816..f1eaab908717a 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/expression.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/expression.tsx @@ -10,13 +10,22 @@ import React, { useMemo } from 'react'; import ReactDOM from 'react-dom'; import { i18n } from '@kbn/i18n'; import { I18nProvider } from '@kbn/i18n/react'; -import { EuiBasicTable, EuiFlexGroup, EuiButtonIcon, EuiFlexItem, EuiToolTip } from '@elastic/eui'; +import { + EuiBasicTable, + EuiFlexGroup, + EuiButtonIcon, + EuiFlexItem, + EuiToolTip, + EuiBasicTableColumn, + EuiTableActionsColumnType, +} from '@elastic/eui'; import { IAggType } from 'src/plugins/data/public'; import { FormatFactory, ILensInterpreterRenderHandlers, LensFilterEvent, LensMultiTable, + LensTableRowContextMenuEvent, } from '../types'; import { ExpressionFunctionDefinition, @@ -45,7 +54,14 @@ export interface DatatableProps { type DatatableRenderProps = DatatableProps & { formatFactory: FormatFactory; onClickValue: (data: LensFilterEvent['data']) => void; + onRowContextMenuClick?: (data: LensTableRowContextMenuEvent['data']) => void; getType: (name: string) => IAggType; + + /** + * A boolean for each table row, which is true if the row active + * ROW_CLICK_TRIGGER actions attached to it, otherwise false. + */ + rowHasRowClickTriggerActions?: boolean[]; }; export interface DatatableRender { @@ -143,13 +159,47 @@ export const getDatatableRenderer = (dependencies: { const onClickValue = (data: LensFilterEvent['data']) => { handlers.event({ name: 'filter', data }); }; + const onRowContextMenuClick = (data: LensTableRowContextMenuEvent['data']) => { + handlers.event({ name: 'tableRowContextMenuClick', data }); + }; + const { hasCompatibleActions } = handlers; + + // An entry for each table row, whether it has any actions attached to + // ROW_CLICK_TRIGGER trigger. + let rowHasRowClickTriggerActions: boolean[] = []; + if (hasCompatibleActions) { + const table = Object.values(config.data.tables)[0]; + if (!!table) { + rowHasRowClickTriggerActions = await Promise.all( + table.rows.map(async (row, rowIndex) => { + try { + const hasActions = await hasCompatibleActions({ + name: 'tableRowContextMenuClick', + data: { + rowIndex, + table, + columns: config.args.columns.columnIds, + }, + }); + + return hasActions; + } catch { + return false; + } + }) + ); + } + } + ReactDOM.render( , domNode, @@ -169,7 +219,7 @@ export function DatatableComponent(props: DatatableRenderProps) { formatters[column.id] = props.formatFactory(column.meta?.params); }); - const { onClickValue } = props; + const { onClickValue, onRowContextMenuClick } = props; const handleFilterClick = useMemo( () => (field: string, value: unknown, colIndex: number, negate: boolean = false) => { const col = firstTable.columns[colIndex]; @@ -214,6 +264,124 @@ export function DatatableComponent(props: DatatableRenderProps) { return ; } + const tableColumns: Array< + EuiBasicTableColumn<{ rowIndex: number; [key: string]: unknown }> + > = props.args.columns.columnIds + .map((field) => { + const col = firstTable.columns.find((c) => c.id === field); + const filterable = bucketColumns.includes(field); + const colIndex = firstTable.columns.findIndex((c) => c.id === field); + return { + field, + name: (col && col.name) || '', + render: (value: unknown) => { + const formattedValue = formatters[field]?.convert(value); + const fieldName = col?.meta?.field; + + if (filterable) { + return ( + + {formattedValue} + + + + handleFilterClick(field, value, colIndex)} + /> + + + + handleFilterClick(field, value, colIndex, true)} + /> + + + + + + ); + } + return {formattedValue}; + }, + }; + }) + .filter(({ field }) => !!field); + + if (!!props.rowHasRowClickTriggerActions && !!onRowContextMenuClick) { + const hasAtLeastOneRowClickAction = props.rowHasRowClickTriggerActions.find((x) => x); + if (hasAtLeastOneRowClickAction) { + const actions: EuiTableActionsColumnType<{ rowIndex: number; [key: string]: unknown }> = { + name: i18n.translate('xpack.lens.datatable.actionsColumnName', { + defaultMessage: 'Actions', + }), + actions: [ + { + name: i18n.translate('xpack.lens.tableRowMore', { + defaultMessage: 'More', + }), + description: i18n.translate('xpack.lens.tableRowMoreDescription', { + defaultMessage: 'Table row context menu', + }), + type: 'icon', + icon: ({ rowIndex }: { rowIndex: number }) => { + if ( + !!props.rowHasRowClickTriggerActions && + !props.rowHasRowClickTriggerActions[rowIndex] + ) + return 'empty'; + return 'boxesVertical'; + }, + onClick: ({ rowIndex }) => { + onRowContextMenuClick({ + rowIndex, + table: firstTable, + columns: props.args.columns.columnIds, + }); + }, + }, + ], + }; + tableColumns.push(actions); + } + } + return ( { - const col = firstTable.columns.find((c) => c.id === field); - const filterable = bucketColumns.includes(field); - const colIndex = firstTable.columns.findIndex((c) => c.id === field); - return { - field, - name: (col && col.name) || '', - render: (value: unknown) => { - const formattedValue = formatters[field]?.convert(value); - const fieldName = col?.meta?.field; - - if (filterable) { - return ( - - {formattedValue} - - - - handleFilterClick(field, value, colIndex)} - /> - - - - handleFilterClick(field, value, colIndex, true)} - /> - - - - - - ); - } - return {formattedValue}; - }, - }; - }) - .filter(({ field }) => !!field)} - items={firstTable ? firstTable.rows : []} + columns={tableColumns} + items={firstTable ? firstTable.rows.map((row, rowIndex) => ({ ...row, rowIndex })) : []} /> ); diff --git a/x-pack/plugins/lens/public/datatable_visualization/index.ts b/x-pack/plugins/lens/public/datatable_visualization/index.ts index 5d9be46db7fb5..9c7d7ae1f2d43 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/index.ts +++ b/x-pack/plugins/lens/public/datatable_visualization/index.ts @@ -7,11 +7,9 @@ import { CoreSetup } from 'kibana/public'; import { ExpressionsSetup } from '../../../../../src/plugins/expressions/public'; import { EditorFrameSetup, FormatFactory } from '../types'; -import { UiActionsStart } from '../../../../../src/plugins/ui_actions/public'; import { DataPublicPluginStart } from '../../../../../src/plugins/data/public'; interface DatatableVisualizationPluginStartPlugins { - uiActions: UiActionsStart; data: DataPublicPluginStart; } export interface DatatableVisualizationPluginSetupPlugins { @@ -34,6 +32,7 @@ export class DatatableVisualization { getDatatableRenderer, datatableVisualization, } = await import('../async_services'); + expressions.registerFunction(() => datatableColumns); expressions.registerFunction(() => datatable); expressions.registerRenderer(() => diff --git a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx index 54517e4ee8c84..175c573d3be3a 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.test.tsx @@ -25,7 +25,7 @@ import { dataPluginMock } from '../../../../../../src/plugins/data/public/mocks' import { VIS_EVENT_TO_TRIGGER } from '../../../../../../src/plugins/visualizations/public/embeddable'; import { coreMock, httpServiceMock } from '../../../../../../src/core/public/mocks'; import { IBasePath } from '../../../../../../src/core/public'; -import { AttributeService } from '../../../../../../src/plugins/embeddable/public'; +import { AttributeService, ViewMode } from '../../../../../../src/plugins/embeddable/public'; import { LensAttributeService } from '../../lens_attribute_service'; import { OnSaveProps } from '../../../../../../src/plugins/saved_objects/public/save_modal'; import { act } from 'react-dom/test-utils'; @@ -221,6 +221,74 @@ describe('embeddable', () => { expect(expressionRenderer).toHaveBeenCalledTimes(2); }); + it('should re-render when dashboard view/edit mode changes', async () => { + const embeddable = new Embeddable( + { + timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter, + attributeService, + expressionRenderer, + basePath, + indexPatternService: {} as IndexPatternsContract, + editable: true, + getTrigger, + documentToExpression: () => + Promise.resolve({ + type: 'expression', + chain: [ + { type: 'function', function: 'my', arguments: {} }, + { type: 'function', function: 'expression', arguments: {} }, + ], + }), + }, + { id: '123' } as LensEmbeddableInput + ); + await embeddable.initializeSavedVis({ id: '123' } as LensEmbeddableInput); + embeddable.render(mountpoint); + + expect(expressionRenderer).toHaveBeenCalledTimes(1); + + embeddable.updateInput({ + viewMode: ViewMode.VIEW, + }); + + expect(expressionRenderer).toHaveBeenCalledTimes(2); + }); + + it('should re-render when dynamic actions input changes', async () => { + const embeddable = new Embeddable( + { + timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter, + attributeService, + expressionRenderer, + basePath, + indexPatternService: {} as IndexPatternsContract, + editable: true, + getTrigger, + documentToExpression: () => + Promise.resolve({ + type: 'expression', + chain: [ + { type: 'function', function: 'my', arguments: {} }, + { type: 'function', function: 'expression', arguments: {} }, + ], + }), + }, + { id: '123' } as LensEmbeddableInput + ); + await embeddable.initializeSavedVis({ id: '123' } as LensEmbeddableInput); + embeddable.render(mountpoint); + + expect(expressionRenderer).toHaveBeenCalledTimes(1); + + embeddable.updateInput({ + enhancements: { + dynamicActions: {}, + }, + }); + + expect(expressionRenderer).toHaveBeenCalledTimes(2); + }); + it('should pass context to embeddable', async () => { const timeRange: TimeRange = { from: 'now-15d', to: 'now' }; const query: Query = { language: 'kquery', query: '' }; @@ -396,6 +464,37 @@ describe('embeddable', () => { ); }); + it('should execute trigger on row click event from expression renderer', async () => { + const embeddable = new Embeddable( + { + timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter, + attributeService, + expressionRenderer, + basePath, + indexPatternService: {} as IndexPatternsContract, + editable: true, + getTrigger, + documentToExpression: () => + Promise.resolve({ + type: 'expression', + chain: [ + { type: 'function', function: 'my', arguments: {} }, + { type: 'function', function: 'expression', arguments: {} }, + ], + }), + }, + { id: '123' } as LensEmbeddableInput + ); + await embeddable.initializeSavedVis({ id: '123' } as LensEmbeddableInput); + embeddable.render(mountpoint); + + const onEvent = expressionRenderer.mock.calls[0][0].onEvent!; + + onEvent({ name: 'tableRowContextMenuClick', data: {} }); + + expect(getTrigger).toHaveBeenCalledWith(VIS_EVENT_TO_TRIGGER.tableRowContextMenuClick); + }); + it('should not re-render if only change is in disabled filter', async () => { const timeRange: TimeRange = { from: 'now-15d', to: 'now' }; const query: Query = { language: 'kquery', query: '' }; diff --git a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx index e7d3e1a4bfa5b..6c86ae5cff2c8 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx @@ -21,6 +21,8 @@ import { PaletteOutput } from 'src/plugins/charts/public'; import { Subscription } from 'rxjs'; import { toExpression, Ast } from '@kbn/interpreter/common'; import { RenderMode } from 'src/plugins/expressions'; +import { map, distinctUntilChanged, skip } from 'rxjs/operators'; +import isEqual from 'fast-deep-equal'; import { ExpressionRendererEvent, ReactExpressionRendererType, @@ -38,7 +40,11 @@ import { import { Document, injectFilterReferences } from '../../persistence'; import { ExpressionWrapper } from './expression_wrapper'; import { UiActionsStart } from '../../../../../../src/plugins/ui_actions/public'; -import { isLensBrushEvent, isLensFilterEvent } from '../../types'; +import { + isLensBrushEvent, + isLensFilterEvent, + isLensTableRowContextMenuClickEvent, +} from '../../types'; import { IndexPatternsContract } from '../../../../../../src/plugins/data/public'; import { getEditPath, DOC_TYPE } from '../../../common'; @@ -71,6 +77,7 @@ export interface LensEmbeddableDeps { timefilter: TimefilterContract; basePath: IBasePath; getTrigger?: UiActionsStart['getTrigger'] | undefined; + getTriggerCompatibleActions?: UiActionsStart['getTriggerCompatibleActions']; } export class Embeddable @@ -117,6 +124,36 @@ export class Embeddable this.autoRefreshFetchSubscription = deps.timefilter .getAutoRefreshFetch$() .subscribe(this.reload.bind(this)); + + const input$ = this.getInput$(); + + // Lens embeddable does not re-render when embeddable input changes in + // general, to improve performance. This line makes sure the Lens embeddable + // re-renders when anything in ".dynamicActions" (e.g. drilldowns) changes. + input$ + .pipe( + map((input) => input.enhancements?.dynamicActions), + distinctUntilChanged((a, b) => isEqual(a, b)), + skip(1) + ) + .subscribe((input) => { + this.reload(); + }); + + // Lens embeddable does not re-render when embeddable input changes in + // general, to improve performance. This line makes sure the Lens embeddable + // re-renders when dashboard view mode switches between "view/edit". This is + // needed to see the changes to ".dynamicActions" (e.g. drilldowns) when + // dashboard's mode is toggled. + input$ + .pipe( + map((input) => input.viewMode), + distinctUntilChanged(), + skip(1) + ) + .subscribe((input) => { + this.reload(); + }); } public supportedTriggers() { @@ -127,6 +164,7 @@ export class Embeddable case 'lnsXY': return [VIS_EVENT_TO_TRIGGER.filter, VIS_EVENT_TO_TRIGGER.brush]; case 'lnsDatatable': + return [VIS_EVENT_TO_TRIGGER.filter, VIS_EVENT_TO_TRIGGER.tableRowContextMenuClick]; case 'lnsPie': return [VIS_EVENT_TO_TRIGGER.filter]; case 'lnsMetric': @@ -217,11 +255,31 @@ export class Embeddable handleEvent={this.handleEvent} onData$={this.updateActiveData} renderMode={input.renderMode} + hasCompatibleActions={this.hasCompatibleActions} />, domNode ); } + private readonly hasCompatibleActions = async ( + event: ExpressionRendererEvent + ): Promise => { + if (isLensTableRowContextMenuClickEvent(event)) { + const { getTriggerCompatibleActions } = this.deps; + if (!getTriggerCompatibleActions) { + return false; + } + const actions = await getTriggerCompatibleActions(VIS_EVENT_TO_TRIGGER[event.name], { + data: event.data, + embeddable: this, + }); + + return actions.length > 0; + } + + return false; + }; + /** * Combines the embeddable context with the saved object context, and replaces * any references to index patterns @@ -264,6 +322,16 @@ export class Embeddable embeddable: this, }); } + + if (isLensTableRowContextMenuClickEvent(event)) { + this.deps.getTrigger(VIS_EVENT_TO_TRIGGER[event.name]).exec( + { + data: event.data, + embeddable: this, + }, + true + ); + } }; async reload() { diff --git a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable_factory.ts b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable_factory.ts index 65e9c22d24eaf..175ec0dbcfd54 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable_factory.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable_factory.ts @@ -94,6 +94,7 @@ export class EmbeddableFactory implements EmbeddableFactoryDefinition { editable: await this.isEditable(), basePath: coreHttp.basePath, getTrigger: uiActions?.getTrigger, + getTriggerCompatibleActions: uiActions?.getTriggerCompatibleActions, documentToExpression, }, input, diff --git a/x-pack/plugins/lens/public/editor_frame_service/embeddable/expression_wrapper.tsx b/x-pack/plugins/lens/public/editor_frame_service/embeddable/expression_wrapper.tsx index 4645420898314..2fc1cfee82fd3 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/embeddable/expression_wrapper.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/embeddable/expression_wrapper.tsx @@ -11,6 +11,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiText, EuiIcon } from '@elastic/eui'; import { ExpressionRendererEvent, ReactExpressionRendererType, + ReactExpressionRendererProps, } from 'src/plugins/expressions/public'; import { ExecutionContextSearch } from 'src/plugins/data/public'; import { RenderMode } from 'src/plugins/expressions'; @@ -26,6 +27,7 @@ export interface ExpressionWrapperProps { handleEvent: (event: ExpressionRendererEvent) => void; onData$: (data: unknown, inspectorAdapters?: LensInspectorAdapters | undefined) => void; renderMode?: RenderMode; + hasCompatibleActions?: ReactExpressionRendererProps['hasCompatibleActions']; } export function ExpressionWrapper({ @@ -37,6 +39,7 @@ export function ExpressionWrapper({ searchSessionId, onData$, renderMode, + hasCompatibleActions, }: ExpressionWrapperProps) { return ( @@ -80,6 +83,7 @@ export function ExpressionWrapper({ )} onEvent={handleEvent} + hasCompatibleActions={hasCompatibleActions} /> )} diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index b0da6cf2e8434..23d026bf2b443 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -8,6 +8,7 @@ import { IconType } from '@elastic/eui/src/components/icon/icon'; import { CoreSetup } from 'kibana/public'; import { PaletteOutput, PaletteRegistry } from 'src/plugins/charts/public'; import { SavedObjectReference } from 'kibana/public'; +import { ROW_CLICK_TRIGGER } from '../../../../src/plugins/ui_actions/public'; import { ExpressionAstExpression, ExpressionRendererEvent, @@ -614,11 +615,17 @@ export interface LensFilterEvent { name: 'filter'; data: TriggerContext['data']; } + export interface LensBrushEvent { name: 'brush'; data: TriggerContext['data']; } +export interface LensTableRowContextMenuEvent { + name: 'tableRowContextMenuClick'; + data: TriggerContext['data']; +} + export function isLensFilterEvent(event: ExpressionRendererEvent): event is LensFilterEvent { return event.name === 'filter'; } @@ -627,11 +634,17 @@ export function isLensBrushEvent(event: ExpressionRendererEvent): event is LensB return event.name === 'brush'; } +export function isLensTableRowContextMenuClickEvent( + event: ExpressionRendererEvent +): event is LensBrushEvent { + return event.name === 'tableRowContextMenuClick'; +} + /** * Expression renderer handlers specifically for lens renderers. This is a narrowed down * version of the general render handlers, specifying supported event types. If this type is * used, dispatched events will be handled correctly. */ export interface ILensInterpreterRenderHandlers extends IInterpreterRenderHandlers { - event: (event: LensFilterEvent | LensBrushEvent) => void; + event: (event: LensFilterEvent | LensBrushEvent | LensTableRowContextMenuEvent) => void; } diff --git a/x-pack/plugins/ui_actions_enhanced/public/drilldowns/url_drilldown/components/url_drilldown_collect_config/test_samples/demo.tsx b/x-pack/plugins/ui_actions_enhanced/public/drilldowns/url_drilldown/components/url_drilldown_collect_config/test_samples/demo.tsx index e6c9797623e9f..1b975da0b369d 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/drilldowns/url_drilldown/components/url_drilldown_collect_config/test_samples/demo.tsx +++ b/x-pack/plugins/ui_actions_enhanced/public/drilldowns/url_drilldown/components/url_drilldown_collect_config/test_samples/demo.tsx @@ -5,7 +5,7 @@ */ import React from 'react'; -import { UrlDrilldownConfig, UrlDrilldownScope } from '../../../types'; +import { UrlDrilldownConfig } from '../../../types'; import { UrlDrilldownCollectConfig } from '../url_drilldown_collect_config'; export const Demo = () => { @@ -14,33 +14,13 @@ export const Demo = () => { url: { template: '' }, }); - const fakeScope: UrlDrilldownScope = { - kibanaUrl: 'http://localhost:5601/', - context: { - filters: [ - { - query: { match: { extension: { query: 'jpg', type: 'phrase' } } }, - meta: { index: 'logstash-*', negate: false, disabled: false, alias: null }, - }, - { - query: { match: { '@tags': { query: 'info', type: 'phrase' } } }, - meta: { index: 'logstash-*', negate: false, disabled: false, alias: null }, - }, - { - query: { match: { _type: { query: 'nginx', type: 'phrase' } } }, - meta: { index: 'logstash-*', negate: false, disabled: false, alias: null }, - }, - ], - }, - event: { - key: 'fakeKey', - value: 'fakeValue', - }, - }; - return ( <> - + {JSON.stringify(config)} ); diff --git a/x-pack/plugins/ui_actions_enhanced/public/drilldowns/url_drilldown/components/url_drilldown_collect_config/url_drilldown_collect_config.test.tsx b/x-pack/plugins/ui_actions_enhanced/public/drilldowns/url_drilldown/components/url_drilldown_collect_config/url_drilldown_collect_config.test.tsx deleted file mode 100644 index a6fcd77d75040..0000000000000 --- a/x-pack/plugins/ui_actions_enhanced/public/drilldowns/url_drilldown/components/url_drilldown_collect_config/url_drilldown_collect_config.test.tsx +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { Demo } from './test_samples/demo'; -import { fireEvent, render } from '@testing-library/react'; -import React from 'react'; - -jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({ - htmlIdGenerator: () => () => `id-${Math.random()}`, -})); - -test('configure valid URL template', () => { - const screen = render(); - - const urlTemplate = 'https://elastic.co/?{{event.key}}={{event.value}}'; - fireEvent.change(screen.getByLabelText(/Enter URL template/i), { - target: { value: urlTemplate }, - }); - - const preview = screen.getByLabelText(/URL preview/i) as HTMLTextAreaElement; - expect(preview.value).toMatchInlineSnapshot(`"https://elastic.co/?fakeKey=fakeValue"`); - expect(preview.disabled).toEqual(true); - const previewLink = screen.getByText('Preview') as HTMLAnchorElement; - expect(previewLink.href).toMatchInlineSnapshot(`"https://elastic.co/?fakeKey=fakeValue"`); - expect(previewLink.target).toMatchInlineSnapshot(`"_blank"`); -}); - -test('configure invalid URL template', () => { - const screen = render(); - - const urlTemplate = 'https://elastic.co/?{{event.wrongKey}}={{event.wrongValue}}'; - fireEvent.change(screen.getByLabelText(/Enter URL template/i), { - target: { value: urlTemplate }, - }); - - const previewTextArea = screen.getByLabelText(/URL preview/i) as HTMLTextAreaElement; - expect(previewTextArea.disabled).toEqual(true); - expect(previewTextArea.value).toEqual(urlTemplate); - expect(screen.getByText(/invalid format/i)).toBeInTheDocument(); // check that error is shown - - const previewLink = screen.getByText('Preview') as HTMLAnchorElement; - expect(previewLink.href).toEqual(urlTemplate); - expect(previewLink.target).toMatchInlineSnapshot(`"_blank"`); -}); diff --git a/x-pack/plugins/ui_actions_enhanced/public/drilldowns/url_drilldown/components/url_drilldown_collect_config/url_drilldown_collect_config.tsx b/x-pack/plugins/ui_actions_enhanced/public/drilldowns/url_drilldown/components/url_drilldown_collect_config/url_drilldown_collect_config.tsx index 3251e85841d86..eb8d01afbf420 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/drilldowns/url_drilldown/components/url_drilldown_collect_config/url_drilldown_collect_config.tsx +++ b/x-pack/plugins/ui_actions_enhanced/public/drilldowns/url_drilldown/components/url_drilldown_collect_config/url_drilldown_collect_config.tsx @@ -18,52 +18,40 @@ import { EuiTextArea, EuiSelectableOption, } from '@elastic/eui'; -import { UrlDrilldownConfig, UrlDrilldownScope } from '../../types'; -import { compile } from '../../url_template'; -import { validateUrlTemplate } from '../../url_validation'; -import { buildScopeSuggestions } from '../../url_drilldown_scope'; +import { UrlDrilldownConfig } from '../../types'; import './index.scss'; import { txtAddVariableButtonTitle, - txtUrlPreviewHelpText, txtUrlTemplateSyntaxHelpLinkText, txtUrlTemplateVariablesHelpLinkText, txtUrlTemplateVariablesFilterPlaceholderText, txtUrlTemplateLabel, txtUrlTemplateOpenInNewTab, txtUrlTemplatePlaceholder, - txtUrlTemplatePreviewLabel, - txtUrlTemplatePreviewLinkText, } from './i18n'; export interface UrlDrilldownCollectConfig { config: UrlDrilldownConfig; + variables: string[]; onConfig: (newConfig: UrlDrilldownConfig) => void; - scope: UrlDrilldownScope; syntaxHelpDocsLink?: string; variablesHelpDocsLink?: string; } export const UrlDrilldownCollectConfig: React.FC = ({ config, + variables, onConfig, - scope, syntaxHelpDocsLink, variablesHelpDocsLink, }) => { const textAreaRef = useRef(null); + const [showUrlError, setShowUrlError] = React.useState(false); const urlTemplate = config.url.template ?? ''; - const compiledUrl = React.useMemo(() => { - try { - return compile(urlTemplate, scope); - } catch { - return urlTemplate; - } - }, [urlTemplate, scope]); - const scopeVariables = React.useMemo(() => buildScopeSuggestions(scope), [scope]); function updateUrlTemplate(newUrlTemplate: string) { if (config.url.template !== newUrlTemplate) { + setShowUrlError(true); onConfig({ ...config, url: { @@ -73,18 +61,31 @@ export const UrlDrilldownCollectConfig: React.FC = ({ }); } } - const { error, isValid } = React.useMemo( - () => validateUrlTemplate({ template: urlTemplate }, scope), - [urlTemplate, scope] - ); const isEmpty = !urlTemplate; - const isInvalid = !isValid && !isEmpty; + const isInvalid = showUrlError && isEmpty; + const variablesDropdown = ( + { + if (textAreaRef.current) { + updateUrlTemplate( + urlTemplate.substr(0, textAreaRef.current!.selectionStart) + + `{{${variable}}}` + + urlTemplate.substr(textAreaRef.current!.selectionEnd) + ); + } else { + updateUrlTemplate(urlTemplate + `{{${variable}}}`); + } + }} + /> + ); + return ( <> = ({ ) } - labelAppend={ - { - if (textAreaRef.current) { - updateUrlTemplate( - urlTemplate.substr(0, textAreaRef.current!.selectionStart) + - `{{${variable}}}` + - urlTemplate.substr(textAreaRef.current!.selectionEnd) - ); - } else { - updateUrlTemplate(urlTemplate + `{{${variable}}}`); - } - }} - /> - } + labelAppend={variablesDropdown} > = ({ value={urlTemplate} placeholder={txtUrlTemplatePlaceholder} onChange={(event) => updateUrlTemplate(event.target.value)} + onBlur={() => setShowUrlError(true)} rows={3} inputRef={textAreaRef} /> - - - {txtUrlTemplatePreviewLinkText} - - - } - helpText={txtUrlPreviewHelpText} - > - - { - expect( - buildScopeSuggestions( - buildScope({ - globalScope: { - kibanaUrl: 'http://localhost:5061/', - }, - eventScope: { - key: '__testKey__', - value: '__testValue__', - }, - contextScope: { - filters: [ - { - query: { match: { extension: { query: 'jpg', type: 'phrase' } } }, - meta: { index: 'logstash-*', negate: false, disabled: false, alias: null }, - }, - { - query: { match: { '@tags': { query: 'info', type: 'phrase' } } }, - meta: { index: 'logstash-*', negate: false, disabled: false, alias: null }, - }, - { - query: { match: { _type: { query: 'nginx', type: 'phrase' } } }, - meta: { index: 'logstash-*', negate: false, disabled: false, alias: null }, - }, - ], - query: { - query: '', - language: 'kquery', - }, - }, - }) - ) - ).toMatchInlineSnapshot(` - Array [ - "event.key", - "event.value", - "context.filters", - "context.query.language", - "context.query.query", - "kibanaUrl", - ] - `); -}); diff --git a/x-pack/plugins/ui_actions_enhanced/public/drilldowns/url_drilldown/url_drilldown_scope.ts b/x-pack/plugins/ui_actions_enhanced/public/drilldowns/url_drilldown/url_drilldown_scope.ts deleted file mode 100644 index 74940c4b07077..0000000000000 --- a/x-pack/plugins/ui_actions_enhanced/public/drilldowns/url_drilldown/url_drilldown_scope.ts +++ /dev/null @@ -1,39 +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 { partition } from 'lodash'; -import { getFlattenedObject } from '@kbn/std'; -import { UrlDrilldownGlobalScope, UrlDrilldownScope } from './types'; - -export function buildScope< - ContextScope extends object = object, - EventScope extends object = object ->({ - globalScope, - contextScope, - eventScope, -}: { - globalScope: UrlDrilldownGlobalScope; - contextScope?: ContextScope; - eventScope?: EventScope; -}): UrlDrilldownScope { - return { - ...globalScope, - context: contextScope, - event: eventScope, - }; -} - -/** - * Builds list of variables for suggestion from scope - * keys sorted alphabetically, except {{event.$}} variables are pulled to the top - * @param scope - */ -export function buildScopeSuggestions(scope: UrlDrilldownGlobalScope): string[] { - const allKeys = Object.keys(getFlattenedObject(scope)).sort(); - const [eventKeys, otherKeys] = partition(allKeys, (key) => key.startsWith('event')); - return [...eventKeys, ...otherKeys]; -} From 4f48401b20a7a1daec9fc3f7a68e22036778b06d Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Mon, 14 Dec 2020 13:35:01 +0100 Subject: [PATCH 10/95] unskip tests and make sure submit is not triggered too quickly (#85567) --- .../operations/definitions/shared_components/label_input.tsx | 2 +- x-pack/test/functional/apps/lens/smokescreen.ts | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/shared_components/label_input.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/shared_components/label_input.tsx index ddcb5633b376f..de7a826485831 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/shared_components/label_input.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/shared_components/label_input.tsx @@ -47,7 +47,7 @@ export const LabelInput = ({ inputRef.current = node; } }} - onKeyDown={({ key }: React.KeyboardEvent) => { + onKeyUp={({ key }: React.KeyboardEvent) => { if (keys.ENTER === key && onSubmit) { onSubmit(); } diff --git a/x-pack/test/functional/apps/lens/smokescreen.ts b/x-pack/test/functional/apps/lens/smokescreen.ts index 462b385f27e5d..b91399a4a6756 100644 --- a/x-pack/test/functional/apps/lens/smokescreen.ts +++ b/x-pack/test/functional/apps/lens/smokescreen.ts @@ -13,8 +13,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const listingTable = getService('listingTable'); const testSubjects = getService('testSubjects'); - // FLAKY: https://github.com/elastic/kibana/issues/77969 - describe.skip('lens smokescreen tests', () => { + describe('lens smokescreen tests', () => { it('should allow creation of lens xy chart', async () => { await PageObjects.visualize.navigateToNewVisualization(); await PageObjects.visualize.clickVisType('lens'); From fbb83af63d347086f816bc7c2a1cba62b80ad28c Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Mon, 14 Dec 2020 15:57:28 +0300 Subject: [PATCH 11/95] align cors settings names with elasticsearch (#85738) * align cors settings names with elasticsearch * server.cors.origin: * --> server.cors.origin: ["*"] --- docs/setup/settings.asciidoc | 6 +-- .../__snapshots__/http_config.test.ts.snap | 6 ++- src/core/server/http/http_config.test.ts | 46 +++++++++++++------ src/core/server/http/http_config.ts | 17 ++++--- src/core/server/http/http_tools.test.ts | 6 +-- src/core/server/http/http_tools.ts | 4 +- x-pack/test/functional_cors/config.ts | 4 +- x-pack/test/functional_cors/tests/cors.ts | 4 +- 8 files changed, 57 insertions(+), 36 deletions(-) diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc index 6cd848e963431..8b50fc38167d3 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -453,11 +453,11 @@ deprecation warning at startup. This setting cannot end in a slash (`/`). | `server.cors.enabled:` | experimental[] Set to `true` to allow cross-origin API calls. *Default:* `false` -| `server.cors.credentials:` +| `server.cors.allowCredentials:` | experimental[] Set to `true` to allow browser code to access response body whenever request performed with user credentials. *Default:* `false` -| `server.cors.origin:` - | experimental[] List of origins permitted to access resources. You must specify explicit hostnames and not use `*` for `server.cors.origin` when `server.cors.credentials: true`. *Default:* "*" +| `server.cors.allowOrigin:` + | experimental[] List of origins permitted to access resources. You must specify explicit hostnames and not use `server.cors.allowOrigin: ["*"]` when `server.cors.allowCredentials: true`. *Default:* ["*"] | `server.compression.referrerWhitelist:` | Specifies an array of trusted hostnames, such as the {kib} host, or a reverse diff --git a/src/core/server/http/__snapshots__/http_config.test.ts.snap b/src/core/server/http/__snapshots__/http_config.test.ts.snap index a440c67944fab..9b667f888771e 100644 --- a/src/core/server/http/__snapshots__/http_config.test.ts.snap +++ b/src/core/server/http/__snapshots__/http_config.test.ts.snap @@ -39,9 +39,11 @@ Object { "enabled": true, }, "cors": Object { - "credentials": false, + "allowCredentials": false, + "allowOrigin": Array [ + "*", + ], "enabled": false, - "origin": "*", }, "customResponseHeaders": Object {}, "host": "localhost", diff --git a/src/core/server/http/http_config.test.ts b/src/core/server/http/http_config.test.ts index f893e7783ac8f..b71763e8a2e14 100644 --- a/src/core/server/http/http_config.test.ts +++ b/src/core/server/http/http_config.test.ts @@ -331,51 +331,67 @@ describe('with compression', () => { }); describe('cors', () => { - describe('origin', () => { + describe('allowOrigin', () => { it('list cannot be empty', () => { expect(() => config.schema.validate({ cors: { - origin: [], + allowOrigin: [], }, }) ).toThrowErrorMatchingInlineSnapshot(` - "[cors.origin]: types that failed validation: - - [cors.origin.0]: expected value to equal [*] - - [cors.origin.1]: array size is [0], but cannot be smaller than [1]" - `); + "[cors.allowOrigin]: types that failed validation: + - [cors.allowOrigin.0]: array size is [0], but cannot be smaller than [1] + - [cors.allowOrigin.1]: array size is [0], but cannot be smaller than [1]" + `); }); it('list of valid URLs', () => { - const origin = ['http://127.0.0.1:3000', 'https://elastic.co']; + const allowOrigin = ['http://127.0.0.1:3000', 'https://elastic.co']; expect( config.schema.validate({ - cors: { origin }, - }).cors.origin - ).toStrictEqual(origin); + cors: { allowOrigin }, + }).cors.allowOrigin + ).toStrictEqual(allowOrigin); expect(() => config.schema.validate({ cors: { - origin: ['*://elastic.co/*'], + allowOrigin: ['*://elastic.co/*'], }, }) ).toThrow(); }); it('can be configured as "*" wildcard', () => { - expect(config.schema.validate({ cors: { origin: '*' } }).cors.origin).toBe('*'); + expect(config.schema.validate({ cors: { allowOrigin: ['*'] } }).cors.allowOrigin).toEqual([ + '*', + ]); + }); + + it('cannot mix wildcard "*" with valid URLs', () => { + expect( + () => + config.schema.validate({ cors: { allowOrigin: ['*', 'https://elastic.co'] } }).cors + .allowOrigin + ).toThrowErrorMatchingInlineSnapshot(` + "[cors.allowOrigin]: types that failed validation: + - [cors.allowOrigin.0.0]: expected URI with scheme [http|https]. + - [cors.allowOrigin.1.1]: expected value to equal [*]" + `); }); }); describe('credentials', () => { - it('cannot use wildcard origin if "credentials: true"', () => { + it('cannot use wildcard allowOrigin if "credentials: true"', () => { expect( - () => config.schema.validate({ cors: { credentials: true, origin: '*' } }).cors.origin + () => + config.schema.validate({ cors: { allowCredentials: true, allowOrigin: ['*'] } }).cors + .allowOrigin ).toThrowErrorMatchingInlineSnapshot( `"[cors]: Cannot specify wildcard origin \\"*\\" with \\"credentials: true\\". Please provide a list of allowed origins."` ); expect( - () => config.schema.validate({ cors: { credentials: true } }).cors.origin + () => config.schema.validate({ cors: { allowCredentials: true } }).cors.allowOrigin ).toThrowErrorMatchingInlineSnapshot( `"[cors]: Cannot specify wildcard origin \\"*\\" with \\"credentials: true\\". Please provide a list of allowed origins."` ); diff --git a/src/core/server/http/http_config.ts b/src/core/server/http/http_config.ts index 74cdbfbedeea9..2bd296fe338ab 100644 --- a/src/core/server/http/http_config.ts +++ b/src/core/server/http/http_config.ts @@ -48,17 +48,20 @@ export const config = { cors: schema.object( { enabled: schema.boolean({ defaultValue: false }), - credentials: schema.boolean({ defaultValue: false }), - origin: schema.oneOf( - [schema.literal('*'), schema.arrayOf(hostURISchema, { minSize: 1 })], + allowCredentials: schema.boolean({ defaultValue: false }), + allowOrigin: schema.oneOf( + [ + schema.arrayOf(hostURISchema, { minSize: 1 }), + schema.arrayOf(schema.literal('*'), { minSize: 1, maxSize: 1 }), + ], { - defaultValue: '*', + defaultValue: ['*'], } ), }, { validate(value) { - if (value.credentials === true && value.origin === '*') { + if (value.allowCredentials === true && value.allowOrigin.includes('*')) { return 'Cannot specify wildcard origin "*" with "credentials: true". Please provide a list of allowed origins.'; } }, @@ -168,8 +171,8 @@ export class HttpConfig { public port: number; public cors: { enabled: boolean; - credentials: boolean; - origin: '*' | string[]; + allowCredentials: boolean; + allowOrigin: string[]; }; public customResponseHeaders: Record; public maxPayload: ByteSizeValue; diff --git a/src/core/server/http/http_tools.test.ts b/src/core/server/http/http_tools.test.ts index 4098b631b19d8..962c2107513b5 100644 --- a/src/core/server/http/http_tools.test.ts +++ b/src/core/server/http/http_tools.test.ts @@ -196,8 +196,8 @@ describe('getServerOptions', () => { config.schema.validate({ cors: { enabled: true, - credentials: false, - origin: '*', + allowCredentials: false, + allowOrigin: ['*'], }, }), {} as any, @@ -206,7 +206,7 @@ describe('getServerOptions', () => { expect(getServerOptions(httpConfig).routes?.cors).toEqual({ credentials: false, - origin: '*', + origin: ['*'], headers: ['Accept', 'Authorization', 'Content-Type', 'If-None-Match', 'kbn-xsrf'], }); }); diff --git a/src/core/server/http/http_tools.ts b/src/core/server/http/http_tools.ts index 61688a51345b5..8bec26f31fa26 100644 --- a/src/core/server/http/http_tools.ts +++ b/src/core/server/http/http_tools.ts @@ -39,8 +39,8 @@ const corsAllowedHeaders = ['Accept', 'Authorization', 'Content-Type', 'If-None- export function getServerOptions(config: HttpConfig, { configureTLS = true } = {}) { const cors: RouteOptionsCors | false = config.cors.enabled ? { - credentials: config.cors.credentials, - origin: config.cors.origin, + credentials: config.cors.allowCredentials, + origin: config.cors.allowOrigin, headers: corsAllowedHeaders, } : false; diff --git a/x-pack/test/functional_cors/config.ts b/x-pack/test/functional_cors/config.ts index da03fee476f13..b792aa2d183b6 100644 --- a/x-pack/test/functional_cors/config.ts +++ b/x-pack/test/functional_cors/config.ts @@ -55,8 +55,8 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { `--plugin-path=${corsTestPlugin}`, `--test.cors.port=${pluginPort}`, '--server.cors.enabled=true', - '--server.cors.credentials=true', - `--server.cors.origin=["${originUrl}"]`, + '--server.cors.allowCredentials=true', + `--server.cors.allowOrigin=["${originUrl}"]`, ], }, }; diff --git a/x-pack/test/functional_cors/tests/cors.ts b/x-pack/test/functional_cors/tests/cors.ts index ff5da26b4e275..774ffe1719f07 100644 --- a/x-pack/test/functional_cors/tests/cors.ts +++ b/x-pack/test/functional_cors/tests/cors.ts @@ -15,9 +15,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('CORS', () => { it('Communicates to Kibana with configured CORS', async () => { const args: string[] = config.get('kbnTestServer.serverArgs'); - const originSetting = args.find((str) => str.includes('server.cors.origin')); + const originSetting = args.find((str) => str.includes('server.cors.allowOrigin')); if (!originSetting) { - throw new Error('Cannot find "server.cors.origin" argument'); + throw new Error('Cannot find "server.cors.allowOrigin" argument'); } const [, value] = originSetting.split('='); const url = JSON.parse(value); From ab07a003d4b79bc601234612ea0f5b099263af2f Mon Sep 17 00:00:00 2001 From: ymao1 Date: Mon, 14 Dec 2020 07:58:32 -0500 Subject: [PATCH 12/95] Increasing default api key removalDelay to 1h (#85576) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/plugins/alerts/server/config.test.ts | 2 +- x-pack/plugins/alerts/server/config.ts | 2 +- x-pack/plugins/alerts/server/plugin.test.ts | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/alerts/server/config.test.ts b/x-pack/plugins/alerts/server/config.test.ts index bf3b30b5d2378..e4691ad6229a0 100644 --- a/x-pack/plugins/alerts/server/config.test.ts +++ b/x-pack/plugins/alerts/server/config.test.ts @@ -15,7 +15,7 @@ describe('config validation', () => { }, "invalidateApiKeysTask": Object { "interval": "5m", - "removalDelay": "5m", + "removalDelay": "1h", }, } `); diff --git a/x-pack/plugins/alerts/server/config.ts b/x-pack/plugins/alerts/server/config.ts index 41340c7dfe5fc..e53b99852c354 100644 --- a/x-pack/plugins/alerts/server/config.ts +++ b/x-pack/plugins/alerts/server/config.ts @@ -13,7 +13,7 @@ export const configSchema = schema.object({ }), invalidateApiKeysTask: schema.object({ interval: schema.string({ validate: validateDurationSchema, defaultValue: '5m' }), - removalDelay: schema.string({ validate: validateDurationSchema, defaultValue: '5m' }), + removalDelay: schema.string({ validate: validateDurationSchema, defaultValue: '1h' }), }), }); diff --git a/x-pack/plugins/alerts/server/plugin.test.ts b/x-pack/plugins/alerts/server/plugin.test.ts index fee7901c4ea55..48fd2e12336a8 100644 --- a/x-pack/plugins/alerts/server/plugin.test.ts +++ b/x-pack/plugins/alerts/server/plugin.test.ts @@ -24,7 +24,7 @@ describe('Alerting Plugin', () => { }, invalidateApiKeysTask: { interval: '5m', - removalDelay: '5m', + removalDelay: '1h', }, }); const plugin = new AlertingPlugin(context); @@ -73,7 +73,7 @@ describe('Alerting Plugin', () => { }, invalidateApiKeysTask: { interval: '5m', - removalDelay: '5m', + removalDelay: '1h', }, }); const plugin = new AlertingPlugin(context); @@ -124,7 +124,7 @@ describe('Alerting Plugin', () => { }, invalidateApiKeysTask: { interval: '5m', - removalDelay: '5m', + removalDelay: '1h', }, }); const plugin = new AlertingPlugin(context); From 1c16bcf8512851748ec69b583172ec73029f6238 Mon Sep 17 00:00:00 2001 From: Nathan L Smith Date: Mon, 14 Dec 2020 07:34:54 -0600 Subject: [PATCH 13/95] Fix UX E2E tests (#85722) They look for `.kbnLoadingIndicator` which is no longer there in the new loading indicator design. This changes it to look for an element that does exist and makes it a function in utils. Change not.be.visible to not.exist in places where the element does not exist at in that state. --- .../apm/e2e/cypress/integration/snapshots.js | 2 +- .../step_definitions/csm/breakdown_filter.ts | 8 ++++---- .../csm/client_metrics_helper.ts | 8 ++++---- .../step_definitions/csm/csm_dashboard.ts | 19 +++++++++---------- .../step_definitions/csm/csm_filters.ts | 11 +++++------ .../support/step_definitions/csm/js_errors.ts | 4 ++-- .../step_definitions/csm/percentile_select.ts | 5 ++--- .../csm/service_name_filter.ts | 4 ++-- .../step_definitions/csm/url_search_filter.ts | 14 +++++++------- .../support/step_definitions/csm/utils.ts | 7 +++++-- 10 files changed, 41 insertions(+), 41 deletions(-) diff --git a/x-pack/plugins/apm/e2e/cypress/integration/snapshots.js b/x-pack/plugins/apm/e2e/cypress/integration/snapshots.js index 0ecda7a113de7..152186a8a738a 100644 --- a/x-pack/plugins/apm/e2e/cypress/integration/snapshots.js +++ b/x-pack/plugins/apm/e2e/cypress/integration/snapshots.js @@ -1,3 +1,3 @@ module.exports = { - "__version": "5.4.0" + "__version": "6.0.1" } diff --git a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/breakdown_filter.ts b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/breakdown_filter.ts index 342f3e0aa5267..e558d1ef9c0bc 100644 --- a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/breakdown_filter.ts +++ b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/breakdown_filter.ts @@ -6,13 +6,13 @@ import { Given, When, Then } from 'cypress-cucumber-preprocessor/steps'; import { DEFAULT_TIMEOUT } from './csm_dashboard'; +import { waitForLoadingToFinish } from './utils'; /** The default time in ms to wait for a Cypress command to complete */ Given(`a user clicks the page load breakdown filter`, () => { - // wait for all loading to finish - cy.get('kbnLoadingIndicator').should('not.be.visible'); - cy.get('.euiStat__title-isLoading').should('not.be.visible'); + waitForLoadingToFinish(); + cy.get('.euiStat__title-isLoading').should('not.exist'); const breakDownBtn = cy.get( '[data-test-subj=pldBreakdownFilter]', DEFAULT_TIMEOUT @@ -27,7 +27,7 @@ When(`the user selected the breakdown`, () => { }); Then(`breakdown series should appear in chart`, () => { - cy.get('.euiLoadingChart').should('not.be.visible'); + cy.get('.euiLoadingChart').should('not.exist'); cy.get('[data-cy=pageLoadDist]').within(() => { cy.get('div.echLegendItem__label[title=Chrome] ', DEFAULT_TIMEOUT) diff --git a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/client_metrics_helper.ts b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/client_metrics_helper.ts index 0b26c6de66f4b..d8d8c7c3a62e9 100644 --- a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/client_metrics_helper.ts +++ b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/client_metrics_helper.ts @@ -5,6 +5,7 @@ */ import { DEFAULT_TIMEOUT } from './csm_dashboard'; +import { waitForLoadingToFinish } from './utils'; /** * Verifies the behavior of the client metrics component @@ -17,15 +18,14 @@ export function verifyClientMetrics( ) { const clientMetricsSelector = '[data-cy=client-metrics] .euiStat__title'; - // wait for all loading to finish - cy.get('kbnLoadingIndicator').should('not.be.visible'); + waitForLoadingToFinish(); if (checkTitleStatus) { cy.get('.euiStat__title', DEFAULT_TIMEOUT).should('be.visible'); - cy.get('.euiSelect-isLoading').should('not.be.visible'); + cy.get('.euiSelect-isLoading').should('not.exist'); } - cy.get('.euiStat__title-isLoading').should('not.be.visible'); + cy.get('.euiStat__title-isLoading').should('not.exist'); cy.get(clientMetricsSelector).eq(0).should('have.text', metrics[0]); diff --git a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/csm_dashboard.ts b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/csm_dashboard.ts index 452d8b719b3cb..5207ea39c959f 100644 --- a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/csm_dashboard.ts +++ b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/csm_dashboard.ts @@ -7,6 +7,7 @@ import { Given, Then } from 'cypress-cucumber-preprocessor/steps'; import { loginAndWaitForPage } from '../../../integration/helpers'; import { verifyClientMetrics } from './client_metrics_helper'; +import { waitForLoadingToFinish } from './utils'; /** The default time in ms to wait for a Cypress command to complete */ export const DEFAULT_TIMEOUT = { timeout: 60 * 1000 }; @@ -36,9 +37,9 @@ Then(`should display percentile for page load chart`, () => { cy.get('.euiLoadingChart', DEFAULT_TIMEOUT).should('be.visible'); - // wait for all loading to finish - cy.get('kbnLoadingIndicator').should('not.be.visible'); - cy.get('.euiStat__title-isLoading').should('not.be.visible'); + waitForLoadingToFinish(); + + cy.get('.euiStat__title-isLoading').should('not.exist'); cy.get(pMarkers).eq(0).should('have.text', '50th'); @@ -52,21 +53,19 @@ Then(`should display percentile for page load chart`, () => { Then(`should display chart legend`, () => { const chartLegend = 'div.echLegendItem__label'; - // wait for all loading to finish - cy.get('kbnLoadingIndicator').should('not.be.visible'); - cy.get('.euiLoadingChart').should('not.be.visible'); + waitForLoadingToFinish(); + cy.get('.euiLoadingChart').should('not.exist'); cy.get(chartLegend, DEFAULT_TIMEOUT).eq(0).should('have.text', 'Overall'); }); Then(`should display tooltip on hover`, () => { - cy.get('.euiLoadingChart').should('not.be.visible'); + cy.get('.euiLoadingChart').should('not.exist'); const pMarkers = '[data-cy=percentile-markers] span.euiToolTipAnchor'; - // wait for all loading to finish - cy.get('kbnLoadingIndicator').should('not.be.visible'); - cy.get('.euiLoadingChart').should('not.be.visible'); + waitForLoadingToFinish(); + cy.get('.euiLoadingChart').should('not.exist'); const marker = cy.get(pMarkers, DEFAULT_TIMEOUT).eq(0); marker.invoke('show'); diff --git a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/csm_filters.ts b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/csm_filters.ts index 88287286c66c5..9aeddad686385 100644 --- a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/csm_filters.ts +++ b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/csm_filters.ts @@ -7,11 +7,11 @@ import { When, Then } from 'cypress-cucumber-preprocessor/steps'; import { DEFAULT_TIMEOUT } from './csm_dashboard'; import { verifyClientMetrics } from './client_metrics_helper'; +import { waitForLoadingToFinish } from './utils'; When(/^the user filters by "([^"]*)"$/, (filterName) => { - // wait for all loading to finish - cy.get('kbnLoadingIndicator').should('not.be.visible'); - cy.get('.euiStat__title-isLoading').should('not.be.visible'); + waitForLoadingToFinish(); + cy.get('.euiStat__title-isLoading').should('not.exist'); cy.get(`#local-filter-${filterName}`).click(); cy.get(`#local-filter-popover-${filterName}`, DEFAULT_TIMEOUT).within(() => { @@ -51,9 +51,8 @@ When(/^the user filters by "([^"]*)"$/, (filterName) => { }); Then(/^it filters the client metrics "([^"]*)"$/, (filterName) => { - // wait for all loading to finish - cy.get('kbnLoadingIndicator').should('not.be.visible'); - cy.get('.euiStat__title-isLoading').should('not.be.visible'); + waitForLoadingToFinish(); + cy.get('.euiStat__title-isLoading').should('not.exist'); const data = filterName === 'os' diff --git a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/js_errors.ts b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/js_errors.ts index 9e10e2fd59914..bc53de0bac6a7 100644 --- a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/js_errors.ts +++ b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/js_errors.ts @@ -9,8 +9,8 @@ import { DEFAULT_TIMEOUT } from './csm_dashboard'; import { getDataTestSubj } from './utils'; Then(`it displays list of relevant js errors`, () => { - cy.get('.euiBasicTable-loading').should('not.be.visible'); - cy.get('.euiStat__title-isLoading').should('not.be.visible'); + cy.get('.euiBasicTable-loading').should('not.exist'); + cy.get('.euiStat__title-isLoading').should('not.exist'); getDataTestSubj('uxJsErrorsTotal').should('have.text', 'Total errors112'); diff --git a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/percentile_select.ts b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/percentile_select.ts index 44802bbce6208..80b90422366d5 100644 --- a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/percentile_select.ts +++ b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/percentile_select.ts @@ -6,11 +6,10 @@ import { When, Then } from 'cypress-cucumber-preprocessor/steps'; import { verifyClientMetrics } from './client_metrics_helper'; -import { getDataTestSubj } from './utils'; +import { getDataTestSubj, waitForLoadingToFinish } from './utils'; When('the user changes the selected percentile', () => { - // wait for all loading to finish - cy.get('kbnLoadingIndicator').should('not.be.visible'); + waitForLoadingToFinish(); getDataTestSubj('uxPercentileSelect').select('95'); }); diff --git a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/service_name_filter.ts b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/service_name_filter.ts index 609d0d18f5bc8..5c0e8c6238238 100644 --- a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/service_name_filter.ts +++ b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/service_name_filter.ts @@ -7,10 +7,10 @@ import { When, Then } from 'cypress-cucumber-preprocessor/steps'; import { verifyClientMetrics } from './client_metrics_helper'; import { DEFAULT_TIMEOUT } from './csm_dashboard'; +import { waitForLoadingToFinish } from './utils'; When('the user changes the selected service name', () => { - // wait for all loading to finish - cy.get('kbnLoadingIndicator').should('not.be.visible'); + waitForLoadingToFinish(); cy.get(`[data-cy=serviceNameFilter]`, DEFAULT_TIMEOUT).select('client'); }); diff --git a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/url_search_filter.ts b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/url_search_filter.ts index 3dc98625baf85..cc9dc177d57a0 100644 --- a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/url_search_filter.ts +++ b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/url_search_filter.ts @@ -6,18 +6,18 @@ import { When, Then } from 'cypress-cucumber-preprocessor/steps'; import { DEFAULT_TIMEOUT } from './csm_dashboard'; +import { waitForLoadingToFinish } from './utils'; When(`a user clicks inside url search field`, () => { - // wait for all loading to finish - cy.get('kbnLoadingIndicator').should('not.be.visible'); - cy.get('.euiStat__title-isLoading').should('not.be.visible'); + waitForLoadingToFinish(); + cy.get('.euiStat__title-isLoading').should('not.exist'); cy.get('span[data-cy=csmUrlFilter]', DEFAULT_TIMEOUT).within(() => { cy.get('input.euiFieldSearch').click(); }); }); Then(`it displays top pages in the suggestion popover`, () => { - cy.get('kbnLoadingIndicator').should('not.be.visible'); + waitForLoadingToFinish(); cy.get('div.euiPopover__panel-isOpen', DEFAULT_TIMEOUT).within(() => { const listOfUrls = cy.get('li.euiSelectableListItem'); @@ -38,17 +38,17 @@ Then(`it displays top pages in the suggestion popover`, () => { }); When(`a user enters a query in url search field`, () => { - cy.get('kbnLoadingIndicator').should('not.be.visible'); + waitForLoadingToFinish(); cy.get('[data-cy=csmUrlFilter]').within(() => { cy.get('input.euiSelectableSearch').type('cus'); }); - cy.get('kbnLoadingIndicator').should('not.be.visible'); + waitForLoadingToFinish(); }); Then(`it should filter results based on query`, () => { - cy.get('kbnLoadingIndicator').should('not.be.visible'); + waitForLoadingToFinish(); cy.get('div.euiPopover__panel-isOpen', DEFAULT_TIMEOUT).within(() => { const listOfUrls = cy.get('li.euiSelectableListItem'); diff --git a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/utils.ts b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/utils.ts index 87b3a1d70d073..0819a27ff16cb 100644 --- a/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/utils.ts +++ b/x-pack/plugins/apm/e2e/cypress/support/step_definitions/csm/utils.ts @@ -6,9 +6,12 @@ import { DEFAULT_TIMEOUT } from './csm_dashboard'; +export function waitForLoadingToFinish() { + cy.get('[data-test-subj=globalLoadingIndicator-hidden]', DEFAULT_TIMEOUT); +} + export function getDataTestSubj(dataTestSubj: string) { - // wait for all loading to finish - cy.get('kbnLoadingIndicator').should('not.be.visible'); + waitForLoadingToFinish(); return cy.get(`[data-test-subj=${dataTestSubj}]`, DEFAULT_TIMEOUT); } From a06b8d1a7b943e186877d04f25036574c14cf05a Mon Sep 17 00:00:00 2001 From: Shahzad Date: Mon, 14 Dec 2020 15:01:05 +0100 Subject: [PATCH 14/95] [Uptime] Log es queries in kibana logs on debug flag (#85387) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../plugins/uptime/public/state/api/utils.ts | 10 +- x-pack/plugins/uptime/server/lib/lib.ts | 109 ++++++++++++++---- .../uptime/server/rest_api/certs/certs.ts | 11 +- .../server/rest_api/create_route_with_auth.ts | 17 ++- .../server/rest_api/dynamic_settings.ts | 10 +- .../rest_api/index_state/get_index_pattern.ts | 16 +-- .../rest_api/index_state/get_index_status.ts | 21 ++-- .../server/rest_api/monitors/monitor_list.ts | 65 +++++------ .../rest_api/monitors/monitor_locations.ts | 17 ++- .../rest_api/monitors/monitor_status.ts | 12 +- .../rest_api/monitors/monitors_details.ts | 19 ++- .../rest_api/monitors/monitors_durations.ts | 17 ++- .../overview_filters/get_overview_filters.ts | 7 +- .../rest_api/pings/get_ping_histogram.ts | 11 +- .../uptime/server/rest_api/pings/get_pings.ts | 11 +- .../rest_api/pings/journey_screenshots.ts | 4 +- .../uptime/server/rest_api/pings/journeys.ts | 25 ++-- .../rest_api/snapshot/get_snapshot_count.ts | 11 +- .../rest_api/telemetry/log_page_view.ts | 14 +-- .../plugins/uptime/server/rest_api/types.ts | 27 ++--- .../server/rest_api/uptime_route_wrapper.ts | 39 ++++++- 21 files changed, 258 insertions(+), 215 deletions(-) diff --git a/x-pack/plugins/uptime/public/state/api/utils.ts b/x-pack/plugins/uptime/public/state/api/utils.ts index 965cbefd13114..54e129c0811c2 100644 --- a/x-pack/plugins/uptime/public/state/api/utils.ts +++ b/x-pack/plugins/uptime/public/state/api/utils.ts @@ -8,6 +8,7 @@ import { PathReporter } from 'io-ts/lib/PathReporter'; import { isRight } from 'fp-ts/lib/Either'; import { HttpFetchQuery, HttpSetup } from 'src/core/public'; import * as t from 'io-ts'; +import { startsWith } from 'lodash'; function isObject(value: unknown) { const type = typeof value; @@ -60,7 +61,14 @@ class ApiService { } public async get(apiUrl: string, params?: HttpFetchQuery, decodeType?: any, asResponse = false) { - const response = await this._http!.fetch({ path: apiUrl, query: params, asResponse }); + const debugEnabled = + sessionStorage.getItem('uptime_debug') === 'true' && startsWith(apiUrl, '/api/uptime'); + + const response = await this._http!.fetch({ + path: apiUrl, + query: { ...params, ...(debugEnabled ? { _debug: true } : {}) }, + asResponse, + }); if (decodeType) { const decoded = decodeType.decode(response); diff --git a/x-pack/plugins/uptime/server/lib/lib.ts b/x-pack/plugins/uptime/server/lib/lib.ts index 39dd868462525..ee84cf4463ceb 100644 --- a/x-pack/plugins/uptime/server/lib/lib.ts +++ b/x-pack/plugins/uptime/server/lib/lib.ts @@ -3,7 +3,8 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { ElasticsearchClient, SavedObjectsClientContract } from 'kibana/server'; +import { ElasticsearchClient, SavedObjectsClientContract, KibanaRequest } from 'kibana/server'; +import chalk from 'chalk'; import { UMBackendFrameworkAdapter } from './adapters'; import { UMLicenseCheck } from './domains'; import { UptimeRequests } from './requests'; @@ -19,53 +20,83 @@ export interface UMServerLibs extends UMDomainLibs { framework: UMBackendFrameworkAdapter; } +interface CountResponse { + body: { + count: number; + _shards: { + total: number; + successful: number; + skipped: number; + failed: number; + }; + }; +} + export type UptimeESClient = ReturnType; export function createUptimeESClient({ esClient, + request, savedObjectsClient, }: { esClient: ElasticsearchClient; + request?: KibanaRequest; savedObjectsClient: SavedObjectsClientContract; }) { + const { _debug = false } = (request?.query as { _debug: boolean }) ?? {}; + return { baseESClient: esClient, async search(params: TParams): Promise<{ body: ESSearchResponse }> { + let res: any; + let esError: any; const dynamicSettings = await savedObjectsAdapter.getUptimeDynamicSettings( savedObjectsClient! ); - let res: any; + const esParams = { index: dynamicSettings!.heartbeatIndices, ...params }; + const startTime = process.hrtime(); + try { - res = await esClient.search({ index: dynamicSettings!.heartbeatIndices, ...params }); + res = await esClient.search(esParams); } catch (e) { - throw e; + esError = e; + } + if (_debug && request) { + debugESCall({ startTime, request, esError, operationName: 'search', params: esParams }); } + + if (esError) { + throw esError; + } + return res; }, - async count( - params: TParams - ): Promise<{ - body: { - count: number; - _shards: { - total: number; - successful: number; - skipped: number; - failed: number; - }; - }; - }> { + async count(params: TParams): Promise { + let res: any; + let esError: any; + const dynamicSettings = await savedObjectsAdapter.getUptimeDynamicSettings( savedObjectsClient! ); - let res: any; + const esParams = { index: dynamicSettings!.heartbeatIndices, ...params }; + const startTime = process.hrtime(); + try { - res = await esClient.count({ index: dynamicSettings!.heartbeatIndices, ...params }); + res = await esClient.count(esParams); } catch (e) { - throw e; + esError = e; } + + if (_debug && request) { + debugESCall({ startTime, request, esError, operationName: 'count', params: esParams }); + } + + if (esError) { + throw esError; + } + return res; }, getSavedObjectsClient() { @@ -73,3 +104,41 @@ export function createUptimeESClient({ }, }; } + +/* eslint-disable no-console */ + +function formatObj(obj: Record) { + return JSON.stringify(obj); +} + +export function debugESCall({ + operationName, + params, + request, + esError, + startTime, +}: { + operationName: string; + params: Record; + request: KibanaRequest; + esError: any; + startTime: [number, number]; +}) { + const highlightColor = esError ? 'bgRed' : 'inverse'; + const diff = process.hrtime(startTime); + const duration = `${Math.round(diff[0] * 1000 + diff[1] / 1e6)}ms`; + const routeInfo = `${request.route.method.toUpperCase()} ${request.route.path}`; + + console.log(chalk.bold[highlightColor](`=== Debug: ${routeInfo} (${duration}) ===`)); + + if (operationName === 'search') { + console.log(`GET ${params.index}/_${operationName}`); + console.log(formatObj(params.body)); + } else { + console.log(chalk.bold('ES operation:'), operationName); + + console.log(chalk.bold('ES query:')); + console.log(formatObj(params)); + } + console.log(`\n`); +} diff --git a/x-pack/plugins/uptime/server/rest_api/certs/certs.ts b/x-pack/plugins/uptime/server/rest_api/certs/certs.ts index d377095a2a370..7af5717d8416d 100644 --- a/x-pack/plugins/uptime/server/rest_api/certs/certs.ts +++ b/x-pack/plugins/uptime/server/rest_api/certs/certs.ts @@ -30,7 +30,7 @@ export const createGetCertsRoute: UMRestApiRouteFactory = (libs: UMServerLibs) = direction: schema.maybe(schema.string()), }), }, - handler: async ({ uptimeEsClient }, _context, request, response): Promise => { + handler: async ({ uptimeEsClient, request }): Promise => { const index = request.query?.index ?? 0; const size = request.query?.size ?? DEFAULT_SIZE; const from = request.query?.from ?? DEFAULT_FROM; @@ -38,7 +38,8 @@ export const createGetCertsRoute: UMRestApiRouteFactory = (libs: UMServerLibs) = const sortBy = request.query?.sortBy ?? DEFAULT_SORT; const direction = request.query?.direction ?? DEFAULT_DIRECTION; const { search } = request.query; - const result = await libs.requests.getCerts({ + + return await libs.requests.getCerts({ uptimeEsClient, index, search, @@ -48,11 +49,5 @@ export const createGetCertsRoute: UMRestApiRouteFactory = (libs: UMServerLibs) = sortBy, direction, }); - return response.ok({ - body: { - certs: result.certs, - total: result.total, - }, - }); }, }); diff --git a/x-pack/plugins/uptime/server/rest_api/create_route_with_auth.ts b/x-pack/plugins/uptime/server/rest_api/create_route_with_auth.ts index 966dc20e27a7e..93593cc315d62 100644 --- a/x-pack/plugins/uptime/server/rest_api/create_route_with_auth.ts +++ b/x-pack/plugins/uptime/server/rest_api/create_route_with_auth.ts @@ -13,10 +13,22 @@ export const createRouteWithAuth = ( ): UptimeRoute => { const restRoute = routeCreator(libs); const { handler, method, path, options, ...rest } = restRoute; - const licenseCheckHandler: UMRouteHandler = async (customParams, context, request, response) => { + const licenseCheckHandler: UMRouteHandler = async ({ + uptimeEsClient, + context, + request, + response, + savedObjectsClient, + }) => { const { statusCode, message } = libs.license(context.licensing.license); if (statusCode === 200) { - return handler(customParams, context, request, response); + return handler({ + uptimeEsClient, + context, + request, + response, + savedObjectsClient, + }); } switch (statusCode) { case 400: @@ -29,6 +41,7 @@ export const createRouteWithAuth = ( return response.internalError(); } }; + return { method, path, diff --git a/x-pack/plugins/uptime/server/rest_api/dynamic_settings.ts b/x-pack/plugins/uptime/server/rest_api/dynamic_settings.ts index f7be9e10d1004..3dd77be6eaf8c 100644 --- a/x-pack/plugins/uptime/server/rest_api/dynamic_settings.ts +++ b/x-pack/plugins/uptime/server/rest_api/dynamic_settings.ts @@ -20,12 +20,8 @@ export const createGetDynamicSettingsRoute: UMRestApiRouteFactory = (libs: UMSer method: 'GET', path: '/api/uptime/dynamic_settings', validate: false, - handler: async ({ savedObjectsClient }, _context, _request, response): Promise => { - const dynamicSettings = await savedObjectsAdapter.getUptimeDynamicSettings(savedObjectsClient); - - return response.ok({ - body: dynamicSettings, - }); + handler: async ({ savedObjectsClient }): Promise => { + return savedObjectsAdapter.getUptimeDynamicSettings(savedObjectsClient); }, }); @@ -60,7 +56,7 @@ export const createPostDynamicSettingsRoute: UMRestApiRouteFactory = (libs: UMSe }), }, writeAccess: true, - handler: async ({ savedObjectsClient }, _context, request, response): Promise => { + handler: async ({ savedObjectsClient, request, response }): Promise => { const decoded = DynamicSettingsType.decode(request.body); const certThresholdErrors = validateCertsValues(request.body as DynamicSettings); diff --git a/x-pack/plugins/uptime/server/rest_api/index_state/get_index_pattern.ts b/x-pack/plugins/uptime/server/rest_api/index_state/get_index_pattern.ts index 5c1be4cdd8143..671c6f0a71aa8 100644 --- a/x-pack/plugins/uptime/server/rest_api/index_state/get_index_pattern.ts +++ b/x-pack/plugins/uptime/server/rest_api/index_state/get_index_pattern.ts @@ -12,17 +12,9 @@ export const createGetIndexPatternRoute: UMRestApiRouteFactory = (libs: UMServer method: 'GET', path: API_URLS.INDEX_PATTERN, validate: false, - handler: async ({ uptimeEsClient }, _context, _request, response): Promise => { - try { - return response.ok({ - body: { - ...(await libs.requests.getIndexPattern({ - uptimeEsClient, - })), - }, - }); - } catch (e) { - return response.internalError({ body: { message: e.message } }); - } + handler: async ({ uptimeEsClient }): Promise => { + return await libs.requests.getIndexPattern({ + uptimeEsClient, + }); }, }); diff --git a/x-pack/plugins/uptime/server/rest_api/index_state/get_index_status.ts b/x-pack/plugins/uptime/server/rest_api/index_state/get_index_status.ts index e57643aed7e36..a347baf770d6a 100644 --- a/x-pack/plugins/uptime/server/rest_api/index_state/get_index_status.ts +++ b/x-pack/plugins/uptime/server/rest_api/index_state/get_index_status.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { schema } from '@kbn/config-schema'; import { UMServerLibs } from '../../lib/lib'; import { UMRestApiRouteFactory } from '../types'; import { API_URLS } from '../../../common/constants'; @@ -11,18 +12,12 @@ import { API_URLS } from '../../../common/constants'; export const createGetIndexStatusRoute: UMRestApiRouteFactory = (libs: UMServerLibs) => ({ method: 'GET', path: API_URLS.INDEX_STATUS, - validate: false, - handler: async ({ uptimeEsClient }, _context, _request, response): Promise => { - try { - return response.ok({ - body: { - ...(await libs.requests.getIndexStatus({ - uptimeEsClient, - })), - }, - }); - } catch (e) { - return response.internalError({ body: { message: e.message } }); - } + validate: { + query: schema.object({ + _debug: schema.maybe(schema.boolean()), + }), + }, + handler: async ({ uptimeEsClient }): Promise => { + return await libs.requests.getIndexStatus({ uptimeEsClient }); }, }); diff --git a/x-pack/plugins/uptime/server/rest_api/monitors/monitor_list.ts b/x-pack/plugins/uptime/server/rest_api/monitors/monitor_list.ts index 50fe616ae9cb5..2c3c649bf68c6 100644 --- a/x-pack/plugins/uptime/server/rest_api/monitors/monitor_list.ts +++ b/x-pack/plugins/uptime/server/rest_api/monitors/monitor_list.ts @@ -19,52 +19,39 @@ export const createMonitorListRoute: UMRestApiRouteFactory = (libs) => ({ pagination: schema.maybe(schema.string()), statusFilter: schema.maybe(schema.string()), pageSize: schema.number(), + _debug: schema.maybe(schema.boolean()), }), }, options: { tags: ['access:uptime-read'], }, - handler: async ({ uptimeEsClient }, _context, request, response): Promise => { - try { - const { - dateRangeStart, - dateRangeEnd, - filters, - pagination, - statusFilter, - pageSize, - } = request.query; + handler: async ({ uptimeEsClient, request }): Promise => { + const { + dateRangeStart, + dateRangeEnd, + filters, + pagination, + statusFilter, + pageSize, + } = request.query; - const decodedPagination = pagination - ? JSON.parse(decodeURIComponent(pagination)) - : CONTEXT_DEFAULTS.CURSOR_PAGINATION; + const decodedPagination = pagination + ? JSON.parse(decodeURIComponent(pagination)) + : CONTEXT_DEFAULTS.CURSOR_PAGINATION; - const { - summaries, - nextPagePagination, - prevPagePagination, - } = await libs.requests.getMonitorStates({ - uptimeEsClient, - dateRangeStart, - dateRangeEnd, - pagination: decodedPagination, - pageSize, - filters, - // this is added to make typescript happy, - // this sort of reassignment used to be further downstream but I've moved it here - // because this code is going to be decomissioned soon - statusFilter: statusFilter || undefined, - }); + const result = await libs.requests.getMonitorStates({ + uptimeEsClient, + dateRangeStart, + dateRangeEnd, + pagination: decodedPagination, + pageSize, + filters, + // this is added to make typescript happy, + // this sort of reassignment used to be further downstream but I've moved it here + // because this code is going to be decomissioned soon + statusFilter: statusFilter || undefined, + }); - return response.ok({ - body: { - summaries, - nextPagePagination, - prevPagePagination, - }, - }); - } catch (e) { - return response.internalError({ body: { message: e.message } }); - } + return result; }, }); diff --git a/x-pack/plugins/uptime/server/rest_api/monitors/monitor_locations.ts b/x-pack/plugins/uptime/server/rest_api/monitors/monitor_locations.ts index e2dbf7114fc91..48a39d94ac69e 100644 --- a/x-pack/plugins/uptime/server/rest_api/monitors/monitor_locations.ts +++ b/x-pack/plugins/uptime/server/rest_api/monitors/monitor_locations.ts @@ -17,20 +17,17 @@ export const createGetMonitorLocationsRoute: UMRestApiRouteFactory = (libs: UMSe monitorId: schema.string(), dateStart: schema.string(), dateEnd: schema.string(), + _debug: schema.maybe(schema.boolean()), }), }, - handler: async ({ uptimeEsClient }, _context, request, response): Promise => { + handler: async ({ uptimeEsClient, request }): Promise => { const { monitorId, dateStart, dateEnd } = request.query; - return response.ok({ - body: { - ...(await libs.requests.getMonitorLocations({ - uptimeEsClient, - monitorId, - dateStart, - dateEnd, - })), - }, + return await libs.requests.getMonitorLocations({ + uptimeEsClient, + monitorId, + dateStart, + dateEnd, }); }, }); diff --git a/x-pack/plugins/uptime/server/rest_api/monitors/monitor_status.ts b/x-pack/plugins/uptime/server/rest_api/monitors/monitor_status.ts index 3d47f0eab8640..94edc6e4d7efe 100644 --- a/x-pack/plugins/uptime/server/rest_api/monitors/monitor_status.ts +++ b/x-pack/plugins/uptime/server/rest_api/monitors/monitor_status.ts @@ -12,26 +12,22 @@ import { API_URLS } from '../../../common/constants'; export const createGetStatusBarRoute: UMRestApiRouteFactory = (libs: UMServerLibs) => ({ method: 'GET', path: API_URLS.MONITOR_STATUS, - validate: { query: schema.object({ monitorId: schema.string(), dateStart: schema.string(), dateEnd: schema.string(), + _debug: schema.maybe(schema.boolean()), }), }, - handler: async ({ uptimeEsClient }, _context, request, response): Promise => { + handler: async ({ uptimeEsClient, request }): Promise => { const { monitorId, dateStart, dateEnd } = request.query; - const result = await libs.requests.getLatestMonitor({ + + return await libs.requests.getLatestMonitor({ uptimeEsClient, monitorId, dateStart, dateEnd, }); - return response.ok({ - body: { - ...result, - }, - }); }, }); diff --git a/x-pack/plugins/uptime/server/rest_api/monitors/monitors_details.ts b/x-pack/plugins/uptime/server/rest_api/monitors/monitors_details.ts index 0982fc1986604..efbdf69a883d6 100644 --- a/x-pack/plugins/uptime/server/rest_api/monitors/monitors_details.ts +++ b/x-pack/plugins/uptime/server/rest_api/monitors/monitors_details.ts @@ -17,23 +17,20 @@ export const createGetMonitorDetailsRoute: UMRestApiRouteFactory = (libs: UMServ monitorId: schema.string(), dateStart: schema.maybe(schema.string()), dateEnd: schema.maybe(schema.string()), + _debug: schema.maybe(schema.boolean()), }), }, - handler: async ({ uptimeEsClient }, context, request, response): Promise => { + handler: async ({ uptimeEsClient, context, request }): Promise => { const { monitorId, dateStart, dateEnd } = request.query; const alertsClient = context.alerting?.getAlertsClient(); - return response.ok({ - body: { - ...(await libs.requests.getMonitorDetails({ - uptimeEsClient, - monitorId, - dateStart, - dateEnd, - alertsClient, - })), - }, + return await libs.requests.getMonitorDetails({ + uptimeEsClient, + monitorId, + dateStart, + dateEnd, + alertsClient, }); }, }); diff --git a/x-pack/plugins/uptime/server/rest_api/monitors/monitors_durations.ts b/x-pack/plugins/uptime/server/rest_api/monitors/monitors_durations.ts index eec3fdf9e7257..09d0ce4555309 100644 --- a/x-pack/plugins/uptime/server/rest_api/monitors/monitors_durations.ts +++ b/x-pack/plugins/uptime/server/rest_api/monitors/monitors_durations.ts @@ -18,20 +18,17 @@ export const createGetMonitorDurationRoute: UMRestApiRouteFactory = (libs: UMSer monitorId: schema.string(), dateStart: schema.string(), dateEnd: schema.string(), + _debug: schema.maybe(schema.boolean()), }), }, - handler: async ({ uptimeEsClient }, _context, request, response): Promise => { + handler: async ({ uptimeEsClient, request }): Promise => { const { monitorId, dateStart, dateEnd } = request.query; - return response.ok({ - body: { - ...(await libs.requests.getMonitorDurationChart({ - uptimeEsClient, - monitorId, - dateStart, - dateEnd, - })), - }, + return await libs.requests.getMonitorDurationChart({ + uptimeEsClient, + monitorId, + dateStart, + dateEnd, }); }, }); diff --git a/x-pack/plugins/uptime/server/rest_api/overview_filters/get_overview_filters.ts b/x-pack/plugins/uptime/server/rest_api/overview_filters/get_overview_filters.ts index 163fbd4f8dd6e..ac58a8002899b 100644 --- a/x-pack/plugins/uptime/server/rest_api/overview_filters/get_overview_filters.ts +++ b/x-pack/plugins/uptime/server/rest_api/overview_filters/get_overview_filters.ts @@ -26,9 +26,10 @@ export const createGetOverviewFilters: UMRestApiRouteFactory = (libs: UMServerLi schemes: arrayOrStringType, ports: arrayOrStringType, tags: arrayOrStringType, + _debug: schema.maybe(schema.boolean()), }), }, - handler: async ({ uptimeEsClient }, _context, request, response) => { + handler: async ({ uptimeEsClient, request, response }): Promise => { const { dateRangeStart, dateRangeEnd, locations, schemes, search, ports, tags } = request.query; let parsedSearch: Record | undefined; @@ -40,7 +41,7 @@ export const createGetOverviewFilters: UMRestApiRouteFactory = (libs: UMServerLi } } - const filtersResponse = await libs.requests.getFilterBar({ + return await libs.requests.getFilterBar({ uptimeEsClient, dateRangeStart, dateRangeEnd, @@ -52,7 +53,5 @@ export const createGetOverviewFilters: UMRestApiRouteFactory = (libs: UMServerLi tags, }), }); - - return response.ok({ body: { ...filtersResponse } }); }, }); diff --git a/x-pack/plugins/uptime/server/rest_api/pings/get_ping_histogram.ts b/x-pack/plugins/uptime/server/rest_api/pings/get_ping_histogram.ts index ba36b171793b7..4797e4aae94bf 100644 --- a/x-pack/plugins/uptime/server/rest_api/pings/get_ping_histogram.ts +++ b/x-pack/plugins/uptime/server/rest_api/pings/get_ping_histogram.ts @@ -19,12 +19,13 @@ export const createGetPingHistogramRoute: UMRestApiRouteFactory = (libs: UMServe monitorId: schema.maybe(schema.string()), filters: schema.maybe(schema.string()), bucketSize: schema.maybe(schema.string()), + _debug: schema.maybe(schema.boolean()), }), }, - handler: async ({ uptimeEsClient }, _context, request, response): Promise => { + handler: async ({ uptimeEsClient, request }): Promise => { const { dateStart, dateEnd, monitorId, filters, bucketSize } = request.query; - const result = await libs.requests.getPingHistogram({ + return await libs.requests.getPingHistogram({ uptimeEsClient, from: dateStart, to: dateEnd, @@ -32,11 +33,5 @@ export const createGetPingHistogramRoute: UMRestApiRouteFactory = (libs: UMServe filters, bucketSize, }); - - return response.ok({ - body: { - ...result, - }, - }); }, }); diff --git a/x-pack/plugins/uptime/server/rest_api/pings/get_pings.ts b/x-pack/plugins/uptime/server/rest_api/pings/get_pings.ts index 2a1a401ec3153..fa1ef565d0146 100644 --- a/x-pack/plugins/uptime/server/rest_api/pings/get_pings.ts +++ b/x-pack/plugins/uptime/server/rest_api/pings/get_pings.ts @@ -22,12 +22,13 @@ export const createGetPingsRoute: UMRestApiRouteFactory = (libs: UMServerLibs) = size: schema.maybe(schema.number()), sort: schema.maybe(schema.string()), status: schema.maybe(schema.string()), + _debug: schema.maybe(schema.boolean()), }), }, - handler: async ({ uptimeEsClient }, _context, request, response): Promise => { + handler: async ({ uptimeEsClient, request, response }): Promise => { const { from, to, index, monitorId, status, sort, size, locations } = request.query; - const result = await libs.requests.getPings({ + return await libs.requests.getPings({ uptimeEsClient, dateRange: { from, to }, index, @@ -37,11 +38,5 @@ export const createGetPingsRoute: UMRestApiRouteFactory = (libs: UMServerLibs) = size, locations: locations ? JSON.parse(locations) : [], }); - - return response.ok({ - body: { - ...result, - }, - }); }, }); diff --git a/x-pack/plugins/uptime/server/rest_api/pings/journey_screenshots.ts b/x-pack/plugins/uptime/server/rest_api/pings/journey_screenshots.ts index 080fc8ab8f8ee..8343b24e601a1 100644 --- a/x-pack/plugins/uptime/server/rest_api/pings/journey_screenshots.ts +++ b/x-pack/plugins/uptime/server/rest_api/pings/journey_screenshots.ts @@ -15,10 +15,12 @@ export const createJourneyScreenshotRoute: UMRestApiRouteFactory = (libs: UMServ params: schema.object({ checkGroup: schema.string(), stepIndex: schema.number(), + _debug: schema.maybe(schema.boolean()), }), }, - handler: async ({ uptimeEsClient }, _context, request, response) => { + handler: async ({ uptimeEsClient, request, response }) => { const { checkGroup, stepIndex } = request.params; + const result = await libs.requests.getJourneyScreenshot({ uptimeEsClient, checkGroup, diff --git a/x-pack/plugins/uptime/server/rest_api/pings/journeys.ts b/x-pack/plugins/uptime/server/rest_api/pings/journeys.ts index f46cf3b990951..8ebd4b4609c75 100644 --- a/x-pack/plugins/uptime/server/rest_api/pings/journeys.ts +++ b/x-pack/plugins/uptime/server/rest_api/pings/journeys.ts @@ -14,21 +14,20 @@ export const createJourneyRoute: UMRestApiRouteFactory = (libs: UMServerLibs) => validate: { params: schema.object({ checkGroup: schema.string(), + _debug: schema.maybe(schema.boolean()), }), }, - handler: async ({ uptimeEsClient }, _context, request, response) => { + handler: async ({ uptimeEsClient, request }): Promise => { const { checkGroup } = request.params; const result = await libs.requests.getJourneySteps({ uptimeEsClient, checkGroup, }); - return response.ok({ - body: { - checkGroup, - steps: result, - }, - }); + return { + checkGroup, + steps: result, + }; }, }); @@ -40,18 +39,16 @@ export const createJourneyFailedStepsRoute: UMRestApiRouteFactory = (libs: UMSer checkGroups: schema.arrayOf(schema.string()), }), }, - handler: async ({ uptimeEsClient }, _context, request, response) => { + handler: async ({ uptimeEsClient, request }): Promise => { const { checkGroups } = request.query; const result = await libs.requests.getJourneyFailedSteps({ uptimeEsClient, checkGroups, }); - return response.ok({ - body: { - checkGroups, - steps: result, - }, - }); + return { + checkGroups, + steps: result, + }; }, }); diff --git a/x-pack/plugins/uptime/server/rest_api/snapshot/get_snapshot_count.ts b/x-pack/plugins/uptime/server/rest_api/snapshot/get_snapshot_count.ts index 224ef87fd90af..2d22259fbf786 100644 --- a/x-pack/plugins/uptime/server/rest_api/snapshot/get_snapshot_count.ts +++ b/x-pack/plugins/uptime/server/rest_api/snapshot/get_snapshot_count.ts @@ -17,20 +17,17 @@ export const createGetSnapshotCount: UMRestApiRouteFactory = (libs: UMServerLibs dateRangeStart: schema.string(), dateRangeEnd: schema.string(), filters: schema.maybe(schema.string()), + _debug: schema.maybe(schema.boolean()), }), }, - handler: async ({ uptimeEsClient }, _context, request, response): Promise => { + handler: async ({ uptimeEsClient, request }): Promise => { const { dateRangeStart, dateRangeEnd, filters } = request.query; - const result = await libs.requests.getSnapshotCount({ + + return await libs.requests.getSnapshotCount({ uptimeEsClient, dateRangeStart, dateRangeEnd, filters, }); - return response.ok({ - body: { - ...result, - }, - }); }, }); diff --git a/x-pack/plugins/uptime/server/rest_api/telemetry/log_page_view.ts b/x-pack/plugins/uptime/server/rest_api/telemetry/log_page_view.ts index 85f274c96cf9a..e69556837af44 100644 --- a/x-pack/plugins/uptime/server/rest_api/telemetry/log_page_view.ts +++ b/x-pack/plugins/uptime/server/rest_api/telemetry/log_page_view.ts @@ -21,14 +21,10 @@ export const createLogPageViewRoute: UMRestApiRouteFactory = () => ({ autoRefreshEnabled: schema.boolean(), autorefreshInterval: schema.number(), refreshTelemetryHistory: schema.maybe(schema.boolean()), + _debug: schema.maybe(schema.boolean()), }), }, - handler: async ( - { savedObjectsClient, uptimeEsClient }, - _context, - request, - response - ): Promise => { + handler: async ({ savedObjectsClient, uptimeEsClient, request }): Promise => { const pageView = request.body as PageViewParams; if (pageView.refreshTelemetryHistory) { KibanaTelemetryAdapter.clearLocalTelemetry(); @@ -37,10 +33,6 @@ export const createLogPageViewRoute: UMRestApiRouteFactory = () => ({ uptimeEsClient, savedObjectsClient ); - const pageViewResult = KibanaTelemetryAdapter.countPageView(pageView as PageViewParams); - - return response.ok({ - body: pageViewResult, - }); + return KibanaTelemetryAdapter.countPageView(pageView as PageViewParams); }, }); diff --git a/x-pack/plugins/uptime/server/rest_api/types.ts b/x-pack/plugins/uptime/server/rest_api/types.ts index df1762a3b318d..4e627cebb3459 100644 --- a/x-pack/plugins/uptime/server/rest_api/types.ts +++ b/x-pack/plugins/uptime/server/rest_api/types.ts @@ -14,7 +14,6 @@ import { KibanaRequest, KibanaResponseFactory, IKibanaResponse, - IScopedClusterClient, } from 'kibana/server'; import { UMServerLibs, UptimeESClient } from '../lib/lib'; @@ -59,20 +58,18 @@ export type UMRestApiRouteFactory = (libs: UMServerLibs) => UptimeRoute; export type UMKibanaRouteWrapper = (uptimeRoute: UptimeRoute) => UMKibanaRoute; /** - * This type can store custom parameters used by the internal Uptime route handlers. + * This is the contract we specify internally for route handling. */ -export interface UMRouteParams { +export type UMRouteHandler = ({ + uptimeEsClient, + context, + request, + response, + savedObjectsClient, +}: { uptimeEsClient: UptimeESClient; - esClient: IScopedClusterClient; + context: RequestHandlerContext; + request: KibanaRequest, Record, Record>; + response: KibanaResponseFactory; savedObjectsClient: SavedObjectsClientContract; -} - -/** - * This is the contract we specify internally for route handling. - */ -export type UMRouteHandler = ( - params: UMRouteParams, - context: RequestHandlerContext, - request: KibanaRequest, Record, Record>, - response: KibanaResponseFactory -) => IKibanaResponse | Promise>; +}) => IKibanaResponse | Promise>; diff --git a/x-pack/plugins/uptime/server/rest_api/uptime_route_wrapper.ts b/x-pack/plugins/uptime/server/rest_api/uptime_route_wrapper.ts index a1cf3c05e2de3..c8769dc4ea3df 100644 --- a/x-pack/plugins/uptime/server/rest_api/uptime_route_wrapper.ts +++ b/x-pack/plugins/uptime/server/rest_api/uptime_route_wrapper.ts @@ -7,6 +7,9 @@ import { UMKibanaRouteWrapper } from './types'; import { createUptimeESClient } from '../lib/lib'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { KibanaResponse } from '../../../../../src/core/server/http/router'; + export const uptimeRouteWrapper: UMKibanaRouteWrapper = (uptimeRoute) => ({ ...uptimeRoute, options: { @@ -17,15 +20,39 @@ export const uptimeRouteWrapper: UMKibanaRouteWrapper = (uptimeRoute) => ({ const { client: savedObjectsClient } = context.core.savedObjects; const uptimeEsClient = createUptimeESClient({ + request, savedObjectsClient, esClient: esClient.asCurrentUser, }); - return uptimeRoute.handler( - { uptimeEsClient, esClient, savedObjectsClient }, - context, - request, - response - ); + try { + const res = await uptimeRoute.handler({ + uptimeEsClient, + savedObjectsClient, + context, + request, + response, + }); + + if (res instanceof KibanaResponse) { + return res; + } + + return response.ok({ + body: { + ...res, + }, + }); + } catch (e) { + // please don't remove this, this will be really helpful during debugging + /* eslint-disable-next-line no-console */ + console.error(e); + + return response.internalError({ + body: { + message: e.message, + }, + }); + } }, }); From d4f4a2c94fc5fc468bd336f7679bc4045df9d77a Mon Sep 17 00:00:00 2001 From: Robert Oskamp Date: Mon, 14 Dec 2020 15:17:25 +0100 Subject: [PATCH 15/95] [ML] API integration tests - security_linux and security_windows modules (#85743) This PR adds API integration tests to create and setup the security_linux and security_windows modules. --- x-pack/test/api_integration/apis/ml/index.ts | 2 + .../apis/ml/modules/recognize_module.ts | 10 + .../apis/ml/modules/setup_module.ts | 128 + .../ml/module_security_endpoint/data.json.gz | Bin 0 -> 1315756 bytes .../ml/module_security_endpoint/mappings.json | 7755 +++++++++++++++++ 5 files changed, 7895 insertions(+) create mode 100644 x-pack/test/functional/es_archives/ml/module_security_endpoint/data.json.gz create mode 100644 x-pack/test/functional/es_archives/ml/module_security_endpoint/mappings.json diff --git a/x-pack/test/api_integration/apis/ml/index.ts b/x-pack/test/api_integration/apis/ml/index.ts index 1bee29daf7b7d..05d682625d8c8 100644 --- a/x-pack/test/api_integration/apis/ml/index.ts +++ b/x-pack/test/api_integration/apis/ml/index.ts @@ -34,6 +34,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { await ml.testResources.deleteIndexPatternByTitle('ft_module_siem_packetbeat'); await ml.testResources.deleteIndexPatternByTitle('ft_module_siem_winlogbeat'); await ml.testResources.deleteIndexPatternByTitle('ft_farequote'); + await ml.testResources.deleteIndexPatternByTitle('ft_logs-endpoint.events.*'); await esArchiver.unload('ml/ecommerce'); await esArchiver.unload('ml/categorization'); @@ -45,6 +46,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { await esArchiver.unload('ml/module_nginx'); await esArchiver.unload('ml/module_sample_ecommerce'); await esArchiver.unload('ml/module_sample_logs'); + await esArchiver.unload('ml/module_security_endpoint'); await esArchiver.unload('ml/module_siem_auditbeat'); await esArchiver.unload('ml/module_siem_packetbeat'); await esArchiver.unload('ml/module_siem_winlogbeat'); diff --git a/x-pack/test/api_integration/apis/ml/modules/recognize_module.ts b/x-pack/test/api_integration/apis/ml/modules/recognize_module.ts index 64f53bbe76c5e..5b70b669aa876 100644 --- a/x-pack/test/api_integration/apis/ml/modules/recognize_module.ts +++ b/x-pack/test/api_integration/apis/ml/modules/recognize_module.ts @@ -135,6 +135,16 @@ export default ({ getService }: FtrProviderContext) => { moduleIds: ['auditbeat_process_hosts_ecs', 'security_linux', 'siem_auditbeat'], }, }, + { + testTitleSuffix: 'for security endpoint dataset', + sourceDataArchive: 'ml/module_security_endpoint', + indexPattern: 'ft_logs-endpoint.events.*', + user: USER.ML_POWERUSER, + expected: { + responseCode: 200, + moduleIds: ['security_linux', 'security_windows'], + }, + }, ]; async function executeRecognizeModuleRequest(indexPattern: string, user: USER, rspCode: number) { diff --git a/x-pack/test/api_integration/apis/ml/modules/setup_module.ts b/x-pack/test/api_integration/apis/ml/modules/setup_module.ts index c86cd8400a71a..d1316a0ededc6 100644 --- a/x-pack/test/api_integration/apis/ml/modules/setup_module.ts +++ b/x-pack/test/api_integration/apis/ml/modules/setup_module.ts @@ -520,6 +520,134 @@ export default ({ getService }: FtrProviderContext) => { ] as string[], }, }, + { + testTitleSuffix: + 'for security_linux with prefix, startDatafeed true and estimateModelMemory true', + sourceDataArchive: 'ml/module_security_endpoint', + indexPattern: { name: 'ft_logs-endpoint.events.*', timeField: '@timestamp' }, + module: 'security_linux', + user: USER.ML_POWERUSER, + requestBody: { + prefix: 'pf15_', + indexPatternName: 'ft_logs-endpoint.events.*', + startDatafeed: true, + end: Date.now(), + }, + expected: { + responseCode: 200, + jobs: [ + { + jobId: 'pf15_v2_rare_process_by_host_linux_ecs', + jobState: JOB_STATE.CLOSED, + datafeedState: DATAFEED_STATE.STOPPED, + modelMemoryLimit: '11mb', + }, + { + jobId: 'pf15_v2_linux_rare_metadata_user', + jobState: JOB_STATE.CLOSED, + datafeedState: DATAFEED_STATE.STOPPED, + modelMemoryLimit: '11mb', + }, + { + jobId: 'pf15_v2_linux_rare_metadata_process', + jobState: JOB_STATE.CLOSED, + datafeedState: DATAFEED_STATE.STOPPED, + modelMemoryLimit: '11mb', + }, + { + jobId: 'pf15_v2_linux_anomalous_user_name_ecs', + jobState: JOB_STATE.CLOSED, + datafeedState: DATAFEED_STATE.STOPPED, + modelMemoryLimit: '11mb', + }, + { + jobId: 'pf15_v2_linux_anomalous_process_all_hosts_ecs', + jobState: JOB_STATE.CLOSED, + datafeedState: DATAFEED_STATE.STOPPED, + modelMemoryLimit: '11mb', + }, + { + jobId: 'pf15_v2_linux_anomalous_network_port_activity_ecs', + jobState: JOB_STATE.CLOSED, + datafeedState: DATAFEED_STATE.STOPPED, + modelMemoryLimit: '11mb', + }, + ], + searches: [] as string[], + visualizations: [] as string[], + dashboards: [] as string[], + }, + }, + { + testTitleSuffix: + 'for security_windows with prefix, startDatafeed true and estimateModelMemory true', + sourceDataArchive: 'ml/module_security_endpoint', + indexPattern: { name: 'ft_logs-endpoint.events.*', timeField: '@timestamp' }, + module: 'security_windows', + user: USER.ML_POWERUSER, + requestBody: { + prefix: 'pf16_', + indexPatternName: 'ft_logs-endpoint.events.*', + startDatafeed: true, + end: Date.now(), + }, + expected: { + responseCode: 200, + jobs: [ + { + jobId: 'pf16_v2_rare_process_by_host_windows_ecs', + jobState: JOB_STATE.CLOSED, + datafeedState: DATAFEED_STATE.STOPPED, + modelMemoryLimit: '11mb', + }, + { + jobId: 'pf16_v2_windows_anomalous_network_activity_ecs', + jobState: JOB_STATE.CLOSED, + datafeedState: DATAFEED_STATE.STOPPED, + modelMemoryLimit: '11mb', + }, + { + jobId: 'pf16_v2_windows_anomalous_path_activity_ecs', + jobState: JOB_STATE.CLOSED, + datafeedState: DATAFEED_STATE.STOPPED, + modelMemoryLimit: '10mb', + }, + { + jobId: 'pf16_v2_windows_anomalous_process_all_hosts_ecs', + jobState: JOB_STATE.CLOSED, + datafeedState: DATAFEED_STATE.STOPPED, + modelMemoryLimit: '11mb', + }, + { + jobId: 'pf16_v2_windows_anomalous_process_creation', + jobState: JOB_STATE.CLOSED, + datafeedState: DATAFEED_STATE.STOPPED, + modelMemoryLimit: '11mb', + }, + { + jobId: 'pf16_v2_windows_anomalous_user_name_ecs', + jobState: JOB_STATE.CLOSED, + datafeedState: DATAFEED_STATE.STOPPED, + modelMemoryLimit: '11mb', + }, + { + jobId: 'pf16_v2_windows_rare_metadata_process', + jobState: JOB_STATE.CLOSED, + datafeedState: DATAFEED_STATE.STOPPED, + modelMemoryLimit: '11mb', + }, + { + jobId: 'pf16_v2_windows_rare_metadata_user', + jobState: JOB_STATE.CLOSED, + datafeedState: DATAFEED_STATE.STOPPED, + modelMemoryLimit: '11mb', + }, + ], + searches: [] as string[], + visualizations: [] as string[], + dashboards: [] as string[], + }, + }, ]; const testDataListNegative = [ diff --git a/x-pack/test/functional/es_archives/ml/module_security_endpoint/data.json.gz b/x-pack/test/functional/es_archives/ml/module_security_endpoint/data.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..f9033c722776a58ca4f290ae285a58ef6f21f245 GIT binary patch literal 1315756 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%0AF$+AH>I(E)lRyh+=-d_ik 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 O_!W9=5s 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%0AWB5R4L>XHS&$jCV%Br5e9biTHW06zaEc!?3 z7mfi@V@4MOSsnFFx9eDfKOheNTzKXE0ZB!<#N1%iFbEH$kf;W-!L_L`K4fMJ%h8r2T$btSnex z%rPB1EpFCh@h>V{K#UcaXs7Nw{E`iLCsh~cHbkhPe#YLKcax9kswnpkjIb+4UWJWsVQ7c| z;gqjyU_(klYQun+6mdD3l?GZY?CIu206{D1q@zo*i1>fVR{&oU4dLtkX(C30o5G#G^Su;}8*#_`s^# zN=tOyAZCO*5Jda|kPBjolC%via|CJ7v$=ibj>c~A`Cqa1{ z{7=)U+eunnzV8fuGdcE^BO=35@_+bZbb)exe*LU*yMC;Ubd-+6_e~ABH_uR3)$TD$?ju6P%DL{OfN0`8@wJM}O=|5T4b+ zfP?Aou})$d21e`#`0p(P{P*qXa;U3j(05$8HGb&F9N$+!H(PSDS!gJA*{VXCiJl{K z6h3PQoVJR*DrzcK(S}L9ToonL)*Uzo{iXkI$IrR$VC|HMKa{3lJ!ps$AqSU8JU5j^Cd|%ECaj+!!$5 z82&|c9XDdX+#G|$*p_50KInC7AN+s%sY!)8=?y4+{BACT3s3G+IZW(@2fzeuVwKMa zxrC%SU_|+@QJO}*^d?t9fWA1jQC|m*`l8uJouaLKAkIvSy0bynGHYokqm3w22(GAWA(><=L1{XhUKP9!vZf9(?$pqn2c*DcmB|c7WM`Exq|Rd@2SWv=e<4L`D<&VL z-gyM@@_b718mF0>pn2ZoR7{FTH3Y}Oc*T_sD+%FBnCoun3#_vOr-)0_6^negz{a*c z!wx!WUF!SkosW^A^i4k8LMR0Y^Z(ihC2x+=T+$hmeewxohbD!otHN@d5TUDo|4?et z9EuMz#aOF=g71LbfCS{S;^9r1Omkj46}a!zCbJ#SpbL-6yO95Xv)~&)-8= zx;@2HL?ucZKk*@$kr~OOXKOFc!(So+gxh5^TmgtJ)W4$i3n_7FA!<~SiN>s_saU`W z(Q#K9W*|ukvT?7m!B2U6)oPe^`g8 zFQGh#z+_WCVwM+{C;=zSz^Zlu#L!X!TbQ?D@s&F-`!cd%O7k>`25Bo#ghvK8e?H}w z0Q_x>0c})*&}nvoLtc$Ry;N{qfF&r-+R5_gh1&l?VL2pOteHreGUE`&iEIrr9=p?H zV-B0-8lb@=Dxv0FpLhkqo6z1>^G)5Ej|&NhN&yFozN0=C>h9P+cD}Ye&zNAlv!H+c zzvMf4*@iRD2J%7y>~`wIl8(!l^Qx}|YXLT5s5Gu5_DX?CYusn{pb9;<2Mp zGF>tDa8h7MQosg!1?@K%&1&mN_K@>>z~vBh3f(Z9povnz(n8;yvATtM8{VM+@W7UJ zTJ{k5BmT4y$hr#1itwCx+kFWG3n}Szh!e8bTpPS80xJK&tv+v9M1Rs=8Ag%}X@Pyfy>uC#Q zhEKacVI=GLaYSpz_g0H2EaK89j+t}-3^A9l3*g(krV&B(XjBUamd6C6Kc5-QH~A|5 zf`PW{y{M~DmQtd$z#F`!BMF<1(bt+tQQqysM3WMsT|TMbR*=43rH3&@0RYaPeSg#B z5)pPZ?b>(;wCefk1GVQpNiaGK@p}#yd{!$j5k`CZa5`4ZUW-@qFRVWm^n6=V@5b#1 z!?Y<@c54$ zh9cc3CaKF$(sNQ0i`FF74%RQLs1TZ#TiPrx-L3KcsQkedv{^R|WnHeu3!DqhyiQ3l z2gUI%JV#M}$}T>qd!&ma{avy6%NgCLdy=|SL0553{5jBlDkvQUcf(b5?uNQ@U16uL z73YDTGl63N=cP)z`m9mCBkGZ}&N39sb`^aIiR#}SL0#u$_3QF;bs5nGNNU2Q629Qww9P7wb=v~&~qYU>( zB5v!D2dkV%^b)u83eB7Ybvq~hvB)+8q%q+RvmPvK2p;K=@rKNz0F+r&lQfwrs1O~~ zD;-VauNbsAZ`HvYB5x7|w~t+?u4*FNg1CIEKFcM~O}I!;Q+vS$P=P*fio{F5$=Qrh--9o5-DE##cICI17LyI*%&>H_2(vtUwh+4mo6mN*Z~ zF(dIMP3V-@${`2cy?x?^D?iAI#EBx)%OFy0*sqqcg!@ZBzGIX~j=bf-@4R(umhxsvAdI)dOv$b7e=Z|v|6S5`Gy*+ z*3fEqaiHCm?^q>a`0a6Of<0!xnP96Inl*Yx5<>1IXf~+OrEGo9XV0D|z&AnlsDv%* z8~OT$=8+}G=b9Z_T#@L00FNhzJ)UK`Y}0MI8y!}k z&?-fF3BMg9m0EAsZ1MhkV&W7MD7TM#fr~7$Pr-i1xEoFQ-lo2Me1hC=tmC&@86iOaldqC&FGV`VULk&&Xtw<4t$cyL(sMUfu$sl1bISB|`aUNL4dI)WT1z588UE^TcWu36V{KI}>)f<0se3O@_x2 zq2o51&}1g@n%v56dOV6#Eu#%OZ=PtOyrGIg}h@ znDGw(;L?kj!h$;J2D`r=*=$H^V9ef8cYBxIzf1s?Q&rw(~XRth7J^)7T`up};S7QLcI+X7YNv4M7ur`!IK>|y- z%2T}V9e{FFC8!ucA8Z;wL!#z`byDlhzx%-ObC4r7!WhicHpaFWEPmuF_cIDHu4>-Nnvz=s0+K6~aG8dxV5 zrf1(H3^EPofptpvP$-8OJpnASV%D6(uy_FaPuU7sG6DlOf0Dy#81`Vg6DxJMlOFQ` zQ0GfDcwMz)!(Uy>0fPkC$Y};jY`Q^>MroSuXisn0(G9b63@~3C$y(yCf1!~SoX0q4 zL%YZKnBodFLxipXTryB1Qk?9a`M_{-;#V%3@o(Doa*qiowHl1-KVO3hgHQj|RmN@c zt7h@l1~PV}V*!3RF$fdjiQ< zO?)_q5l@B3%P;i7(fG^ZAyF%}+3kiH?aqYnI1d<5-%9t!GEUp zImU-*5_RWXw9$r{r^82Elk*me&CzW^FjM!jWi*f~9=84d{IU5KFxmEkfe-g`n)aC; zgiR6mbq13dhAA{vHktTqXY1y2`VA)L>?3ORRSjxCEu!O?!*G>(wcX7Rp2JAgdQx?WY zx^8*3>T_1F=s?prtNuthWe zK6`g+(7*2o-C++;`fUB#hg+Qrp|Nx=%KqcV;y>HJ4lAHYRc0(wbyQaTqL=fo2Tadg zbVgOYx}JbL&0|`3n%K&NBfPv*fmAnW`ILNjKRp5|{+hD-r(X(#)@k)MXgCq6V(nbZTlN=v!b@YMEY_usk)h|=D&qRp#5`C2h{xqpS zd_rdZypMO1@+S@j>qwmVFS~8}~?ZOt&dtc1*o7 zOVX2)OGK9H{*J#Xz*!?H}W{ssY_?(11?>nL+-eInU+~{vDr5GxKLr$DUEpo^~ zkJo5pXwfk9cq)rfi!!(^7jvDVRPdzCu+Ap41YLP4^pltj zn#i5RsFSpdr^H+c_DLgx)Wt)SjPV+yd+vms`i-8=jP^9jkE8BOW}UBbsKMNK3_i${ zN{N1xA&*Dzn2hSu_Yh#6Br8+*dg+Wm&2gzE&Vxt9?r~zpyTwR@u^Dageh%6@BTu#k zU^Jib52Zcv*E78PHr+*M?MV>~o511$An`Xj$tA7SPRDS@SxC}|ss7ew;|1F@{o=T$ zAp$&MFOFbxv#^LC^O>hO3wd&LDNP(wGZP$V?7hIArZBJj&K0QJ{~1%j9&NHP3NMlZ zKJHp`9?iiit^dw!{Yz)SK#Y}3hz-*+&&gH$|9)QY_6+0e6yUxh{ z<`|Rfok{yWAFD!1`z6q){i?`7NcWqfs0&9!fhL6!`F&a|tljt)=jvE1D9!Cl(5-sc zrLG)heqeckFYmwYQrogCokPxIUW*zac{JtQuk^FrbA1}wOo=(|(tSyNiHN_yz4udCZ!XQo8$UY7sviJt^-L0NTV^xsaW($}T8wD?Zg} z9k#tgVQ@lNh||hunG97*Z&J$#{^qkju8^V!T%DGB1Dc650Mt%6!ofQ*R%@dp7$O!Y zp;TG{H7C?6X#_L|pJk(UhU(<#_1{;$p@`^m4O~It>YPPBE^2~UR}1k)()sX+~d2Fm%UtC5C2G$w)BcjHxR$Sf`J1jOQI)W(snG zO|9?3VZfgPr)vBgvhT`d8|_S_txyn)%gn;Kzj*tsZ8=CIn$7<;ohNh@uP5@#DPvp< zF$Sp3i7qe(4AbVg5`0t#eA-F*?^*XO z6=JEjtf%E#!L8DoM1^+y4dDbI7&Atwerfz)J!StDFuS}0HRlAvi=?I1b#Zg9c zCzx^)Q(sMvlm3ta=|aXM|}YSsdL>E8>T~ z@q7g+MQQ*1Q>e7X50?B9U4;UvMJe%6ct~e+*o86cpnrZ3E!b_^7L0(XYCGaLf1cf= zX8sK|cl{4DQU|bbHaaJzY{)@9l_ej_0`t!~f<)!yURIhi_qS?0LG)Rqj&v~tspVFA z;vG;%^&HztV}%7n$#2D3#?_XLZ^^-yQ(%%i!*$FD=hKf%q7~gRTizo;9iTC;rBDYN zN)>?j4-P^N$c=4S0OD2GZU6b-JmM(Hrw41QXJP}u`qcbbpL{qc!@3EQcf#?);qvnh z_|8w`JEdV`+mcq@VI0lI=mGV$KV(&j8R{_MR7AW8)ZHB_G&l^Qv~+wZuFuIHHM&i+ z&;X2Zdu7MusL01dSUGwLKJ1A%mb9_BhCopD`Q%66enIzB0Eg#0f1Z4BC88RG?pzmM zH295q{=A1$pR;+@G*m#cYUO0J^#JdCr#ZtRaA)`YU{tLnJTt>o6;k1b-fpd%=Rc$g6q$F0oQ-SZtTm7{Rwpp1S^8i6-2 zrr4m$n;teoMAfpJv_w^07?)hf&3lLjJQvw%E(S^2tY&Y2`Q)#N5p71TEM@j}wp4TY z`^@YMd9!zc1eeax1!$@Si2iM2ANY>6mQ>&h;zF8@voUQJbkFm#t)$YPfa?vFr@C?? z)RMzmMFj7&o_+wGu?dOrz4|eZKh>cPq!ErHV)1k0aWUtUCIa}dQAT^)Y)g5hRWz-N zD*42H=*Q|{JX{MK=qHEJG?@&}DhONDWx?d345PiQZ=c`)0mdNJ78t@4xV%F!J+ux` zJ{VPPMMeZ9Q!T%{3uCH0#({_UYf`XLcg!6CeJ_J(hIZqBUK8OehH_S@(PdIBLFQ`L z;8Zz3=grHII9Be2xkG5bcRK2HRmBse8C7E%Vso(pt5_kI5jl#gO&Qr?`$DAUir;dW zH#r?6;?TXqfQY@YaG*rSiZ(%rg)2=TTr4oZ2)_wHmG@qtR5N_(d&tXa;*8*M5k4es z&fF47GbVc4mr-bdm^MQJVMZlT@GxpKWN!kU#Tr3!gH&mTur*+qP2XKV29}&Ae^H>5 zE{RpUCV(Q>WrJXa*^6eGIx3jP5qY8)iP{x;}xXwI;|Wsa(K5z4@cq^lshur zlbpW(99iS~R4BHVC3vo(7{xy2c>bcJ+3hlKr)5uC=6di2UiN{KTGqnx0j3J~o64(V zg|K8cv|MC8ogwKbud;;-DX_p-fCcu`!2;r%8?Hztzo1EGTQwj%9_MnP)v1*}nT-I+ zY-M$-=V$xo@7a(?SF&dVlbHbvx!D+s!UXw2;s)yUYshtD3(SVT4IefOt4{E|(+TIc z?RdUwikOwB!xlZKIGWK~DrbXW4Yhjgt99M!W zniz6TF$;dob$FVtu3g;J8bv!eF(dh+lrQstLr}`sr^yX)b>VOk1G1@ICfACT=qF!! zQAP%Lqxb9Fo-sa6OlImW(TEPbnhI^Ru`0ey{I0#>3hE$wYPnW^)O~}ry6Rx?t?jst z#;7TfSLCVvMkHQo7ya+fN?F$IB9#7OCcq4*wV>JQN+qK`DBmx_?MkDV?wty!3|Oep zjtVI??3dfCC)KX*z5#R@X1Fl`bz1JRv5>ev-f9QTgKdm2dGZXOXyH5&W-jMfprPK< zel$Cc^J8+n`z#4T2M7>ourb`g2ne`pZM%GK#%(w@s`ImK{D3oL*U?U#@px&C@3osU zXQ#5w{!G2T6WitG>=*}n;=1w1*96hLD-~f@s`u~!b~GvnSKg5C{-9ItP}9#tfphw# zaQZ(V=xQaun%$IEk$>tH63r#HOeWI~^TMB}7*%Fot(-*zz6TgF5xy|PK=#l}{BYgb zP$f6pnKkplC6SMdYg(9V2X#3E9Y7D&GM@-s(k(?_9S)t4r(ZP08(8CV={X8!!zRw& zB1@cC>SX!>oJ129=!&8OW5Uu_bVT*9qO{9>T0!5MS~ZVo3@II*3sY&|wX^gBaIFT5 z(;HC|#HkI6i>@Zs(jqPUzYFLmN9k6C)XN0^`<`m!EVS79dfHM53tR*~$~|eHz(@J@ z>@0R&m?LZl)!#HO%D63do`Wwm8T@Lsjw2&p7h;9N3?+i3we0(lyo(2$C?Hhn!==$!P7`QJ8-k{R=6XAL_TDB$o%SWp3544A-~aZ$wZCSL~B&I+^K0sw6}pUX!{-I zToD*RWxqjSMRmdc_;Rhnd4t)=Hsb3_eH>Drvo*~)){>zR>QX%krXeVqO*W}D#7{%< z1n2aa(jJW|FR(oZlJ`t4%MMaz-dr<57kW7$5*(i`;o)!Cg!tKKk$*0*hP<@oS8oUX zI{4gQhhkPybQ#s=p1i&e$mddbqYu-#q^YnyCRh(zib>b*p`m_9hfhW(oBthN@pT~* zmD^Kr7F3q~*6-Z$VvZj0n9kLh?u?Fg`(`F4WKmmReDZ&lqKXAdH^e+n@(HP7p1cE; zDCMPz;-8^2L@5Q(MwZ~9ic|eH$3GL;PFd9&1XMLp7uR4v@w;_PiV;;AF_#8=8c+tG znoH%#^OG{_(!Mrz^OTwPRY+c%#ZkaR4@~?xTsNTvGYZP!0>cu`L>o*~LMs@j$Ucm( zBPlNTEhO)4hio>7GnQiWhI_@BsG=lSfTrMI3gLx?2ZoQa%U3`+!0cZKa=$hBPu%aT z`v}S<>VO%tY?BK(rcGbKOUj;+zT93o67?RYs&A3iz+>`(z^RZc6`hRn5}p|JmFzb}T+Y?1f(WoFzhn5-`dPgdSs&VCT@jBJ?H%;(F{nPwfK8dRAl1VRiI8NR(Gsk5h&R2A(vl!@iEfmqMsfl;PRXubnx;n% zfDneYdLr`(BX&cA@(ayxJk*_u@-rZE?TZjyt{&#EaqQZzivVO%br~w0Tpkk}(|#eG z0GyN$fuyC9PSOHS%K3@enK|rOe6j*MXH>xBE{cDS0V5$BvY}6N&>ArMhm}_aberBm z(k%Loi95qGR@$Pxh?!!@i*X>5qs9tE1Y~YmF;ZcPe7fp4L{6*NQdpE&{yd;m#XfiR zxf$V`oHTW}tX=)JyJz|=0st&`T(_JEr&#jN@o^JCGRm06vuj5_cx@^{)%0Y-R)%en z=TBV2M`1r;G+(}T;cY#}mqq4x?CC32r_~OChu=t*sKLBV0rNTx^ifO#I*fQTJbN1_bytz4fmXTE%O@;KyKx}IK% zjuk3)dKWNo`((!KIbf|o4yg_Hd08l^Fp^H&cjQ!02+?mGxC{)cJN9o_G1DKLfsE9A znj0{$ja%yXPjI-=SiWclPDOZ^GX$6c`5gJU=|8;$Qmfo)b3qZ8CcOvqOOcGPCFn!y zoY;083`*Z4;dDq-jOFL!z6`l1GRS>iw_*W}6v;$~c=V_#ORx;>ec&bxo|yzv&C~{D z=}L9U3J)va=6ApZGcbW|NpDvRxJ?Bxawd%A3uZyLY@lUlu8uC76J-Pje1DJ$7=s?A7WJmrB4L2h?4Q zm~E?WP+in)ihX2SGF2`dC-1UyG-tE0Zi6eNe^Ran1<;3_uT6|vq_ccs(iFTKCJWYN z1;ODG-GVun9gqUdk#v(g(0nLx60!o*>etL8k3;pz2Wx$Fg`aFkC*PO~#IgIV?~}Pj zaHo?DfSVgP z^EtP5nS)8uEK4kaV`9hExz5nQhVz&xOrmJ{**g$=jsx`k=&|c_aQw9c89;aVTXI^Z zjp~Pj%%S7M#!l`$ULRC4pD^|BFdO&mo&?Rbrc!Q<_eC)oCIC=cVjo|&m=<1s8s4l2W*WMe$`->tPdR{nAw%tlOE3gr&FA#u z^zAQZ@A`@2^kQ{WT4kbc*B!BZe0U>>7-RA%47hbdR?^6>=h$^f(VCH^`T{O5pxTvz z9AX)UOoz-*(!iwk{_0s3ULfwtQvqTfggsaT*N6@%Z84H8-c=7!<4iiPmG)zTPLBm6 zi)wdthT~n4uEtq`+!?{}W6K{yw$^;&zm_G5Sb0Jf{}lmKWlcJ~aP&wbmSNfVBA^*v z-&E&oLm&txH|x7{;kvp~h8 zL<^nF-;CuYvYCxKj2LiKqe(KvF$8>#oCgC2@9Tt;HkzgYPRi0yC9`PTXLDFMAUM!_WO3%Efm{K=U_D-8p7zsy*zyR(bz-RyQQ$#9F!f`=m@D9y&HIx<}%S=J*h0hzobrq35GoCX&GqR!?L<>|=s@O<^4D_-TW48yPK zis#3E#A2T-12hy(h}TE>X=; ze@=9t)a^=BGqVy@V2V-NI*2cGNY}R)fI4Z8&?7(3#|}9rrc$(VvVw!U&L7=Zvq1&aiC&iVE|CP;!<)N)R6c2Trefp~HmW|`+ zt+4W_8v&h1%G=ER6tljWrbgUbIi#;+AR3(ecnO%dJ@>xFK6pa;eZZKPH2J55!uy)L zKUWSOxnih_gNpwH&VqHSN8q46yFtD1-lgSC>ozX3z4&k?(LrsmLGl9I;qQ@(%5e$) zioR;)ZF%C`(~7e;{JKpv;rhPF`yzY)0_Eo_tlnyJ{ap@~Oo?h0+CLBpcGO8bDA7~u zp#NymIlY0D-HKPs`q%QmYSV3he0Ti?_kxH;VjHF3wZXV?VAMi9bVAfEUU<(`1m$lp z4_lD4JIrcF)PKXuWw$t_S;EByLfdjV%0vD3_7>PCnq1T?O8R92!};i;(=RV6e_$Tp zEAT6E!T8mP@5)ziXLz zg>u;v7Y($IZe2XUth&*w?}_E-lp_(Y+JWqU1eIS0jB=1S|5@?#h0n?$Ywcc`x2JY>qerH78r2`rN418~@x%Sn1`Lf$_<#Azs2*V+%L$sbv z{}#>2@bh$I1c=;j&J!rTl(bBHdQ4Fdt_{3}V5YSq@?I`OokFqISJ_XX6<2|-IKL*R zRej0C6Fu5Z5A0L(ICdI_9~p8^Grx5vrJuY*JjXSY1Eg|wvL5vO#|$JddOJ>AP2faq zeZMY0d~SEE*AxALw=)Jrfa1f&q%TLcrvr>xcpx7J1eV6|i9={O-&mkUk*@ez zF}!qF6|zq>{s1N~axm3Ja7sACRJS<5#DQjDO#thmVzZaZnKZw6cZ2{cFM~6YlAl^$ zl0w7E-?TXH165id!&?Wn%7}>z7DYm(W+npV`>qFgXnv)4c*eXuGcv?!Cc3y7t^!#B z>@cQ1E|J@%?hRVBAppWq-`q5LN%eP;&S5v4Jx=-zr|Kb@W^fU~TxfOmZtol8?(b{l zf8pAaKlD|LMZc<0|4skaDeSFoJ+!(wuS(K;WjXv?RUu}$@!l7t)qNB`Dkc}VcxT?GuLU`li1ej?mS@vAl%%ypQR;OTX^ z2s8u3=cHCNG?S3-Hsi^0g)#I|8kojSxT(@~VhuM7P=<*xufM$SYl@y23{=kSNAL!Y%>!$f{YSxaJWiE(1y#=ZNnqr;8vR;^ znVEdjDpX!lXJ%Fq$Ld|7pL|{PlRWIlt*{^Ih~9)lz%n>djqEuMU!d>yH3@k|jikvk z4m%qUXBDnyMlwe3G2oMOjXj0{2@es*`egZ-Q=`sRO&&TY!DY7-Rfl%~3uaD%63rHh zdQ`cPa?i3zu!zbkSd)rEF=ml3_)2w5%G8m5kwx9$lt&jhE>yoro$dtYTFPdcUwHTw z#fga>Cm%Q3> zn2m#v6+IgSwqT~)ks4;{cr}Alii-Cjehm?l?b4e z7HS3khYEjVUAM>pP!7Y&HFSsxu?BKp^l(PbU3Iq%U}T~yl8P^zY7xU!uR#L`OmUDx z%g+@HLrRRw8FhOBps-Nw*mS{)AfX6ijOl+nlIfdd(8}~7N^eKI`MEi{T569Vl12@sbyo-jXq_V~upVEC9Eb``3 zU7W1$uAng&0&^<&`r!H82%?tv#cr}$JS&JMo0Lzk$@mR!tMk~&dUZ8MvWZbzYz=$~m6*fHip`z5lF|0(_HF^ym0T zKt1$#I3~$h2O>A9V=P2Lw0NLAUr5Z=h}M>|4|$1y5O^ZjPJo%+ZE^`%A$r8ORm@c~ zIf?RE?*4vS<%>e-GOjhZsc0pcldEyUGO|(^S-H}#V*Sl9bK_-x;HWW;#cJyijiM_5 zmB+AHTQCBd?ZGL~g7WEXZ0pwzXs) zmkjcABkAUeuSXZa3RX))R*c(wa_U@nrLY7d6ZQ^9-ga(MY%kuTMXB z-;lX9Y`S)Q$4=QomX6YYt$$qITA`b-)hKFrY{NC@0~>`8vWdHXTyvH%B`SPr&A}x` zEcTp79@ylkre?0t^W7UCNoX%%Xa2j-=YT*R^Ix-zA>YeSIRdIT;v=(vCFOX?5bkDKsAd!Z-m|nx;QM3RwLe@(o;7O6HV@19NwtN zBD|<#uXNaQ_z|x*&~|5|+zJ0S{@d2!cAN#ADoZ#$B7)0bczx0yOtKxcJv3cc%J_fE57@i1nf``4sQU1jk6&m0TflC$W|)uDgkzXp`UK%*vaMLGm7`vS#n&9+4UdJ75^iNLtR{3)wNpCRd2$>|M1|8i{s4!QY$8436@RR7d+}KS&`1&U_U5~eUI4J>As@EYb5BY_D<{J8{w2UQ%?Jm8sn`cOh(XLn({f=dy!v z^n|>)*FlD*1uwGn!v0?mLpMsl5z^h{vgvZA;J|OE0S>v=%Ymhl?b>hJwWl}PI<4OI z5f14OZs<4|YV3l4F66S)msKdIT*G!l@nHN$=~yw>J7QNJv?+TJ%kQOZVt87z{6X?r zeYe(+S`r29usb~((0AV;iyroDOLIUj!H?NeMxGN+o>y$1>o$RG`={EA(Jv`-J)X{K{XB+sQ|F8%m$#qpavUb$)4~ zdLtgk*X8M*E@!T3Y+iGOHN0ulgN;JC*x+)(TbmX3+3q%6IK6}Sy3~g$mYS^=X5RL1 zLegPd>MP}~z#Sef{q6kD%b`_~N2(%oBHFbpeD=Wh#da}%VI9t#r_1o2=RTj_S1|!o z1!!3b9WP6b-!>Q=Pppchz98_USx#7>eo2M$;f=Q^`ja_5B;l(ZV80#sFqtbW@@Mz8 zxvOescOj6sJk?{M?>NbP@#@!;xS!UfC$)DYcU&HqXMEN^GgBUZwWvCOmi|X9SHYa|8^}d7SQ=Pf@)s%Ah z$b)}8PvnrvPyP_c3>_9G=kv!kXQvPE*)P6+PesV@fjx2p`<9%o?f5|)vOMU1IkNN< zpP4gP++wj8+VJE3>xxt9=RRwd`E5106xnPb=^vTNX|i0*>RJBnJ>sd^DUlx?C8fWv z*t5jolCjZfQonX9yd0&XS~qHW+m$|4=4};{{gJv+2>(vqHaYbeOVM6iskJ@BrkSS` zGi*&oJ{Mj)Wq}>7XjhIO#JObi*U4Ic{Z*6B4Xfopb#vJk<_S`)dBaFhrGj{Nf5b;e zn)TWxtO7@Dd|-2ct3auYP)!*eIBU>)Jwcz#a>v)6jNVu4dX}P>5WS1+yaS;Qm%V#b z$Dc6`oRl$NGl&kfK~u^0*dfj~g%{pF8}7F>ELw(+Sai~|uM79l4?KMz%$4DUMXw^f zTkkZ8$4h$KMVmj+p069!1|rk9q+f}=$+Brx@Ydk+kWul#@`AQ5y?c8<>nlA4IIgZBMnUHbXZTgNT0 zUO|>2?VGq=NF_5(()_FXiP`PBQHXpixZLvVjV-Sjt`Zm1)t{B$e!oO>xZ>=Da=7>T z890V7iWSSfBc?Ah(t35md3tSeOCDI6T3i_Vvi3kCyjzr;6Q{s491?Uda@XJ`hS#v( z#4KKU)4Ah6c8|1D&$NLlbM=)*m5v;@8D2xvF!IBcO6O!-jy3Qmx}2okBV;-5!IGgv zK?d_3Iwm|>LuETEuvHbpR(0Ttxq9B$@&*rnT1N#RHDqvmxDeH=F7jjwk z#ZB5u_>qTnhDYro7Xe55bv6C$Kk@GawhrYZ)d`v1=n{v7O3F2YcCZ!8n5pSiO{u4}%UX8(mO!#m zPHaxhe0S!vn+9H@XW-0{5t2oRt4fuIEw}0rc63jF3anhoO1pQIk5hQ{=h^*R8TuRj2DAJN|C3yTzO-jF;xhid~;g9mG=o z774@ax}JJ4RBG7TN3EFmB1y08-xzPB%32lq&IWyl)}(`??Ye>*zicxNif>j$>V3sZ z15_adTrY>pjPGvo2z626O%fW=mK1X6OZv@F3R$QP1jDN8|JeRU3BA|Uul%H>(8TMU z`0L+;Pba)Tl`s_D9N^Xa3absEM3`FrpD)~)Z%2=>eo*5xTKk9)_}}G6WJ&Jf=vAWU zT?MM=T*?`{5Ah0M?<%$a7%2!m9e1c@Ik)YgFzk0~aiAQA6Cs+JuIsg1sJ$_rUM{EXNvMj;-FI>}vYycY#jvXJYUotjaZ2?1 z0c@+ITL%{tT+YI3Dw|)9E$MYwQq4I_YT${EB>aMjDpVPeEB$@4L%Np^(;a!RWm-$p zH_GW~SnPrmY14o=DkyRGaOQnHCu8Nua3n#!XKeTE(>GqnmvH#y#}H}Wt%Uxa~-kcs;6>0QQ@%Bf>bs8H+T660cOxN;P#_NN|HAhaV zPCdA1dut<{G_NSfQeVL%f$N?B7+tG_l9jgBntZlJ>wIT83wl|%QtnpYTS4VUOYUok z1)hcyjIMvVJb7))t50Rm*bZSuZqV2^bhC~?kt?xU{z%5A9)=4?T4>@9$B|>Lih+8M z%WDIh;cW_3tWdWV3Hh7Ct$v>Fvy_ERj$7)7btmQQrFmZ?;lx(H>WI==Z-cdp1auh9 z0o2ar%DBYdsj^*nOW6%HXf7PGhiJFhR&D26mTu-dw7r^xR#tkJyw7u8RQXYxqcIIh zA;YrZb63j4+qL0eQyMk+LEciHohEJ0+Pc-j?O$}aqE=+k>Zi4D|H~%H?N&h!|0dYh z?xu8V(t(P$>ZIJ1LTB+SE;MDtK;rGd(JSfQ#1;WHvBA7u4BdBDT9zm49xg5DNU6;?$z4)Nq0L+DT_gP4dD^rGC4sax6QT0e z}eN)}6m&v-tU7W`vv3hME#MuYgTNg!6$Krw6q z#qbTfVi;%6-BmX9`a1Ts)RQ<@ufcHf<`TwD9Iuv>%^Kn3thW|k?&+pC3=V{h4=d)m zVF_PM)@9jp++-2QfF!uo0>`A_>d{(vMz$()?hTt#*gw>8eaA(#i9^Z}?o_S`3#&S= zGJl0AXS0iYKeX!9U2N4M9zgnABT6e!5yCXQz$27|M{JIVe?+HYu=`i7ZE=x76`Ya z>b4zU^=9%{ad%Sp8_Rd?+M;Kuo!pgjwf)39T`!-y?AxIpW*z<$0I+>OImW)INOLxx zjNbT&aTjBClS2kqAXfGVQObS-QOM=x-wzco=igxv5<}gD>R~IWhleY9+mq0N(~Z!N z?%#S1BQIG$>sKse*u-$>e2rDl-z$ykeypUhjSMc|zrU%%n-e}f-JPRTo}DhOxFvg2 znBgF?_7Zy^XtvrSd(7H(Ut*zG4PXxi3{G&c54P9dyUy_^96QzL#s>zXaiDW@f2 z0fp97lcKhR@UrC#uRMRX@4EoOVlu+=!8pfZZI01bC3iR)j-9-HUthdDhp9O*@iLS5 zEw<-DxcKhH7rlzja*ns%=l;U%z-DQeU;K;>w=8UB(xT42^62EC)DjgS$ z3Ugn)FMZI>_}%*Yu7tvySJl6+Sa6PM!(QeO3x=*Sm82X!CH9H%gs;}|nan-3-uF7P zY(ou>HdTCAs_)5qYxtSFU0z3Kxd`ufsZlo4W&HQPzirtM(r$b`DY0IJH|pQ5p?LJ6 z@O`=cW~upCR;KVxMnB=Y{Cwkf?915LPf+`QCp{|u?@g{1Ix;txeh1`m@ptwDrqfBk zmR($(>DMt**DK$fwqc|Wdt`X>an_9`De|W}LR?mAbtHJ6)B`L{PLF)3-MWzd+iipG z7GS~y-!t_u;Mgx^q?-)yX4TpCEY*_tBHH6j8G1}NYdu#hEI6(vA(-jMB!6+`e~W%G zjUQc9_%QvZ{1&Oz7Y!s@YXiTbD#_7>vt`TYgA#X3t{zfNQM!VsKB_}7e%I%NUYy&4 zi^M-3=EtF`1g9oR+P~@H@5Q)Ax(~s9$syfn_^8A_2|*FwcMF}N=UnLge(~|>YTa|j z_g;Ydt~YJUt=ZVCH|^57ji3pwy)?9Xucy9S-8WY5XQ}N8o*w8i_vzsygF7Xcwdmho zdW>ayWsh@=_6a^JG+Vt;qN>imH!X(w``D=XvghtQ7aV5mP)IlahwdToEb=scS||B> zGZUEJ#Oxn}Wf(UMH|@4b#HdcP5B!SS@-SfeQ;95eC5YuX#e)v3Kg0jTH3;L&#H`Z zIZGuc-FSixUs&nLNdKwdRT-n(+UHa-`dj_iKMnEr5`vU1i*_zqY4PrnF4gaym8=}M zxI*j}TmbBHVSir%)B5<4mM2^}*t1@MXAPfyR-YwdB&hL2KfP1h81L1Uyu(KsdhGEw zJw5lM3Vre~&gId)80y~(w?yBUxnA4`tIw1W3c3JBT*>r%!nN=hb>lj$ z`~^Uj=9@#RU?8;SUPg5umw_~AiCEe8aMKe7)By*22M$@H6kF}E+9l;wPuv&GKofl-RO4fO*|D)LvCdMSLAVl#1p-h=;h3o{jbQ&Z9$>+HZ~A*vVAnQcQ`_NuQBvp z|CepE#j_iketKR@(c__~tY3qZNWXuhRx?dzR5$2t#Ap-Oz9oM~wsPa{8|^s1Q~N&d z7>lgzQ`h|p+n&~|9!sso{T{;GjN6a*ZL7M9p`Gl^5jc`_tFEW-?=wDOr)9#rx1`$` ze7SeoY_GJ&dKmp6(0?G-Tzwc&_G^1Qi}N3Cl(4dB7mUwWeH#(eqFX`=&R#S<>0thw zz51_@{)24)$NY6@Hbp`I$zjYB(n^0W_ol2*!ZoqSSliy=oyLcVt5tLG0KH3yVeH%pW%#ilG<}jMngux9We!7;xnmlb<0-f$5c0Q>?R{uV z@8L=FB+f13Yfi9ln(mg0S=F=BC7X*jZ=z`oPs7EfG^P&q%Rz{8poc_Pe?EF^=)+=E zYpx8B#by&w^g8K5(|@hKBHJm!MAhW1fG>%D{Gn0@7+^Gaf=?*SG}tM#o*jVYY?nXh zDv$-GG95s7)--IrzwOIqv;ERBcyK=>Xd-r_IS0!$z`SV8Y@e@wbZh6LFMGapeNZfr zp;RS=>Nnnf54!*~wuICv`raQrdjDn0}-<~X`ibC+-#;8r0)*W6?% z#TtT`Rfs}4t6!ea@sNi9Wq24Js#O%HhawR$#*@l^05!sP)S0EFWH+vNzcmeR1z?V^WP8(bTORlh*t$#UW|sePdgR_H zboMd66&@kF)}fR&EOYz#*?R>_D(7Qea8h@3?sPk)Sm6am9yWUAWZzs!99qn=NW@mI zF;?*lcrR4#apY~QT_mesbW+UMsaw?YtC-_SRb`#*k)E>)FK2NSC|$m}h?t1?dg>!O zjPn`zR3u9X<=#kcd;D$UtKel)dz8@S5TQNdY>RT(k9xGc;doI|^r@nC>%HM775&r& zPe%s6SE)-D9xL@3{CJ;GYvwUDO#FC1zT!{pl|Qx}GCuG4l?oN6?)7a@@)`PNFn&YP zQ)VP~wPt00qUGCvnu2`C_lJu0RwVHMt`eK@a{2W7`0lp0KemHGP422ajb#%qeXLn} zgp;fH*WQR)bEf=PH;GhLQjt(HM%2C*LUeL{Ua(eguv7Z(&&XJAcR35o^IOLP$Hu%$ zc1#^Iydld)d=t`{?;clL->E&m`q$Xl-zT{pKk-vnmkT*ghE86%sC;to`2JCk;z>3W ze7k*(L1%uR*-q-zG&)EXkunrX(-U!t3pub=pzkIxl3_BE+N-A zsU&{G59UtR#K+X+x#K*B6U|h__UJQt94ppcwK>06n5((P)192>f%ob75|G)# z`*-}xbC-2gYM5#zNvZTIpZ0C;IbEq=ByQ96`213`VQory9qOD`vHRlb^DPOMf>F?pL_S@O2w=XJ0Qaz*Eai_bc9uuB~-Y0llUuhcJ zuky#n&hA3-sV|e>%0Hc5$v@vosGNAKIT5L1urh(a*T+}LYBH{1v~G0ts@xvAcwgsu z{zQF}9Ql4{k(=l4sgHb}0V+ABgQ;;!z6tzf-1~dye;@buKIbw-Ja@lmaQu_m*x1PU zP=Mv=M{-|fXk>lln3Tu(rOC1wfk7+p=T4J`o~sE78;Y9+w8fIbHgWph@_PQhFqt$u zs!&xLR@T-wl5ME@NezEJyU6UK*m<1vuuIO#b8DS=?L=d}+D0!JKiHV4bt1`geaxYY zF`8ZzR%+zj7JFcFZYOI_Uc5%p8Ub)KkqP>?`O?%sq&!zFD!eYY-hi!TecZ)o^tzLxB?-P4b z>Eq-_zxWqhCJ^x8a4 zViW)VBXVkJb*hy;v27~oqvX{7wxJ!x+5CMhc9U&Ex4Sj@L=FA^z7RbXbISUSy7z*Q zXF_mR#lN#mZVxhh4rU%TIg%3SMH*`Ib}Jt9%)xorU9Xu6TlpcQqP^7UpHItdc8@$H z7_z679Di#~u6W&xi!KTbKeLSh8Pueze)StpS2yw#>bS>}j=6Dahh~|?WP~yYq`g1U z+gy~tV~Qx%x8oRoYSM8czsGC@=jB|O?CViFWmz6jWA)1mucTMq-+yl6RgC~?(3?E= zWsteA7e}^PI4SUEcra0A0V$s6)bCf@a(y~_8;5&qKDiLw&+tc*?G-;XB`WT3Cisk= z_+9-ett?5Uqs+atCc=(%J+aDOVB&Ypp6z{+AAV)B8xo{ib#%RNe^MdkO0-Np`rDdi zPU8;k@v{Mf4F_r zw>xtCk+gTqK6QtF^>~)+<(|POFg|Rlp-j}=9Uhs-FYq#;|E}AgyEpUQ4hndZ^b^?S z#Jr#6un9E}gzAkzE@Y*$3pYFc;c_2^H z;c$`M)t~<73^eb*ip|A`ZaTavW5KQ!h0!shWU>S^q%3+|`;`fd%8Q)A9| zyqP>`8F@k@W6SETUM>@tk53JsA8g8A^YB$gXVd*Q9*xDiZ)1;!JgGHu$Q8Z(cg&lW z^^kmt@WRB#QC-^xf(mJ<;MsT1sm>#%znHBAHW=cWGS$o#F0B>Vz;B>T%2pYE+g9;A zIO5p{v403fiiw9g-ZY+Y2{N3V?CPu^^Q)E3T&p2avgX^WlfPBu6*dU?kRB=U;k>z| zo~+n(dAulZ0pF{)kN&LZ!;zld(rjx?Eq@d6>BS%7WMu#M?Ij+cI^OtqRQ>tZTG0_o z=1Xm>KT~t>O1+cbpqj5p#f?iFyC-ADe1DfbZgNklxbnDw_XL~lUroZ+ic`;9*8441 zh&ULkx>kGaCsR0|p_pcsMC&>8fX?^T*}MA^HVBj?n0pI^gjTOKUORQx*=49~h|6Pq z(AVPw$@L$6jg-BZ7_%1-uKL~mv)unFPnCa@xsdAaEdQ)vkLTDQo_|$bUixQrOyWx? zIh66O4_)$3KW!RrX(9BI2l^b#gc=S+?_8T&@_CK@=HBI@ghYcB70GzE2d{@tdJZ&- zlC(lUSGa zhSZ@$x>HJG=x&gfp;KBqhYmp+1f-Etltxit06{`ZKsrQPPzMQ#_Y9)H|MPzE3*%u9 z`|N%1bzf`k8ADl$DNTx6>Hwkf=b6%tco;Y1noCd4c!dR|Qh#rN9}t9f(jUZ%9K^!S z!w$5>m*Qn0XK1LO;he$htFRe~;}j|55*>sP+@+b@#n7Z0LEKvz6r>TGIt-Bt3`R`E zKVMo*(F5P5Aurr#HWx2r{O@X=3*vQlt(Mvf;^}G%>G8^4{zKTx*wA*iTxxUdFONv6 z)X+}4KzA_Tt6fIi$8Xo}_*jF9f;#Eyady#yAkM$KsMEj%Y2aHa_I<_^&@`#W*!Fh% z$OEIzIO$P=g|$apO61uT(b;V!#8E#&v1lg>A2}(YKbB=Cj%Umk5SOUfAc9SOXDHb# z7CO$8phG%J+|sQbW(vrM#f^-T_SzM!@J~>dc}~@KQ=*TA3oV1hFh=BrXeOAGfmZ%puUkae^QsB*iNX&8*85ch`9Q7`!Hv_eO z&olh=GnT(b-OChN|6L-f#tWM(TD>&|7-p%5F~EcKjcLgpvC>R~w4poH10#R+01)9j zX-vMV?R7`uT}Hd50+XKxmNQBF#s|U$ke?w`y5>`mMLQ~Z`c%8&B#Zdp8-!W)S&?#6 zi>8e{%D}@gjOmT_kY4=MFD)Uj*#W|n4UvF3cwpxjw_HE{^>|yjn#R)#wx@2ZDM;f2nm29O znK|HZuQ(aK4Z9`Reeg0J`y=1T-5ht1eqVLNZk%OUtL9 z2|7&f-Iw?1Y7gg6bvH%?%inGtOo$pL!^>T)(K^-oL6{eeQ4-G1fFIYblP!d){kC`o z?4~Rl+4cS0+OD9l=*1wP(Uyw z7l9dxnwZ$xfd`@FpHy;lj+gOaIfawrYxFd99nygT9)d5>f9kzVr&IiR=Rrpv4Smwl z^M{G1K0dmIRU{r9c zyX}tB2WI7f}# zGH4@-_%i=?(?r8o3eFPM0p6LG>7v1gXRf8G-bH0;CA~ZPFH8FTr16H$Rch~FcJuAz zhd~c+^QAnp%(hg#5+(WO=EK0&oiz#ax zjP;F`;52la1yk+exG8%}LqE0T`<>qcR01SN1~Xo!wp51Z%LzP9k9l^#hn^kr9;G~h z7qeuHGnvdEk=i5~{Khq05ALgIE$Bcbo5fZt$Koj8+e@gal+x?fwWfGiBqNxlF?q+q zW*%NJys*T#0BMm7Xz}y<+}s+ylx^ahcGMh;$bP#}t!9~@)E%m7&`&Us;`=xoSWB7N z8*M*GfiwtOlwd!>kH|L+eEow~v3z(pWw~kIGMf_0wap-~yp|r-2*wp<2seuQ2nNM7 zs$)>SLfWw%I|b(ZEa5K(|IDH&yO$eTHo+DV7cJTKk?1~9iKN~bJVh^9#wADZC1%A; zVAN1_m=Y@|;H&!fN@#74Y%+2lvn^gUgl!+8@#BufV?WUlO4lrSEu)JbS?Xc9KG|-x zaekg-#}fGfbzn~3vnrw6Ng0o&o$m}X61%Vw^K@BEF<5R;DG0wES~!0Lb(DB5@Q03L zOO$^~W_N3sHBQvBw+Bz<(9I(D>SkYoXDLl@Cx9M^aOQ2E-G8ktK{J|nLDiEqmEN|0 zPuVFU(LM52)^~>rJF%@2)2`>ZJd{fbc}1)uDei`_(9oJZ>40e5uDPN&?P9%#`C!kE zfS;@IZ97-*v?da-;pHBfpLb^<>teMZVq8Ea7^4Ji=%e3%VKluj%2fwi9J?X-LQ zWC=Z9pS#a1n(Ur(Bu(>XNGMs8MR{Zk3t>D?Hq8a|Vr>uoJ@|D*J6p?V;j`rIF$db0 zv1E0&*NtK7_8dZM!Qj~TMIu8d^~i1F&nnPa8``FFKxH?rTNE}slE~-mtnrrf(78d) zMsGG^xl!6LA&?38(~cbXf*tdRYmXRDM-e^m3+k#t-3gmXNC>9n-qc41D%3+;bfAh# z69_5uwI)XUSG_Z!3gQXYWw!X9b+dl67RHpq3=ziu!W$O{(>bToC-REPw^$2ajLPx* zSG|!2O_9Bwf5cCxEph+RC~1$K$clUgtE1#_P;Jqn_$wN;WZB!JxWd-`x6KiR_Dz;| zqr7e61d*cg!dg5I2l!$s-o9H&JxsKZStrP92~DH5?yl5SRxuIuqrV!xcqwvj+kT9l6QVI3 zqD@D?_s%_3e}v6rGvGsR+DxT(5B1X0!osornN#=r)E8i5@k9<&pA3s$x|1e1AXk?( zF7}bfu0QSv@o8V~pA3wxgmo-Pik;sM84F8#x>x=&M_WcBB#^$K;KEao^71L!rO0{k zCu<7d7{^xyxpy4M=9rHonK6lTZ=qn#q(6=87B)$m{ zH5m^*6@Jt99f|1L*#c`^wv7?_O~3nfItY77|8Aw9hRptV2kD$?ouNDH>>^N)yv;q7 zQdx`ZtKUT%4m0rXh%-%i=W846zj#;wfgQg_hk-mU7%xt0zME6q#PBuqv2-(Rp7x#{ zfu$r%E%erCG~JFIpiNIBhmctGN#iiAw?*Hvg3<1G-a3db{&cVHaUL&g($+fzhi(tQ z4KLVCfRy4M_x+rkA_`f75ikL@87>zBu)|A5<-MMc{7CY|_oy?cESdxdc*l*E~4q9=(MFcI=wZ8YrJ{W+q zam|icO`(4aY;)Ux@azM`z`DngQbHYkJbM`4g6U`S_*g)nx$GC`!-@H>QIqSrhNhk9 zB^<#=!?Zr@MKl&eHzgU;Vwee?VMs2MjLeXKs!#M2YHmb+k;#FtcBgSYP>?98K#`dk z59szF^(qSLK?<(aRONO0Qsx=w7MkZI_GQ67cQB(OMErKLJRR}Ay<+m@(`WV>)!OD2 zBr;0#1m-su@YY2Iu7OF`rNB?3 zVf&1O;Qd9SVlHU9{X)+4^Lq^?%?`47iKaxT;8y&247m-W0H|i!izw1}1 ziE-5Kux?oOi(gB6{+#%H#JypcA0J}rP7$q~VLF!1J1iHnBgOBMT#|i zFQTk`jB9P}p~w`1OSlN(E$m8amfKv5n%>v07Q}vG6IiHMuFwY~6e3Dq=H(&<3o0_H zWD{4UCt;9TlAy*!mK^);ib$%L@#t56KuPAq$0|~=b@iVj8d)`Pc?QI-h+Q21Y(AmG zBDnjR4+|g)J%26Bm^^14stDuMh*qFWTDUo#{b9%Ee8hhB{0etUIs zU{)ccu$QsNjIn}w5iYX~243Tz(PJScCg933{RxI{(*3%7#{Ybf{!QG-gita{BMjmA znS}Z0Y=DhaY?1(>FawGBd*OGr4-CYK6}-a*!$SAfhhR2C%BXlo1v>%U_KKi6 zkp-Y7He8Wut0ped{dW4gqM*cuDpQt42hYH|vs|O_xzi^lvnj=~_xbXgQsB@sg;Cy7 zkz@hnG{h9u;ocn)#@^B#<(k=Bp4q!V&d;F-f!?UUvjE|DQ zj8K^o*%8=+7ZD&Pj97?-47+EG1{)=Z1|uD^NP%_|H3fTBztFs`w&#!vGzXur+&42k zffES!ZKB}N`B^crBc89YPtfP5jpb;aL03=@67@Xg7g+ZQFGdeyf$iN_m6|VbhUNT) zr{M*0hL@UtgTk<0m`fIFWZB=0P(&9gPEOx%_>x-_h3X42nFNF%$9a+Wstk~?20ru7 zyjj@Ay+&LrA|)J?&`v|Tgv(Jj?wZmE33LIup@6`}kTAA;83os-*TIlsHwsvw*+9Iu zSFkx^9!`yi4Dm;`2Qy-34iNxl@pPBqCveTJN8TZfG0SWC!Ck8a$LMdv`IZR~hgcju zeJBR7_(}`O|NdaVt~wvq&h#POrjs&o57me6fGUAzzT_Z~LL8fwV+F;F!i7KqY2#J} zuSmi*C0iKSj-8ADEC)Sqr& zHZbD5>iulr`mq{mDh162Lg$RPjA{X#nf($-(H#^5GF*{=&e`MheEYP42%6@5HQUiM ztpbqz=|tJC@y)s)=%zRp=BhtHe2D367&hu-#8GmKlk|mRg^Ef|`M&L%nU&4oPCtAw z9aB6u+ECq1vb4@u4LQot2P`o{31iF>^@vsvvLspar?NxuADV>m=U$`|jX6*M4+@M> zB)qjOAK!TqBQm?>v0ug+(5DFNJ-zv!gQ=}ou>qnmFXV}*ujyK6aBd!-+w zEIzM!0d?9p@1fjuF-E+N0eUIJ_@ zOjU~z!VfAtOa)_*9towO>0EWZztr^#i1 zH-778LFdL%9@*HD2NbF8+aw_N^q_AXTay||)eIE{c#fj$x~alxN)v4(l&dS!ksD3D zle9jn>zi--_&Cl&zi4COaBUUTYz$Em^K{$U8JAJ(vYSCg3@NsPqP^Ai1y%U7rZA(i z$<72v19a4e2%vLz6SWMV8Ugn5Q%8`9h)7xKCoKZ#$jUwhuB~0-I4%=PZq{+XCU}2I zOZUQcZK`JAdzb(I_R$Y`!Qj#$n27Axp8Ib8H_iKcQ)Ut!ZXKC*`Y1@gs5Tkv@f?$c8pP7m78FQJb*F3}mDc&q(bE+^ z*n7~i^!BHmTS%+j{=T%7`sMk9pi}Q}^MOHa^yfaoNTmP%r#?R%KDL3hw=xoer(Qoc z$Ic7plw-At3Z(;)$hCJTYmTR1)Gt?4Qa&y!enT3{oTRHdwMD5}Sdv&1kktGSR%pOP z_Yc;whYe1dTWf`}PhFf-RVF#grqVPgGDHts-c+$93|4o)$gN&hIPj!c*W3-buELOf zHD&{?OHb!7nhu{dk3u`Z~2-VWTsUg9M95L(B^ecbI3wH~=!%$!t{hRG5|6vHgf zMxmL(OZd!ZOI=h#Q#)ZNwY2YbOD)ZL7MJpfq*)-|;^&n_L}^QG_P}G?Vl1yH8r?b6 zzsX*^=lZ7oiv|Mc<|Y$Sy1MFL+j;ep5H&Khk1VxiOl9l|qo?B=hO#B!NTZExeMY{A zyPxN)yzv>2yx3UDv7DViMo66^Z_WpWcnGSU`F=kU{4MyvBB5WZ@;F;rnQz4M((~?# zzxDKJ2><0*cJ%|W5Z+?V@>fgEVm0=FPx$f392o#qb-aQ2;KXV_y5ZMJ>+z0ie&2Q9 zwM=y3`|o6UdNT6+`RTfCAVtQ-Zt{dfdh%A{yyIojMUdT2YH67`ls zwKe?OJI<$HD3~za;Xk&S8WO#2-(4dQ=DkbwAK`Zy-jVlTzEV^LhaixIeoLbB0lkAz z)*@vaUxR}A>;?$hR7?}cQLI0XxTT#jq>uCS_ z1ZCpX2{fX#s{a$f_$j;982PzO?cZ-e3=1fDfe2z~V{A#!g<=Zo+LIXjiEoQzw^L0$ zuPJ5$Y=NHF=8q2qLyCOIJ{4^`UpH2eEcTq9{=Ve0Cq@VAvs7Pm3z}Fy!c@qNod5Tn{f<<8qZr(Lx$fJ74io_W6W`rWBRn%0cP&f=FQ#)KE z7p1B`P?`&?;pP?nbE@FOzS#6%&0f8DPYHSRcl%yub=-4|1XRLC4VBL>j0}lt&LcHa3eU z*qaFK?Ch!XaRoD0cYIAY1u;vh0PtUtICmjJrZ!vr7%fmY>`&_5iWngnr_jG8qcz-w zcKPWGgTcKEkNxSv^OeQLttYNw2X)&;yVItK$e%e8hSo3XIDsbDZuA2lEr))IM%6nz zHPlcLezR?l@G|mmixe!E%_aR7F1bw9iU?61LOb}V$v@&gKN4(ZnAp$43O1iFX%C7dJhOR1UDg>}5tdhSo zj(+aMoocGqGbNt4Q?zE&XMU4tx~)~6!DOfwhkj+&G2h}{#kp;K>nn}9j+8mYxy!Q; z4EYC~_;6^d_on9vN)a2vMD-~my=G5#3zCrK^;%fwoErY-Gt?qG&kb9}|+rZV-5-#OmLbrsS$nQs}# z+4sH!OTS}S994L9{nJ?Rb^DPVw-x|`QTUHwg!_r*bP~n3$>$8~3w9rR(CxUh`HI6+ zrUH|NQoW5^^5uc~W*A@D#<}uFr@%*IMs@_1bO7EM_DmdSYe1a4UtDD=&)cIUMLag| zlm@9{Y*W`s-!G07-9R-*iYq1+?Ck4hrd>p0gG6H2xdCX4#>57H7;Zzpynz7nBW37P z^&n;BPe#Bj&PTMb)1VfK8qJq#1^&g)yk10)Xg#hQj@m@lt%D}Bvg8)2A&E2T=N}l# z9x^E;LQ{P=J*TdQb|s*K)6vGzyvv7$Z9t-k^cf~+gc_*TPU6+r$|~iM7g4-!P^;+a z5m4N_@Jnl9&7|V7B17UR*G}b4KRMp*M&Xk5d^pp_mGO#R!f&9flb3-&4+B`seWdEq zOZ%$l(l~s@1FXQ+(l@ak+4Yy}n(^Rl4flkXb(D>f8 z{OID7ek~_wpciYHM9IPfPJN2nB%^qWNp4E#EP#=qlp(fx19O#0yFYf=q3UmGDtQYF zB3-;SZV~k_G*kQ=D6GTenZrfe72l>fU4F%)t}S9K#fS`qT$tm+-m)A0k*Z8>)dnN& z9+*r`9W zYyLwBoHP#Ak+&v7_kg>7srwh|^zTg+=COQIGqZe8H)3-7(a55YxFnOw8U&GlnhcXp z7z7s1bAeXm$`>Om&~?rMzy=;ax`>*Ffo6V4qO`nTYS;VtV=Jda+=#XlhC`8-)A@GB zRm(X7^f~MlA)*bLOulN@GXTElU7x|hv+eCT$5sbF3KEZ*#sjVEPMb3f%JUHX#H z^FAh)mowvTy74vIbF?o~*m4P?$q<&>^_-zzL*bgypfKXx3kEkG-iNDT4eQ=(_wCy; zGhCWGuiAEYw@YOSAzmBAX80MdFM&Dm%@GMBJiJ0bQ2SGO+El;R9dZAZKQJy_2K14> zgf5yNpDxVtSblD7=nTsC@LRDA=GFY0>ZCWQka47p-nC6{{0obY5jy8x0x+k?GOf*c z>V1^ch|sO~zP>V;>(M=Q^|6Nyv2CNgJ46!Gw2^7;Pi9#v8dix~F@QY))S^0z{{^PI zkL+zYIg|F08=y1dlX_lt)Ri3dT`lDEcsy`ZJ1gKTuWWakI*Mk1JtN&{lNZGF?;weF z{cl>wLd(hXqPf7ziBjWefU>H(T`W&<0HcF2Jw^1Q3@F|Sde_kpzIq+x%>Q`hUH|Bg=~0>ENdQw`*QHr~5) z7%^hvJS>pos7db)5;+v_@o65zF7?E&&X}rGs{+JebD#Jcc4p!PR_g5@WnW)&Q|m5q;l)vmzzk&vRCqFupmnO1y2?ezwUp~YLT8wl)# zZlyjzab)rGVExdNeg)V)ArKPtVUZh*+4~0BwCr(n@&8A4|(CDgX z?1(lR)8BhBn$p8xsk~O2eQWLe^1%R!QDmkS%RRG!g1v%dWQAntJ5P^Sj~DY^+}9d+ z=u-O6$+x+fxCEU1qtz=X|KvoVq1fm2*(fxKnv6EE>HDu`QDs-Q1Bf*#lCb&{WRjgE?NSY9?2oyT%SmPnjxH`4!4TJ^QR694gb>Fp;>!c8 ziXx^mx0|0wB`pxEyEFK96dFtxcnvK@Epw#75Rh^s8`OB85t^>)S5Y?kr}==y5Q6L{ zf9LYR{!0B97e6=U*w#Hw(ZB63Aruqz&hi21=$F=#Ej8R-n{VbtoucVMbXaI23-=z3 zWjwvEni-ntm{i0_sk+&~jB1sbX>8RZ$#h-X&{g?W(9^=18SqrPJ|SQlg08$YoloQ?s6c?l;`bT6DN}-d2d9zQQu1m?ej< z_R*?JGjv4I5U)s7E~tkJ(vzqGkCUshk`%y={Sy_6fPx+_8xF96r#z3n z(9ySn)8VbZnIa0?9Ho!!p*LE-D!KHQ8z*pGU4`QI100UO#yw69t3?r*1-&6-${}lf zqOKYVb!Lk`*u>xgcen+h>OtXwLj_yT)}Ze6(hmm8w+YJ2B>tFm0eeo{m+XleRYV5QUts-fI8yC8l>~hJQp*Yg-<}4zk z>88}BXz#Ib-(hslKsw&$4!N0y>ejQ;HDE8b%t-DraD&1WjfrnYVZkUpcA}|$QXbAx z=2OdCN6}rWbK+>v2y6oUvZ;DmXX)la=N_jDrB;56xX-E~85Pp_sKyafI3nQ1t-(@q z8>=8`>|airJQVh28vJ)&aYd5Lhv|c^TA;;^wg`&x%G>79gsyaJfT7^bqxnG|Brv8~ z=Uf%La?Q)6mCq=pQ-|LL1Tp_9omdT<)F~CN|D$vcR^$3tfmkp9&TFDOzdYy<8$1pB ziaPMdCIC+PCu=zg(Yko-1TyuvF?wthcMUxu2#;iFU=p->EGv2T$YXS;0VP;nO5dbG zFcO)j>Qeqt0nuD0*c+tQ!?FG%2r>Z`6$BLPvY~f`CJ8}8#@7-17!&BPafa<}7Zs|C zQl<@AHY6350w7tbqWXn#*_!GLxND3^|HdP%dUni(8U-~%?S8m0hKN6_8DMH(u9dyd zp1bWmU;r3jfeAxMa`zB}AE)XS}ht(<99}}!QJh6gF zQJ)~O!rEob09T3zJIF&X6S~P>n+Afj?2IneqRN9IR(2)6R#{5t^1W1FexL{Xqnl%D zNNgO;? z?4ahm1EbG6>YWD~MVzV0ev8rkIx#ZDF3!Jc=>&?*mv{Fj0_KJh#W(xvNWuX#BYqkq z0xzPg)}n|MV?;dF%aNY2#vJUDQJ8X-4A@NP3(npmuvFL@!ViZ2XMSl)sX!f&XN*H$ zpbrVsUm?!GgZZdo6M3gLy_52E=V}I0h7S3}SY=Gr^b7E7;{8ps5^RzaYyv*>5M&(u zOJIHO6Ul}*tLnA9oN6<0Xp(r30Ne!xqi_v{5!NFDDlStzNu3hJp>=4hfF z2E8gtl0wGbLOG0D#*WOCID!Bk5<}HIcGr#g$mtQaw#akB*>ukNvJ>ZSM3^;~q zySX>4I(A^0i;s{%U|H7YFj|l6UnIR zj{bf59Nu0%-Iw19Z8l&00b(-)!q^VM^;|R4MYsQ8{` znCJu~nA~W4M-&^;Xn0MapnQoBhs+944ulF>pm8?RtA^7-2P*b^-A5%q!gBL)Gy1`m z=g*+eQu27@;W&XCbI0g)F9FglxpnGd&+*yH7eFn1?|~s{hDoeog)wZ5uI2x6dS3*T zq*|)+TVolj#cb7Fc~=ai@+0CZ>Q`8yV|4KY-NQR?YHJ$0dK{yO#4yBT4+pQ>Nwu@A zDpIvUM*}u-oUboR{zQjI(KSa_b|gweHf#MD`QEp|@ar`ZN@No6@np4r& zt!TVb;&;L&d`Ko**#r9(hlTH7wlY+B-*yi_d(e1Z``P6Tw!e#H9{crmgqgNGS>4C( z`^jaKn%tpB|3poCwT~i8p8(R)hAX5w^8|3=xIPNmDdM@U;v0Q5Nv>t!>c@W-Sql=| zhoSFO11An*%=!lC9{C1R0C|gBkwi)*Aa?nbOcVkrSW&DHV)nn+^}Lf3^zXpI7emq{ z-#nQpN_Q-qKx1-WV_mc8wA*eO>ffRP=dHsx1{Jsgy$~$uih5>Y9wB7>N9SVa!#dAD zr$v8iuj3uSa{^|VkFh>=G!@5M(k)v`#~#ZTrQK#!#N$1%?!axTyg1z@!?8DXp5EmYmE_me-A|f7p@s$SU&vWM6R?8jenZ5*S4f960 z+7-4Cu*$4YTa0Ow7u$C`U_y>k${CI{tNg2 zE^c~H`ITrTza?qESct~Wa2*tCI<&Io2Gp3{KMuQt_()Mys#>1l>nfoF=;~kQwfmgT z$6bCW&Dq8V{JJ>BdE0ID_IgI#EDS{Z>kekTN^MaKP(^_>+ZHO#261oNEotd838cOb zPt1TkNn|o`xs$A53X}ojRqB={Q@V_N!uBsp-=Kme0Vk~bmbJIWdbGauGV)yqL4EW` zbIyG=Zz^GbevecJXjzhuB{nEBvg+cd1}g1im$F5?+|hn6UW1 zJXL|0{qSBs{op{@yP3JxOMiUh%Mg?F-qrn1jeFLkAsT*@pKM7oC%gIe6MWuNRY;<8 zj5Zos@7e8|a_EB%*%wx@wutUxX?N)#WE59O=CdrkWLWS7)cVEpIHEH4g7`}P8DoUi z=UyzAPR|0Qf%MM_BOvFy%WfLj_~ZgGdN)#848dlNm-F2y2mvEJ>#{qQZ!}^Dk_7C3 z@fGaZMG@$tzLOr%pWK(J{Y5E&{(P;dCsbPAa1;2QQ^WMA9YMz)sUDi{?1 z4{F%5aRJAfFrM-j4hLYR@2%y<%K*8kxUKq}{#0X$=AikPe}Y0<6`V8A3J9l{UhPqm zCSE^T;mmsH_GF$FFGy9+#LJF`f5(D0s|)w`$eaFY&$lJMK^qBEdkCcY=cO6(g(vM= zS@zGw)J9j$L~Mj&9>>qx`+DrVe!B>D!a4f(Wnpo^j&=?>2=sJmN7dc{yrF(hSv^j% zV|zei#<=t{^yJaW4DSxVpAc=I+9x9b7$Sx&F);;ubQ(5 z9KJhpaKUe9=ORIQL=L%?_y_Co&z!+Ch7v6@!T}4wt%6cI1~!xRxHOaU`<$qC+E>91T1hmO{3A|5_9Sb48usPdvu(f6Ei4mwt!{AYNf zLYNj{LK~g-Sg8p%Utg&lND~2_%_jeb$BI%;KH_lwD(%61QNTeuh3_CqNlFa@2-=^l zDl|Sa%^5fg;v}gz-n6QGnlqep+<17=J2u#Hgya4^?+DqH2267bc%dZ?+uf&MOkzAs zH(gy@K;j8wPipbBTkdGK`gBj6yv#p4O6*AfolcyvjKOZdJqlN4(;oBrQjeJ|gC+{|!RhUqPr8)PC@KsWl z+~Fv_vvZrrZ8x~+W7+_6jl&kJY5=q(bf0VJ;*LkxM-txZ(&dl7jy`=^OqA`NL|GFR zwvAG*uf*_x>Y+alDgOUl0R?K!-1z$3kxrw#*W3e*jXvX@z%e)6xs<1799|UG=h1GM zE#S!a29|QCZ`9>a9^t^dP{5cY`jIDAAMy1;8vd9Sd&&})0b(;8ampRkMgmw7kjQ(w z#z_&)yG=VTR{_eGPdG@G5{e|ZT;h@<6|c9EEcSm9eeTiB|Fa_A?(UHf{m+V!0#?MY zT?DfA&x&|^z`X&Sk~8jYk$8V~N^W?2eDO2Tg^ly>UA16??XPZy-j@!;R9Q0sJE#>* zO&Y8yZ#|)Fiti`bdbC&}iQ1mZ&kCJ5=y7c4cC`HzDn_jZj6pZyy`W+1Bw7{*t;YNd zv$%+_k~itQYe~%57Z4Q*6M)qdg`D{{RNVVdAzlvnU_X7Pc0wTr@B3pAby6CaO6@A` z{tEfrl}C3|wZQGuI*8GjmLu&-SE3SRl)nTE7|YyUa#<*VQ*2j&^|#<;?w4ElLJi>@qYCgdF;_trtCth3f>q}NXDtG)xl z2}q=$+8}Vi3!d@!fR`)i17;G+BI`@-uZaC!wPY|kRWuP>^-C9LJL8$YX^TWTpm1tP z-v@B6WcmmsXl-ZQ{fFZbb^0ZIfu03=W;TuSV}~9gh$lLkKo+X0iJ5lhUPlb}7_fk` zJ=2_sRqe-RfDr4y=+S2E+xq9GoU>)q6`H4eMutC%(hDl|mFS)sZ@cvBnUQBVMNMd^ z*Xy+lW~G|EBIhvaW~#^KMo)x1pzD5l>h=v}-$j!;{p%z;GObLigt&sWI`a$MXSBW_|I6e!d-~UVw~5^q z)6h;jPew729@`UnFHCp48fjN08GN;nI%#f+)Ld!fQXlYP@pDU@4Fh4OBCsv}6yI-@ zqhs`4`XguLs210>>S-5nSeL3EyB5s_*s+ZeMzM-Nv%ZdeeG{zOPnX5lQB$?T7?GU9 z`=qc$g7?si7$;@pWU0sIwja-nXotCen5UcjFNJWM&egs6By&k$6T!o`T<2jT2GRl=fHs#y3rQ%&JpK+qfPY+E4w z!PGVqDepc3Mo|(fjS3Mlo-z}~ON^gc_4u{uHJzL><^#4YT&sZY3|7qFTT5v5E9P`K z7z-RsGRmWy;zRq}_(vUWVM>}D6y>yCM=bf5DLf-8VwJG;cRmmC4^k~5C_MLDu0A)- z7z6H+i%p`KY8S{zh0S<<^oBpKm`dRQw+B4}w=Q+P_8W|#=Qk?#TAGq}6ZZ^8dw%7b zFn&3a&ye68QzeKRzs}177D!@d1_f?UBvnP*A1{MzLLUCCY9E@?=q0CkO1Bxb>ArM@ z8KTW{tBXoT7cIk<;DSi0zfPhk#fpj0LZ9grTkdu-UsQE4<1=EOAUYMXT8r*t$~ilh z)j5rL{tqTx5(A>fKM1ecA$(XAB0+caE47B%d${Ju5jrF8keTWa^d&^51j(|g?wg)( z|G1YAD>#i^%00OHMa6y){(X)XKN%=o=z!+`BoD_fP|R)%NKX^lGbbH&OC|xPUwiqL zVX87eOpeD__Gv-Gv9+K#M|Qw%}v0JHXC6KDT}6~>QObshM6}?>oi6|Ym2Xs zE2cUT3vki`0CG6r2=&>04E%8`4>Fp8fdcjPmgd?q$R&jg$wpP5bp@c?dL zoY}-qRqfhRYajn`}T^KZj;;gF+Td zr}*_;Imc{PwVV24L)-jbQ3H98Fb|a9Nv!vw7;Z^&q%r7QYa|2{zq?$ZI(N>rEu4V? z7Z*J;oI%3Ob=jED!VJE=C_LCc&n*wlDZ^xpo+5G~#>5e5Js+{NCh6DsQR}HdU=*si4#GbcFR5lg@iGz9hlL zt1d5Zew+YT>_yYLL4E^Ewzz=;$GpkF%=2Ke(YAcJ-X^|6SZi4I!ie2%$tq9d7qd`8xg07gN#jrfRTNFhLfJg;|#I~ z0?(4=ly~rH3zr}5WQU=jTemX3$u;6ICnR{<$##GjJpNfO_U1G1$w~3ADPMn-unh>l z*^JLEJJYfBd*LC%eKx8jgPJGwF~y84RXE$Vg9*WaWnSIA@sZ@r_8Tv+B*|92?cHRsx!uPZFt7t07}lAI6!%}b$ri`xAOI|(=MZV^gb0%(v<^%G zk2o~za0ml-T(S4`Fz3<8Z2~2O6CKbUgfq*;Q9SHtjJmC;_9N*>+K3(oM<|5CQlPc+ zb<^sU^)nf`NhZ|UPDmKR&LhO`lo?K$m+#6?o?+Z=3ukLx(_3XoB{lat5_7SB1;y96 z6OLbm{_F;)l~C`eFK_{PG(`*bKwo#l&WxaD0#7%tW*fSh&USz6q;C9btMm!^CK(h< z_aR<6QM?|`8Tz|Uz614G_rmo+tb0RV_usN$Hc=A62+}hsGYrPnKrjSI{7ErsujjA_ z#2NbtB&^*!pnAqX?u;+6JUe~ojUou;pjy-*l*w$M58y)emd=XJ*h>|C5V)lLr_Gvy2q)%6rQp! zT}^fC?Yh8=R;doZ)IpKT6>|*l|oG?Wvzs|h0P5XgC zk>DLuPSQG36&?Nz)6X0lOh_J8tYQ2)jzR3IGyoMM;>rD6?mkS|x4N4*<%~sC)QIy1 z)!FUobRr##8GsUINPIsOxN1F*dMb{A{O?s`p9wNCor#-{xba>vxuVvKh|tT2fcXBoZq2JhtEmJiAkXW-6V@umZ*&xO1BM(;Qp=99kn-mM(!eJewH zudvqb*EeyMU}MAG@F8-1_hWy* z-=BYnd_DHNIH)i9((&X%N?bftlr|+)|4a4{w9Ao@rjDg%mX{B+Qy$hbE2IRybARx* zRhi)Q*XG{J{47_tU)+y*6+@g=!n1{KF)ZV=!G=}JG3qZ32tTMjMLNZ zzP^9&7Yb`SYpEh^pi_C;xXXe$AI{XR;^L2{IinS+3ur zez6kRz78g}d~++pJKp=jzJI;Am_-=!G2w_&?=oUA{cu>++T&MWN7<(r)B4|f$87f< zkE@RSFRgZE{YYxu=|eA1cIs%qi8FPC1Qimrfo05J`)}u8;!Zm4$7uh4E=^`Ga(Y5( zvh^yzRiB#L{mz|fY(gkoIgHSVpJn&cYrYj44L^rF3l@Z{ts{)ZBV&0lS!#=NYWiWV z)R^cIIs(vyg_l)?JpA)@(SBm`KS8mtGgSu(j0AXfA!v|S*a&O#_Y@d}Kf^qYb**&lIX-{i!u!PWKS z!`1|<$W<|kA)1EhSRXuZt;n-ChK+J0TV{ZDigUWoUqTiiEF3fg)j?1WTQE|U!}T^| zF3;MZTH^*;CnkS!Lp0XVgXt;iq-k(|9O#^1^8?u2`+?^P*}uKOpoqTpcUHQRQoG1a zoxhvG6xaX(76~m{ggUzUJ~~%p<&eR3Nvka)M^R z`7H^a3GvDSDquG0Enx&FT-E6Vl$^KWabD?%aF*%19lamGN>#Z_6xGw_tU+d>7E`2- zcP%*nA9HUW57qm|58KBw%w)@M#MsHcL@~%Rql_UtA!IkUC{nhuBuQhZ>>*oOvPAZM zCrZc?AtVt(J!ep#@9+7&p8ubJFcF9_@ON4wk#JzS)JzZvz)yzIM@mew8h;=r}yN7`<*ntav5dVo(KYM!j+WENwV|D#$A|Rfg zN$$9U)nepXjx5$v$3cs6*VE7T8He%zbNv{ z5#(qB7`*V2GLJ96+~rpeew*J^?WR9XF(-gr41%;|sx>p0;=KC_ znY5;6k2}U@*}W6Z&TAnL5LUFeqj`Tb!<47cOC}`M zix_u1m8$msvg5XPpL*I&?;P{xh`Q3e{fgRjA8V=BBK3QCJWS@DA=Ho)7RgMV4S-Pc z?|zvu1@H6ukLwdeBmTnWy2&aHRwDh;Dw%sS(khhv(&=}W^`2~nCiDqMxxHI9>#21q zJifhpf{fsW-uGXuC532D7BR85DmcNl7k+`&-Fx}D%$H1>rPvSjQytWjpG7Eu%VcVm zK!eqwy-7S2RD>N(*x1%3>Pcz%HwWxtxffd5TI&%Q+u0GR{GpsMuDtOb!P|s}Sgp=u zQ zK1UMuL9pSFA(ij$MN~X`Tl6nx+jAcuD2*#s2{V0%U{A|o#iV+dU%uU3(s*ZA8NfVt z4ungUZH+)nI~GP+gs`$410etp4Nd=qYf7?SyhoArf!UZf+NMhC%cfn5EwqhZoQHyd zhmIE@fQOo~-D^@sV2%?sL~Of^ZH><@kP_u+c_bX#g_XPGiwDrg?8Z9VZkcU$mTv_u zfNN1vdRh4LsbqeK9Qc`Jw`0;oY`E;IPM?tMR`AszyYXW}&7yGY%JMs>3n6j2P)&no zNlscs64jVfTdZo5Ewvr0(*>!_9HZ_h z1CVcJKZJm%BNil7w0n~CU!75zy#)vO2tZ3BHjH3=#IRLi=#QJyAHS)>2hmUng$|XP zPSSgA3WW6r&qOWgI%8*Ei;f^uOg`#8rGQZL6saIF0j+982=Op3aWT``82MuFDYe!{ zfT;Q}Y8%yTcVI(`NQ>x7UR>}876!bL4AXM4T+k)gB?@E636R7Su4u^VE<%~Ga8wM* z*P#kAx6KQO{kP`D&`=9W_Am7L(J+?37aEL8ghhUwZpfwVe&x~42dC^Vlqo=DdI&8v z!hwgG*Wv_YB9o4;fNzVrorS75AALxSXB|*f)^!;`m?2(%BDO@9tYM1C{0y}N zk7S|rBifgJcrB4N8BBkf;p+;_n%^c-OMqO3460uKGnoAGNIL0xN~VnrVu0hC2~YyO znW?Qo6(QIi=IAhh_{Y{VGVn_#n`kAW^)iGYLTU=SI|N)HPs%$IZ#t+34f!$6iwh2s z!1Orq=dlL=tpgW67Kb5LCb{@mcSE~}Ade_?_M1(d6`O-Vds*Q@1oWp;psHe$Rj~AM zkJzv3ba(kwM^NoV)Y0*KM8j_<-eEyNhDf)ENG>i*TohEBZq<{`22G7pUsOH*@rPAX za_-e4$k;bfw=OOy`S^bfk*Fl2Rak^0UE3fWgr#)MaIc{@Tix8P<9nhxj^akPLj@xf zG@Pjc3g-5M0Pue`Q~V01?l9y{%TGD|$bIe$0i+Dg`74v`$&ntTl|#BWYn<-5#o=KV zGv^~a4rR!O)lRs-5h?lH@9N$i_A_J~SYW9TWP{~$7&gMC0e0wDV-`QhS?ZFLOO^#6 zP4%KA30QbF&He>o28RAnB>5GpIls5^u|jJPN#Uwgz|vUWurJ+cxS99kb+tH0OFJa} zz)YsKF|P{0#MLt(fnu4E02Z*@=>q1Wz=U{b`%AyBcK)}1F9dvzR~^pUn1s*mv_2|J zc*3oV3BoEqd`a&ODNy`u?!BquX(3n~uGY-bu}<$yMH?3Q`3t@d|E7OLDVeoQZY4n|PPA7GnT$R|H_Vre%%!SUobdL;DU53ILk=|?G zs_i@r(V+s-fO2pdP!0?+U2sDVY!dykY;a#Ai6@vpFI+l#-6K<`vlb@+Tv=v%Jz4Ym zivoqp()n3Hs)+MvrKD01w<&rYP3n#zAqXnO=;aLxCqs^+piR#HK1fFa__e$;Ycrt_ z0ITc`^-k#0NSx-O_3;uTEE`cbBAEN@UWA#`Wn751(^4~|k#q~IyHUu$4d##u6BXEC z{}>4q{aZYSKTwB}5Gq~I7tnwYP8y;I25MsyL~OIwsDL|0D)%vU80=UnAYepsJ93!& zZM_$_zVYr;&F>N){}(Z6+jX0eAq{F2+XsFz>{t4e|E+N|AUXLyZ1qku^MFI50~JC# zJ^S~?JRwSRNfM7BnHi&`c#StXH}D$Ni{<(TbNZe4|%T}2GO@F4?h<^A8eMmqxgp-SSsxU3lT6m(MiUcAOV`s8EAsCM(< z3EzfE(YjuI+uO6T4*saGqXk7cSqcYj zoF!84Io0i8<}ryZneel-;jRs{z)rP#57`c39QX3twTfc|E`OHXpV(@7%NI#4iwjpz zP6>|Z@RL`*;`Az*sZS!CF_cx}!%%fM#jzCGsfrXVV)d|lg^>Cu9QgPWndyM##=x3` z-2KH`QBfQ&boapVTcNO2$Jl<_|8^-ym|C-A>t|q>*7z}MW~h>O>(J+!b5-WQ6I&iWLBzNt}<4lqbs)S!Tz}1PYEOJ(d9OtKEEk*#OV6iln2t z>8yYbD;EhFY3SsP^HLF0HkjW3M!T43lK^aHwG(k}KVb$wM^Omj%&4~z3jPSVzHN;S6YeH!V@@9aZ94oag8R3^2 zG-Nz#pqPx&o2^i zwQwA@nf}%8>9UM*u*-7Ge?PyI-V!P((-I zf>fN^RO+%fAL?Fm6pbY%cmf2thxV<>gN4B_w01}A;Q6@?5yg0dc&~;45Mn7zw*V%y z@al0l=;1rhzw)&uCrH)s{Piu){1^F)rbn6ZCb7TkB?9g#P+oRnWu% zT!mUTi(fV)Io0)9`0(N^zW0Q7uQy*reHG{Uv}>gD@X=4L*0kyJD$fS?YPxCmj;kIE z{w8evk&swQGfWxRFQc=#m*%Tk8_MEATe(oV)_0Jvqm`R583DF{h5Hf3-^*FrJ-?T; zMT2s{&rwaGBwY7Fv&8;HTy^H*dsG)IOQ&YfhRRvk2~s@4vnU8mS*>RQ8YynA4Gk1F zTYo4&1&Z%EP5YzzGod(wc1ln^>O?4j+q_<=aeKV;o%YBu*o^LH+e434s4hc=QQCT@}n)3F+J05ISzwM!Nn zZDdv(cI;W4R#Rpzzont? zEWNFRlxAEEi+*Jxu}5~Pdt3le6TUuq9(>6roxbDSi~#OfyC(r8d`nRu6Dt-2lhMUk z!B4Cfr9}>=w=`06+nU6x3~g8Ix?=^^iKE~?8pnL{DM(F?-c5)1MG;^MFrM525_jji z)IbSaY_I|9Job*Hb~Js6qfnW6I|*FEVwf38sLF7wFpQduustt+=(JE~YTVo%)Q?(; zexYb|^r0{M49+|;3x;7Ulh}x{tP=*41^>)*{g`yjCd)_)Gi%~oM6>R8XWpeEPzWk? zw#zz7+}I)4x-YNa0@r8k3Wfx{i0FLMdenV|fq4Zllq?n&(EFm$AXzQ0S2NF?*22|o zTmDbutyx>|LpOy6_o?jPN?6k^YZYWi@Cx<|w`$ryOdN$`8*ui7N8?$Q6W zH^FUDzbC1_FJ_I3C~IzSj0u;nnM&fhjN6@V)OE^jWsa$}c(8CuD3a&;iI#5_5#3z# ztOqV*XG-^%;rr}f66*JSHU^jgnhFZCtTO^TUid_o^!k`tDCvdl$?-Fb#|2*rNEZ}5^?xM$0po?-z}mzEq;e~D>x(c=;i*tVnj|iIe`0f~$#K{s%|=a(KX(@;e+=wR zFon=%O14};DI^92!@3?aB)HmcY|m!S)qx4*QrL}#Qav<5uQu|UdAqJ@HuHQ82^9c* zF&h;qbvm`}`-};8#;gMQV{?UA+sAL33vQS9BVY&Myov6tEm9mkK?y^Kr1s=li&Mhn z|0DN&myDyQqt}erbf)8w1)UP!O8%^QtaoR;&o_kPEzy+R?WQV;K5Kq9@`ykhI$+QA zhUsPLqQ##Hpb8Wy4LO=9B}ih3crSENqG(XmnwF8?#Tha92V6HHVrm6;_xg} z3}bBJ%GXMNk+GZ-BK^OH)E@ykffsxZG{OtvE*k_ z8~^~ATKNpYIAlH`rcGs&oh*o<#X^ca$#>;g%8Eb~eL@cFNUF0$hZ+3DNFpd8td~Hk z2+th&^$=E>|FjcJT3uUX(jV7YYcR^KmbC0bSEM%8v;p&KI4MkVdAuJHK)_9nWNIyr z&QY$mbg7s=1nJ2l@?oN83?1?XL|ID@rI5Obe<4MU!tQpkv_tw7dRTi91&R(x zIQET+`06OAx-G&+Lxc?^0!G`^ZyQ#eeI=>9R>H-lN4G2RfX&3Gz3#!}Zo@mhg`A_| zO$RGiH;cgsCF;A4uSP#Aa3zjWVJIrXQD&9Ktan|gZEB0}t`e&3l-=*VpYngK2vxS- zJ@zf_bCC&b-cNQ3)J}0FpL%Joj5`m_fT`jNU1_Fek=r7j9#-pG-%43V_^!B<)x(k z2O)f|)69vo9jwy=M4%m?70MfAkv$M2qz+SQWXX@#xt7q6i*5#bhkvx0E(0#4_-*X#e1Ti)`P5LO01TLO#!2nJ?V$fB&w* za%OF9%f{=^!AIUEZ@*bn-cZPk&d~nmm0vlJT(Q-{6x^4df(NRnzTC4Lqs&Dg#2lhEwoR9Ud19Is*B9c_w9t3Q0U3AdYKW|fz6x0~v= zpQ0JOYxlmZ{ISiayEr#YZ#h#I8|zOoY1hFgc3ZW9F$vEEl9`O092V~}Yf3)S>v?3Z zcBohN@$>o*4|qjV!|LzRDDdyY$W)IPg@?**SLUCQ^MB8N!x$nrY}86Q;*-0gJ<6^- z%3b2IJq4K_UfH$yHTos=`^)pvfAq=@_rFU1c=kZ=t-sdF%dIO3&>NL;Qxd}mb@vV% zcaPRTANJ?&t{%#>k8*9T%PoD(j`w)xmQk#7=k^5!^PI1KPd(DQ_J3A_H` zV5C*|ZsX?uYC}j@^}S0U^EJFnV#a!&xv_I9O=xCwN)B5c>PMzImVx*u*>d?j(N$K~TV}BO{?@lMV{j%$zYgp93SWk2fO458 zsRHRwEJ^XAwHamYyuZ3 zR+cRRET;zl9-JG-6ayXB?k5(hJ_XNp^|q+2CA5KevP!CHJiMl-{N~CIuv5kotRl>Nioe@kCHxrbxh9(5;B<#@ZeDA`KuLcazTylnp#qg8~jgvCD}j zVGKySj6I@RF5THKe*sS`9!hPAqKUwMCl$amu^Msy<^oI&cewFn3s^$E!=V5G-_mdB zWJrC>_^$?FlK)3>x-iu^_6`v-DP7Z7%{W-zv$;!XI9IBwD1de(#jFm*ETU~#1SvF2 z7E#F$ml*#wcIN~HQ}tJX>hmT~=keVOEKGkBH)0Vgrf@cWR@ey3b6wmpRH2l)TD{5| z&*V&HW(}wyCt}FYVc*)uSxaRB!EVp*Q=h1=NJ?O^Em5jk^h=rM@}PA95yZWRglVLV z{LtLcpQrX7I`!+PLqitBCCPsp8Sl?3rf>@}4`RJr(-4?DaT@wAo)74^htw_ttD%(Z z68S1Z|Aq-~+9$GLuFR8cPF)cQ2|q+HM1)tQHzUNAUhUq z6~Ihk*JK8eHPJA^|HP=?C$Pw0N(pa5&~2>e)De6#-*tncM9|*HfF00x$>0`6Yrpkgsy*mH(hdHuvjPv|Is&ENmN;nD~5>CkF5agD+k0}~r z7oIE)TJuxzdZmYR#RA6mP#Bg*GE=%RJVOT{zcf*!s@m9m`1311{5os*N&m^4Ae*5S zAKA_#l{C|v@qFjB5W2w4CJAv2h7FLA^GqqS4M)PtN$Ci(0PqkhDrkQ^R3y?^AGX+t z8wWTu&c^Hs?W9?IDZlI{>7B$^UrmcNJPifYkJzZmFPoBXf>k? z*S$@EN}~v}Hwkihf7NB6nf`_tE=%9b(Wcu*vU)*n-U`OYSEV6q;Mqi>aO&4>Ya#doT9#H5^mK`k8Gazz6v7H8^Y);ljgS`{0`G0sHaKhSdE8xSAq%QzjVzG_3NCX&Augm8N)j3WgIIdimVY(|(A)FKP zw}_M|Xd7~@3e$}!eQz?sF^uv)!E?tfTHMgG$O)2Q3^Iv6$pk`x>r}As-wQsLwh7wv z^j7F%_<>6Ms!?0*>+Jl!&9$m1>Y8| zD%0u)lrf&~S};C^C*q+_xmr~6FG)_z(Jl$_C+}une9m|vhp#w1lpr)zRE*CjhCO1Y zh!_z%ATUA`uA-i0O@7^9)(@x+HBo;h+Fm9QBL~0)%UkSBJXju$Y=z*btn-O?aB}19 zK!hpj%peIXPi{i?;{d$Oh7CC(FG8%lye3Hcu0VcFR{?!5{<0m8fo=iM&*4*RkyQBN z3gaf}Ds9R;c0dU@OC72zsO^wv)e;u!@E-*P(|0f%Myf4`$jZ-dIsST=OOpc%;(-(+5?quq{4b9jjDpcq4hm8YF2J{bg_mrf`|n%7q1z6X?c3CJfPsTgtU|!|``Fs% z`32(!Vc5+xjb=e@F|^IVgb+1;rp_`zpR7bffr2U$`L>meCklg+d&M^}J7RPqU`7sH z)iOOgR*edm4>ru~yI9>m zef^^|)=lKpy-s>?My(`s6&83GHHA!^b6#n1!Z^f@o5Ws$z|;rqOu&F{w&rrkkK&IIvKp27B65 z0tA`tA3>fI8@)yTHMc|$tos9e5I+MMIYa9A2;))=qX%}A7ZaiDN82z8Q;5`9YF>{I z`yZ_^pNC?pzZ%cadJEpb4Tn=~{8kr|Yq4RjbE_Bvq5(8fo-n=|K5-p4J7zZaIU<6w zM#OkQndaeFldm_ku3bsO;MnJ8^q$a%;rkkLvX_Yd-Q#Ih1Z8%z+tMW?*Rar2XLaKe z$fDECIJltp;kB0>N7leYl}&UE{b({5!Tjrli%SkcFrLC}wGZFT%NhL_C@3I12^4q> zQEEytwgmyWvyZb3PG&5EfUYxy0X{JGyaXDGG{|BrR5hXdx0H72jn=#i>YbbbaYiYR zT%UE|%D3k-ITXae!g5 z*HmbY6x<{xkE~`owrZIE&>DvGq^D@&3Q9DHNu2MqWl|IX2FaMCO%ZqY&ngL>-eN#W z;uk_VYL);>ex%$~HY+&~cXE99@@AFpn-{2puMnGR^;qi1 zb9=1-&Sb4*>|vXljV95cp&@KhRFp?B-da4|$?g!rfFn63@j#;Ld4Y9tZ~SGRN>u#O z4rs*W55b^1`fE^8Qtn=LDznlKB8OH(6KCQfz94DyKd|uX{kgdtW)+Z0Z4pLtE(yRh zQWmA~vrtaQ%{O}?eR%zwex1nf_ag?bsz``tmZk++&+wtV)8^K;J1g_K2mH3_Wkj1H zV%zF~%<*eZbO>T$!lUy)+)v&30AJ9y=VN&=((=Kg`uv%cxx?*Fye5|&PjY-JmVnCA z2#?(WS{BrBR*-V;X^@%RIm)$rr=tqXuH+$7MA`Wyl7NTw*h%bFu`0_RG4Ci#$^aEL zA0u_NK~a)~REdPtY20df`m)z}u}_fA6dP(x{*3J&eOo4wGCB=KLR7k#bQSmHbiG^caI&~ieLy2kmyQ+!k^-kWIFkaFN*lHR zqZx0$QlOReKX0GV_a6M5jgBDPO`!vJUY(63HlTG&aXvQvH&wN3a-Twny9`6y4gWcp zyYiIbFL9~0%RpJ}zs&x^gy652BADVS?a4!7Xz#A-C4y0FC;>#SMz8}LjZ3Pm&UB~v z*fK9n*mdG;vlvHnAC1KdScL8wh0bk6{jL*I5=49c^BTcy+zFxG4#~oZ-|&@L1Ss_} zvsUx_k|_%u?BbF${ZSXU6Rs!JFSFd@xXW>k0&sy}?SufE{?43d=GmzqR&nsDG!02| z+7m`TG<`ITFZK!4-#(=3qhJ$FP*xY$eKH@yWM~k6#t|%2>7Ge&<(R(n>7GfY6u09g zr5&@*JWekGnEo1hjPz9Yduy5Amx@ApHWa&Dx@5fZY7jv=J9Afp{KtRD)kTJkth`0l zPVvB&l!aLH{uyhHh$I1xOM|@>nVa`tPLIQ z)0VH=GEVCN*=~YipV8o+fZP_ieO|8!cx}&&bzT=%XnT$zbT}8xEu+<%c|HNEHK8tE zjH6E9%(HmigPj)s+vSqf42u2JfZS(nqZWYZVpXff=_jseQf-Ezp&!U+urSddFN@f$ix!ci4yQLB}k{=T;Wma3LAq?4LG-0Ifm%{@D+o{9n!=6!+fy zI+wfKk{9zN;cG6&`{!Ae#SNw}HA=SeL+RnL@Ocd(#tTFVfm!np=4;UVN{qsqf+ZVV z!W@3hB!pxsNrBH^ya2Kb`NhK<5PnP>^WeRka?b~;t{738Vcjf?Y;17%Y$)t$_%C~Y z|HLIfcCY2KDEr6-#WyOKejjcW=sG{|?2m6|X!GnY=hUQ7S2$bwn2I`R6kOda5-X6? zePY+k4wh^U%lsjg2)HU1s8=nZ^!%MiPw}^~|95g;^8ZTCX9)fOGdX|n%-D5r*rcak z>p??9#nIp*sOPQG;fiI2oVP%wylMG|#V4Ed4V3PvDYt3+eTI|tyCbU9+0}hC^ zGp^@j`2)0C^%^=4KsN+lKQOs_uz4_e-D4d)cxGk&;}46jyL)q!?FL^CwK8LURK_#|Ho4OpyZ%)!uGzKD_ribfmbDlbe}Cwvd>q5OQX#`bzK~AQIlcMMA70^qR)+ zYX?bO6C>ps=|Luc$P`<@xef2<B{~{o?T4HE1JmB9i-#JcL0GV<@*8`CL3Jg6&M$ zX=?61tnQADcoI>YMf5db%!rCc{2K1;(r@`ZeVSc6`yO}%Ag>H!8ZV*%^h^`}C>RY* zm)6{9ys0zGC48D;6Ze>j6ES%?#}ImMd`S@Jgt3jo)$AHfsf9wnT7ioj@iw$zHOtMh4w7?)$Avua;Peu?!V`OWPrpv^swQW?ry3d91qJ! zs9hna2uQ1X37mqG&&SWWV}Z0|@Q)w07~uDRC-45y?u1t3WEMbF zCoT_R@@t;(F$T!E{CDfzeyr^;M|>i8u|%IB;bU3c%%Q-~9LQi^Xsuu-L`8TNiD*w{qTfL-D z$lwttKJ|!bp+D{-Y83APU=s`5g1j&QTUJxG03D6|yDS=N=*hwKx)GDI5iPS6B+{FpbT$3cna4UuJ}9L zFoy|4ixepK0cR1o=%jFb;m9A&?Q4>e3}Z+L9M?gyb=wqaYvHyVNOn@_qx|U$tiX zYZDMZ`rdjt+*g+{1@bPI*L||px{on?!0PlbGh42zQseO=reY&&9z^@!LXt_%xn6k7 zHuYdcl&7i(J2Uql5G1ow(35IVN7J4fdqU*CunCB2!K)moE~4IpL4Ol|%^*OpRhgH# zMa`7g19IS}%a{ z03|cMk<{De_`0(DPl4d2SE&ME#ELWez!-C(iK1EandKA2r-6^&RtU4f$d8*645Ud? zz#CKq0QHE)uTs|u$f*ef$!vw=`{N4ZWAvm#0r9o)l7Ba-M8Uc%t9pL6!Cpr)Z4SR3 zrPBk6VR4`(M9GIvSjIcFN{u!Y*qheR3A2>|(NMps#V&~G#Y}g#wtVO#LtKuPhFm8k z$-AVwTWmp;+2J9_qiD(iR&Ypvp!pLiatalI?2-}GPz~xEk~vf8B<6%OfLy0fy%U#f zoho@nmY@Xao+RHpf%J2W<2zjlgS+$J{O`t!%QZaf!`4Tr<*PzYBwe4EEORptsf`?OjlTD}jj5P;IXCZFx8C@3 z<{U*(BGaSmmV?oqTK4yGFV$GIK>Q8GL*a62W0w|UI|A(arT$PKIL~n|%vU&+eQ(B2 z-rJceoWs#ad}}*zJS=oh`g4>{v_Ps(l8((|GHMPnQ7pknFndZ-c)5=I+CZ8R)-i+x zbn+Gf9lr?Cp>x+%?B-k^&&SHLR%_qA;@H&!mkXnLksJa;&vsQe%@JVt9xt96$)3N_ z*p%fdDSC?VBEALKGM;T6c-HJH`q9xDSXzMJf`#XnJ4UJXX8|KE$Kv|4;J8d(Avjy1 z;;gO7U$NH;jMDci_)faJk2oVN^=5qKD}XAs2cqT+6f)=Iyn!-xJfOo!tqFBcLVL~v zvizx7>USZH{?*97QrpIn*O zW6_6w*bCY{;3+A0D)*b=tX^^iA1rmwH0%I2<5vZs0>z zpJ3MUp_{jaOrO}d+oa-Qxz{%S5CcP^b{1HItcCUmM%z$fW-W4{P`7`=?}q-!GI}e| z4D*rWwbrwInNe8ut1@-V245~95h}HCOu}q9mdTrw`Ru5y(EpLQh#c1ID~F8R1bVA_ zCDv8}<&=$J)@8U_cGVSlq-bZY2*Tu*+WZX|AKqvqcw8eQK5rTQH~Z~sgUCb_md?Tg zgW_VoEJx^Lg5r<{t#c9lM-uV{)GAXMC-ONcIA|!I{$rO|F*CzI;qymmz-u9stQ7fu zEccsW$RhE8)eMeibfx-$cbD0Iv!cWQaP`g&(Eds~aWG}H?Jt&(|QlH9X`Xp~F7kh>2F{>>+&xZOM79LgEL>63k>xZbEEyKVf5L+ZnxHPdJQAb2a-kS$O=o@)n&==btXVmWNf6GJho(^b6|)&GPKmB833EUIc9@6bwBs6cSYtcCdpjfr@k{{cb% zq@7|YUjxY0GdriWIn0IBk+D!AK86_EegINz9YYEW{y-|b5Lhau?-x!K4(%fve8!p# zxHCTfdk9}&-MZgUARfkJ7t2KORq^ziP>W(Rsn5EEu#{};YqT`WIX=t(9T}S&32p92 zPSsNlpr6d)Nl{Kc&CG2qMP;SWLs*7##$vhPKvXB3Y#$gJik5Ql<-Le}|Cul^4q%e| z^CYQwcT?}UzH$z7NhA_#;iazcMFcaWjBI}&`xQjL3c6D8^5GuhpKD~fL&`uA1Rxlo z(H_pyr-J=-D(vz(rs^{TTIk*RK(L_-ywhMjA;TX`jzNgne3n|`z)JtqyP^M z`{~%Bi>v2I)#Z=CxdWmAXmV-cbD4q2P?I-c+CN ztK6A>mCMvW49TfHY^ohMYKAPE^i=%sd4**0>!4k6a3aKeXyupvo3ftv3v!PPavFW;B&hNVckQ}Tdq+Q zn#9^_JM{o36QfJXLs^FM#;V1E2Lf2mK*m*Xtu(QAv4p%lbly@cO-OH5H!2wz)3Y| zkFJ^-f(fC!Cmlx>O>MtCJ;BZb4BYS@rwW%jc*BHQgjl>4rQdq)o8LC7F#f*+Y&0ML zQ_%kf*gF0ZDqe*;_1<%F?Q)E0Tp>k{lOfh-7DL>2hmDo&J0!GAw&Vy=Ivp8#bauZHCJh=cPLJ=P^lG3 zFE9l%=NxrP2RmZP$zL#rP1hW5YX#Ey&SDIfKm1@^>|?jqFe7;`}n8ORlrp{8Qm-$ zFh$!EN2cyS0L0oVJ)Ilgj2cK%7V&_y81uE)O>MV27(O1`UvEhZoA&8TpD%)cOk&A6 zh_#x|SaPZWM+9CZ;ofbAD;e_uy9QL8KTL16XsEyBpZGq zr?WVzXZgI(T(x}VO6{QZvOk2%QpL)q_RYfg=t+;J_Q5*+_%F|yE@~r1t_Jqai7%DYj(Me#`!X(uCY7_?k77NnR`a8^w+643*Gc`|0s=a04*XMq^a7 zh8Dh0Y~96jd{O|;CX*$1F(e)yiBLNkL{Jh@S#dK^@+b{}J*ymmIqOwK*JC?w2!h{ftcw1%*)RO(gZm)$5Ih~Z|ya-Jj8}d?D z=T8Y&fPIFF%1}i*zM!>vXz?NJA=BxgHfQ6j?%AIFNs+XevQ<@uYx`6!1SymcDalKS zYur7dHZi2+HdJ$AfL9>;@U*ozsge?TzEA{-*Y*G<4>*`e_C~zT0I?-X(NF9efx7){ z$;_OFz>NumJt8?NQ7ziAt%KxS^@^)&=8<2Xo)`Sp#isq4^Rc5@jqVb5eYF zQ4(WZ&8{ffvzR3iMeWe5?WN|lKjP%zv6(N$LpY&!580)1v8dUP_}z#*xxajFD%`s6 z`esyKZR33wW9xK=cxZ4>i0AxyMX0$z)T&CA9ct)1_D#Pf9xeImE)Ra4{f7PZ*d1(c zM{18{g4S>rf788j`s-Ggepd#m-TFOw_xpP96Md&Bw|sXODzEEx9&+-17p5Y+E`xTz zIo3F!&R@WLq~87HaugJn^Kz1N>&&-r8(^pFCcM9H1;}iCE8@KT9_{{ZXs#p@y`NR9 z5fZPYv>UOI_2z9o%JTPBy9&+s4Jt|D(BJeFBS+smH-6|%$Q$cD^9WKl|5JWz z{`c;O+19kO(089U5-FyBGz4zP}!Re)q#oyW1!}3M!|N zv+<~vN}4M&nNf5Vb5~JN*~c77wBJZSH05RfatA$sn{zJwGW`i`WU_AOe5qz&(NR<= zUw_W1vgde_)Q(_ifKA>t1xe5bEWIOD6-wLUMIgxhZ)VnPBSORvY#JMoSnQV)YWT)- z2XOU*%2V}w!~i;Ow-JHl1L{-+S_MCy=tK2CA)JU+2+2;wzVzN4Xr%O)I&u`64ePl} z9Qzx-$K#>me@?NK`x3=31SW^pG=!V+gxUj>TjVN%*B^9H8MWT75JBX+CU9@V0Q;f4 zIBWTO2)Ea+Iy@e*+9bvxsor%lzd~_;1+)vFL7UAJ4Scyy#=G%Xfd~(?Ma`~0ZUa8m zKGsv8tW3;xmg#wLfrKu5PFl@_Zpx{4G8rOT;CT-*$)IEFlbt0H4XNenFN~aQQ}O(w zqZ{U#SyW@^UX@-3zNt9-fZ7M^&z5DOd$H7gYWGi&xKAV#NfqTczOeaX%M4wiD{2KH z+L|Tvz<6_96_@g3j%$!#6M(sj=xcGVuOBAm=lL7vGsHLXwY9MJ6+!S-U-^xtf$VuY z%Q^6!l%d|S9xHym`x5|t|vdlN-@mNY4ptwGl}^vq%E_4fbENVBZNk^-Oiq0X>)Key#gQhU#ohVbW)@nR z*@8Z=FvV-BljHpCE^y4&D-l~@N+-|q`(2|Q-O^^%k)Q22>T)tq$w;q^P=nwV#iWAp zOy&_xgCU7>sg+5lRS%DMJKOMsaI4I-%j9>0t?Fv~=&It8_EKNbIcZnkFVzsJf0=Oy^{VWx}Bvs{thtaYo zu~?ttN+!!=Kgkw`ZqEJ!gm<3T?p5^16?Cn;V=KVYy#oPXjc)x!3nsMcKx zIP_&Qu2#`tlQ^&|9s+z*W5|o(J5ao&PGTm=wE#FqjgZ(}M(c-QLLKq}$znfv_11b# zg;2j*`m?&q79!}fgYymfCjw0iE%OLL(U_4AnUVh7p?)$2BD(vevqMY@j=Dw@o@xj3 z4)w2l~fltNT#yjiw6<=EiK?^HAwD-T?m3Mdt6_a&KxUQ+Qe_7`R3-IPDwh^PZ zgok>a3-s%<{*7Aaw)5}fi3ulQ(HxFsWCW1Ci$lt`0aCwnB!B)j_BRKkl`>hyuv7%Y zkOoTjCCjX*>~|S7L+Tm8c`Tt1`?oqXR!XvmG%P)sc4ip!+<+x9l8f_Ac_W4Na-6^J zsP-X5N8jS`_09M7zZt}{rz(~t{S*kc)mqoA)Xa)!h3zJpr-kGps~|;A3cBu${i@Dx1$%4+*2`;k0>goC2Dwl)pPsCD4>;|lN%ao1jMD61O?h(n9!aUP>BqHc z@65eGFJ-QnM-hvoUuMqU`F1Zu;p&vj|L18>KaNj($|julbQtvI_e#U3wa9vTQ-_qX zTLEucgM>bH2?%t4%U>Awh~5tRG!^LihoqrLj%H>){Qj~< zx_DjW_gbmx%i4QK4c#H{#h>||{iZNvF`sIFV~~5j-)TT`!|te6>v!M}A=5wQzjp5Q zHz=g8anh>naDC&B$ygDe-&z0hFycw;^ijiWkKKVk1NA!lE~s7knT#(gz9aST1n&89 zU+W;N|Mtm}gfHC58hJMe7IcgLN!J9T)^vw_!ey*|K<;3%sc)4k7}NG$&B}P0f^(QAs2VTOI8IL zjCc=h(j0BG@o8cxF6D7&*)KCICF!jr)zZS=F^+9r{%Dt({Gz^keT;@APFe?@K&3N1w2*gn_|G^u{v>k z-w`_Mb4`#5WG575+AoFVp#G=#x}e!YMH|fusk2BpBnrp(6!w z3oXEz1RMntxR06+YBCOLnWW3sLee~k0uE{<5o${UDy1oBzJtR9>-KIu`1Ww4J!n5e znS;yv0$Cu+`-0t3sKyt5!kn8sbv%#RFS*Ha zWhY8d|f~APYh(b=TgSfAG16Y?6`>%GB?#oJA_MkVxZGCYW(5L4lrrr1Xget~Ez6VV%;5$6e|>zk?xWl^Q0?LuuPhtee? zP!lnhh@;M5`rV`OFNt$4!WJe>w4(e%<$}GmiS*f@9|v@|0!zbQ3Y-wd?7-@`ikT{; zsZ|w^qxn2?tCn(Q#?cUwAnIx?!c}!s;zwv!(Y3Qi7g_k0K!u7x4EZpwft#5C7^+E+ zV2UrJ&o@JAHknTzSJsZ0!8l_dd?@}bznxHLlW9vs64#XBl8GPBLF4ORPJxvwY%}18 z5icgx7v?sxH4Kyww?4%RZiH)I|DpEV1QYb)PG{@8-@OqN<4RUfV_tXWe5K~{?F{

F^ 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

Ie9E8r($7 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

l_OXJyCEI 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

}`)XPv5hL6*z4-v9$k` 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

e-ZY`I$HxeXWjh#h3e_ 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ÿ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 F{{Wvb@hAWQ literal 0 HcmV?d00001 diff --git a/x-pack/test/functional/es_archives/ml/module_security_endpoint/mappings.json b/x-pack/test/functional/es_archives/ml/module_security_endpoint/mappings.json new file mode 100644 index 0000000000000..e9e8b542849a0 --- /dev/null +++ b/x-pack/test/functional/es_archives/ml/module_security_endpoint/mappings.json @@ -0,0 +1,7755 @@ +{ + "type": "index", + "value": { + "aliases": { + }, + "index": "ft_logs-endpoint.events.file-default-000001", + "mappings": { + "_meta": { + "managed": true, + "managed_by": "ingest-manager", + "package": { + "name": "endpoint" + } + }, + "date_detection": false, + "dynamic": "false", + "dynamic_templates": [ + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "agent": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "data_stream": { + "properties": { + "dataset": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "destination": { + "properties": { + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "event": { + "properties": { + "Ext": { + "properties": { + "correlation": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "created": { + "type": "date" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ingested": { + "type": "date" + }, + "kind": { + "ignore_above": 1024, + "type": "keyword" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + }, + "outcome": { + "ignore_above": 1024, + "type": "keyword" + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "sequence": { + "type": "long" + }, + "severity": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "file": { + "properties": { + "Ext": { + "properties": { + "original": { + "properties": { + "gid": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "ignore_above": 1024, + "type": "keyword" + }, + "mode": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "owner": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "uid": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "windows": { + "properties": { + "zone_identifier": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "accessed": { + "type": "date" + }, + "attributes": { + "ignore_above": 1024, + "type": "keyword" + }, + "created": { + "type": "date" + }, + "ctime": { + "type": "date" + }, + "device": { + "ignore_above": 1024, + "type": "keyword" + }, + "directory": { + "ignore_above": 1024, + "type": "keyword" + }, + "drive_letter": { + "ignore_above": 1, + "type": "keyword" + }, + "extension": { + "ignore_above": 1024, + "type": "keyword" + }, + "gid": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "inode": { + "ignore_above": 1024, + "type": "keyword" + }, + "mime_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "mode": { + "ignore_above": 1024, + "type": "keyword" + }, + "mtime": { + "type": "date" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "owner": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "pe": { + "properties": { + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "size": { + "type": "long" + }, + "target_path": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uid": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "group": { + "properties": { + "Ext": { + "properties": { + "real": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "Ext": { + "properties": { + "variant": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + } + } + }, + "message": { + "type": "text" + }, + "process": { + "properties": { + "Ext": { + "properties": { + "ancestry": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "args_count": { + "type": "long" + }, + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "pid": { + "type": "long" + }, + "thread": { + "properties": { + "id": { + "type": "long" + } + } + } + } + }, + "source": { + "properties": { + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "user": { + "properties": { + "Ext": { + "properties": { + "real": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "Ext": { + "properties": { + "real": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "settings": { + "index": { + "mapping": { + "total_fields": { + "limit": "10000" + } + }, + "number_of_replicas": "0", + "number_of_shards": "1", + "refresh_interval": "-1" + } + } + } +} + +{ + "type": "index", + "value": { + "aliases": { + }, + "index": "ft_logs-endpoint.events.file-default-000002", + "mappings": { + "_meta": { + "managed": true, + "managed_by": "ingest-manager", + "package": { + "name": "endpoint" + } + }, + "date_detection": false, + "dynamic": "false", + "dynamic_templates": [ + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "agent": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "data_stream": { + "properties": { + "dataset": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "destination": { + "properties": { + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "event": { + "properties": { + "Ext": { + "properties": { + "correlation": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "created": { + "type": "date" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ingested": { + "type": "date" + }, + "kind": { + "ignore_above": 1024, + "type": "keyword" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + }, + "outcome": { + "ignore_above": 1024, + "type": "keyword" + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "sequence": { + "type": "long" + }, + "severity": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "file": { + "properties": { + "Ext": { + "properties": { + "original": { + "properties": { + "gid": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "ignore_above": 1024, + "type": "keyword" + }, + "mode": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "owner": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "uid": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "windows": { + "properties": { + "zone_identifier": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "accessed": { + "type": "date" + }, + "attributes": { + "ignore_above": 1024, + "type": "keyword" + }, + "created": { + "type": "date" + }, + "ctime": { + "type": "date" + }, + "device": { + "ignore_above": 1024, + "type": "keyword" + }, + "directory": { + "ignore_above": 1024, + "type": "keyword" + }, + "drive_letter": { + "ignore_above": 1, + "type": "keyword" + }, + "extension": { + "ignore_above": 1024, + "type": "keyword" + }, + "gid": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "inode": { + "ignore_above": 1024, + "type": "keyword" + }, + "mime_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "mode": { + "ignore_above": 1024, + "type": "keyword" + }, + "mtime": { + "type": "date" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "owner": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "pe": { + "properties": { + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "size": { + "type": "long" + }, + "target_path": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uid": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "group": { + "properties": { + "Ext": { + "properties": { + "real": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "Ext": { + "properties": { + "variant": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + } + } + }, + "message": { + "type": "text" + }, + "process": { + "properties": { + "Ext": { + "properties": { + "ancestry": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "args_count": { + "type": "long" + }, + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "pid": { + "type": "long" + }, + "thread": { + "properties": { + "id": { + "type": "long" + } + } + } + } + }, + "source": { + "properties": { + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "user": { + "properties": { + "Ext": { + "properties": { + "real": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "Ext": { + "properties": { + "real": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "settings": { + "index": { + "mapping": { + "total_fields": { + "limit": "10000" + } + }, + "number_of_replicas": "0", + "number_of_shards": "1", + "refresh_interval": "-1" + } + } + } +} + +{ + "type": "index", + "value": { + "aliases": { + }, + "index": "ft_logs-endpoint.events.library-default-000001", + "mappings": { + "_meta": { + "managed": true, + "managed_by": "ingest-manager", + "package": { + "name": "endpoint" + } + }, + "date_detection": false, + "dynamic": "false", + "dynamic_templates": [ + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "agent": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "data_stream": { + "properties": { + "dataset": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "destination": { + "properties": { + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "dll": { + "properties": { + "Ext": { + "properties": { + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + }, + "type": "nested" + } + } + }, + "pe": { + "properties": { + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "event": { + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "created": { + "type": "date" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ingested": { + "type": "date" + }, + "kind": { + "ignore_above": 1024, + "type": "keyword" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + }, + "outcome": { + "ignore_above": 1024, + "type": "keyword" + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "sequence": { + "type": "long" + }, + "severity": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "file": { + "properties": { + "Ext": { + "properties": { + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + }, + "type": "nested" + } + } + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "pe": { + "properties": { + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "group": { + "properties": { + "Ext": { + "properties": { + "real": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "Ext": { + "properties": { + "variant": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + } + } + }, + "message": { + "type": "text" + }, + "process": { + "properties": { + "Ext": { + "properties": { + "ancestry": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "pid": { + "type": "long" + }, + "thread": { + "properties": { + "id": { + "type": "long" + } + } + } + } + }, + "source": { + "properties": { + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "user": { + "properties": { + "Ext": { + "properties": { + "real": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "Ext": { + "properties": { + "real": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "settings": { + "index": { + "mapping": { + "total_fields": { + "limit": "10000" + } + }, + "number_of_replicas": "0", + "number_of_shards": "1", + "refresh_interval": "-1" + } + } + } +} + +{ + "type": "index", + "value": { + "aliases": { + }, + "index": "ft_logs-endpoint.events.library-default-000002", + "mappings": { + "_meta": { + "managed": true, + "managed_by": "ingest-manager", + "package": { + "name": "endpoint" + } + }, + "date_detection": false, + "dynamic": "false", + "dynamic_templates": [ + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "agent": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "data_stream": { + "properties": { + "dataset": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "destination": { + "properties": { + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "dll": { + "properties": { + "Ext": { + "properties": { + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + }, + "type": "nested" + } + } + }, + "pe": { + "properties": { + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "event": { + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "created": { + "type": "date" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ingested": { + "type": "date" + }, + "kind": { + "ignore_above": 1024, + "type": "keyword" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + }, + "outcome": { + "ignore_above": 1024, + "type": "keyword" + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "sequence": { + "type": "long" + }, + "severity": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "file": { + "properties": { + "Ext": { + "properties": { + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + }, + "type": "nested" + } + } + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "pe": { + "properties": { + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "group": { + "properties": { + "Ext": { + "properties": { + "real": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "Ext": { + "properties": { + "variant": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + } + } + }, + "message": { + "type": "text" + }, + "process": { + "properties": { + "Ext": { + "properties": { + "ancestry": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "pid": { + "type": "long" + }, + "thread": { + "properties": { + "id": { + "type": "long" + } + } + } + } + }, + "source": { + "properties": { + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "user": { + "properties": { + "Ext": { + "properties": { + "real": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "Ext": { + "properties": { + "real": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "settings": { + "index": { + "mapping": { + "total_fields": { + "limit": "10000" + } + }, + "number_of_replicas": "0", + "number_of_shards": "1", + "refresh_interval": "-1" + } + } + } +} + +{ + "type": "index", + "value": { + "aliases": { + }, + "index": "ft_logs-endpoint.events.network-default-000001", + "mappings": { + "_meta": { + "managed": true, + "managed_by": "ingest-manager", + "package": { + "name": "endpoint" + } + }, + "date_detection": false, + "dynamic": "false", + "dynamic_templates": [ + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "agent": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "data_stream": { + "properties": { + "dataset": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "destination": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "dns": { + "properties": { + "Ext": { + "properties": { + "options": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "type": "long" + } + } + }, + "question": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "subdomain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "resolved_ip": { + "type": "ip" + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "event": { + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "created": { + "type": "date" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ingested": { + "type": "date" + }, + "kind": { + "ignore_above": 1024, + "type": "keyword" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + }, + "outcome": { + "ignore_above": 1024, + "type": "keyword" + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "sequence": { + "type": "long" + }, + "severity": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "group": { + "properties": { + "Ext": { + "properties": { + "real": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "Ext": { + "properties": { + "variant": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + } + } + }, + "http": { + "properties": { + "request": { + "properties": { + "body": { + "properties": { + "bytes": { + "type": "long" + }, + "content": { + "fields": { + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "bytes": { + "type": "long" + } + } + }, + "response": { + "properties": { + "Ext": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "body": { + "properties": { + "bytes": { + "type": "long" + }, + "content": { + "fields": { + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "bytes": { + "type": "long" + }, + "status_code": { + "type": "long" + } + } + } + } + }, + "message": { + "type": "text" + }, + "network": { + "properties": { + "bytes": { + "type": "long" + }, + "community_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "direction": { + "ignore_above": 1024, + "type": "keyword" + }, + "iana_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "packets": { + "type": "long" + }, + "protocol": { + "ignore_above": 1024, + "type": "keyword" + }, + "transport": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "process": { + "properties": { + "Ext": { + "properties": { + "ancestry": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "pid": { + "type": "long" + }, + "thread": { + "properties": { + "id": { + "type": "long" + } + } + } + } + }, + "source": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "user": { + "properties": { + "Ext": { + "properties": { + "real": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "Ext": { + "properties": { + "real": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "settings": { + "index": { + "mapping": { + "total_fields": { + "limit": "10000" + } + }, + "number_of_replicas": "0", + "number_of_shards": "1", + "refresh_interval": "-1" + } + } + } +} + +{ + "type": "index", + "value": { + "aliases": { + }, + "index": "ft_logs-endpoint.events.network-default-000002", + "mappings": { + "_meta": { + "managed": true, + "managed_by": "ingest-manager", + "package": { + "name": "endpoint" + } + }, + "date_detection": false, + "dynamic": "false", + "dynamic_templates": [ + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "agent": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "data_stream": { + "properties": { + "dataset": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "destination": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "dns": { + "properties": { + "Ext": { + "properties": { + "options": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "type": "long" + } + } + }, + "question": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "subdomain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "resolved_ip": { + "type": "ip" + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "event": { + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "created": { + "type": "date" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ingested": { + "type": "date" + }, + "kind": { + "ignore_above": 1024, + "type": "keyword" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + }, + "outcome": { + "ignore_above": 1024, + "type": "keyword" + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "sequence": { + "type": "long" + }, + "severity": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "group": { + "properties": { + "Ext": { + "properties": { + "real": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "Ext": { + "properties": { + "variant": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + } + } + }, + "http": { + "properties": { + "request": { + "properties": { + "body": { + "properties": { + "bytes": { + "type": "long" + }, + "content": { + "fields": { + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "bytes": { + "type": "long" + } + } + }, + "response": { + "properties": { + "Ext": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "body": { + "properties": { + "bytes": { + "type": "long" + }, + "content": { + "fields": { + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "bytes": { + "type": "long" + }, + "status_code": { + "type": "long" + } + } + } + } + }, + "message": { + "type": "text" + }, + "network": { + "properties": { + "bytes": { + "type": "long" + }, + "community_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "direction": { + "ignore_above": 1024, + "type": "keyword" + }, + "iana_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "packets": { + "type": "long" + }, + "protocol": { + "ignore_above": 1024, + "type": "keyword" + }, + "transport": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "process": { + "properties": { + "Ext": { + "properties": { + "ancestry": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "pid": { + "type": "long" + }, + "thread": { + "properties": { + "id": { + "type": "long" + } + } + } + } + }, + "source": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "user": { + "properties": { + "Ext": { + "properties": { + "real": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "Ext": { + "properties": { + "real": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "settings": { + "index": { + "mapping": { + "total_fields": { + "limit": "10000" + } + }, + "number_of_replicas": "0", + "number_of_shards": "1", + "refresh_interval": "-1" + } + } + } +} + +{ + "type": "index", + "value": { + "aliases": { + }, + "index": "ft_logs-endpoint.events.process-default-000001", + "mappings": { + "_meta": { + "managed": true, + "managed_by": "ingest-manager", + "package": { + "name": "endpoint" + } + }, + "date_detection": false, + "dynamic": "false", + "dynamic_templates": [ + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "agent": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "data_stream": { + "properties": { + "dataset": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "destination": { + "properties": { + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "event": { + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "created": { + "type": "date" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ingested": { + "type": "date" + }, + "kind": { + "ignore_above": 1024, + "type": "keyword" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + }, + "outcome": { + "ignore_above": 1024, + "type": "keyword" + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "sequence": { + "type": "long" + }, + "severity": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "group": { + "properties": { + "Ext": { + "properties": { + "real": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "Ext": { + "properties": { + "variant": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + } + } + }, + "message": { + "type": "text" + }, + "package": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "process": { + "properties": { + "Ext": { + "properties": { + "ancestry": { + "ignore_above": 1024, + "type": "keyword" + }, + "authentication_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + }, + "type": "nested" + }, + "session": { + "ignore_above": 1024, + "type": "keyword" + }, + "token": { + "properties": { + "elevation": { + "type": "boolean" + }, + "elevation_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "integrity_level_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "args_count": { + "type": "long" + }, + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "command_line": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "exit_code": { + "type": "long" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "parent": { + "properties": { + "Ext": { + "properties": { + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + }, + "type": "nested" + }, + "real": { + "properties": { + "pid": { + "type": "long" + } + } + } + } + }, + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "args_count": { + "type": "long" + }, + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "command_line": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "exit_code": { + "type": "long" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "pe": { + "properties": { + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pgid": { + "type": "long" + }, + "pid": { + "type": "long" + }, + "ppid": { + "type": "long" + }, + "thread": { + "properties": { + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "title": { + "fields": { + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + }, + "working_directory": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pe": { + "properties": { + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pgid": { + "type": "long" + }, + "pid": { + "type": "long" + }, + "ppid": { + "type": "long" + }, + "thread": { + "properties": { + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "title": { + "fields": { + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + }, + "working_directory": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "source": { + "properties": { + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "user": { + "properties": { + "Ext": { + "properties": { + "real": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "Ext": { + "properties": { + "real": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "settings": { + "index": { + "mapping": { + "total_fields": { + "limit": "10000" + } + }, + "number_of_replicas": "0", + "number_of_shards": "1", + "refresh_interval": "-1" + } + } + } +} + +{ + "type": "index", + "value": { + "aliases": { + }, + "index": "ft_logs-endpoint.events.process-default-000002", + "mappings": { + "_meta": { + "managed": true, + "managed_by": "ingest-manager", + "package": { + "name": "endpoint" + } + }, + "date_detection": false, + "dynamic": "false", + "dynamic_templates": [ + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "agent": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "data_stream": { + "properties": { + "dataset": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "destination": { + "properties": { + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "event": { + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "created": { + "type": "date" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ingested": { + "type": "date" + }, + "kind": { + "ignore_above": 1024, + "type": "keyword" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + }, + "outcome": { + "ignore_above": 1024, + "type": "keyword" + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "sequence": { + "type": "long" + }, + "severity": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "group": { + "properties": { + "Ext": { + "properties": { + "real": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "Ext": { + "properties": { + "variant": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + } + } + }, + "message": { + "type": "text" + }, + "package": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "process": { + "properties": { + "Ext": { + "properties": { + "ancestry": { + "ignore_above": 1024, + "type": "keyword" + }, + "authentication_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + }, + "type": "nested" + }, + "session": { + "ignore_above": 1024, + "type": "keyword" + }, + "token": { + "properties": { + "elevation": { + "type": "boolean" + }, + "elevation_type": { + "ignore_above": 1024, + "type": "keyword" + }, + "integrity_level_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "args_count": { + "type": "long" + }, + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "command_line": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "exit_code": { + "type": "long" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "parent": { + "properties": { + "Ext": { + "properties": { + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + }, + "type": "nested" + }, + "real": { + "properties": { + "pid": { + "type": "long" + } + } + } + } + }, + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "args_count": { + "type": "long" + }, + "code_signature": { + "properties": { + "exists": { + "type": "boolean" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "subject_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "trusted": { + "type": "boolean" + }, + "valid": { + "type": "boolean" + } + } + }, + "command_line": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "exit_code": { + "type": "long" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "pe": { + "properties": { + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pgid": { + "type": "long" + }, + "pid": { + "type": "long" + }, + "ppid": { + "type": "long" + }, + "thread": { + "properties": { + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "title": { + "fields": { + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + }, + "working_directory": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pe": { + "properties": { + "company": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "file_version": { + "ignore_above": 1024, + "type": "keyword" + }, + "original_file_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "pgid": { + "type": "long" + }, + "pid": { + "type": "long" + }, + "ppid": { + "type": "long" + }, + "thread": { + "properties": { + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "title": { + "fields": { + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + }, + "working_directory": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "source": { + "properties": { + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "user": { + "properties": { + "Ext": { + "properties": { + "real": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "Ext": { + "properties": { + "real": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "settings": { + "index": { + "mapping": { + "total_fields": { + "limit": "10000" + } + }, + "number_of_replicas": "0", + "number_of_shards": "1", + "refresh_interval": "-1" + } + } + } +} + +{ + "type": "index", + "value": { + "aliases": { + }, + "index": "ft_logs-endpoint.events.registry-default-000001", + "mappings": { + "_meta": { + "managed": true, + "managed_by": "ingest-manager", + "package": { + "name": "endpoint" + } + }, + "date_detection": false, + "dynamic": "false", + "dynamic_templates": [ + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "agent": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "data_stream": { + "properties": { + "dataset": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "destination": { + "properties": { + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "event": { + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "created": { + "type": "date" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ingested": { + "type": "date" + }, + "kind": { + "ignore_above": 1024, + "type": "keyword" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + }, + "outcome": { + "ignore_above": 1024, + "type": "keyword" + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "sequence": { + "type": "long" + }, + "severity": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "group": { + "properties": { + "Ext": { + "properties": { + "real": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "Ext": { + "properties": { + "variant": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + } + } + }, + "message": { + "type": "text" + }, + "process": { + "properties": { + "Ext": { + "properties": { + "ancestry": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "pid": { + "type": "long" + }, + "thread": { + "properties": { + "id": { + "type": "long" + } + } + } + } + }, + "registry": { + "properties": { + "data": { + "properties": { + "bytes": { + "ignore_above": 1024, + "type": "keyword" + }, + "strings": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hive": { + "ignore_above": 1024, + "type": "keyword" + }, + "key": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "value": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "source": { + "properties": { + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "user": { + "properties": { + "Ext": { + "properties": { + "real": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "Ext": { + "properties": { + "real": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "settings": { + "index": { + "mapping": { + "total_fields": { + "limit": "10000" + } + }, + "number_of_replicas": "0", + "number_of_shards": "1", + "refresh_interval": "-1" + } + } + } +} + +{ + "type": "index", + "value": { + "aliases": { + }, + "index": "ft_logs-endpoint.events.registry-default-000002", + "mappings": { + "_meta": { + "managed": true, + "managed_by": "ingest-manager", + "package": { + "name": "endpoint" + } + }, + "date_detection": false, + "dynamic": "false", + "dynamic_templates": [ + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "agent": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "data_stream": { + "properties": { + "dataset": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "destination": { + "properties": { + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "event": { + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "created": { + "type": "date" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ingested": { + "type": "date" + }, + "kind": { + "ignore_above": 1024, + "type": "keyword" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + }, + "outcome": { + "ignore_above": 1024, + "type": "keyword" + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "sequence": { + "type": "long" + }, + "severity": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "group": { + "properties": { + "Ext": { + "properties": { + "real": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "Ext": { + "properties": { + "variant": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + } + } + }, + "message": { + "type": "text" + }, + "process": { + "properties": { + "Ext": { + "properties": { + "ancestry": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "pid": { + "type": "long" + }, + "thread": { + "properties": { + "id": { + "type": "long" + } + } + } + } + }, + "registry": { + "properties": { + "data": { + "properties": { + "bytes": { + "ignore_above": 1024, + "type": "keyword" + }, + "strings": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hive": { + "ignore_above": 1024, + "type": "keyword" + }, + "key": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "value": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "source": { + "properties": { + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "user": { + "properties": { + "Ext": { + "properties": { + "real": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "Ext": { + "properties": { + "real": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "settings": { + "index": { + "mapping": { + "total_fields": { + "limit": "10000" + } + }, + "number_of_replicas": "0", + "number_of_shards": "1", + "refresh_interval": "-1" + } + } + } +} + +{ + "type": "index", + "value": { + "aliases": { + }, + "index": "ft_logs-endpoint.events.security-default-000001", + "mappings": { + "_meta": { + "managed": true, + "managed_by": "ingest-manager", + "package": { + "name": "endpoint" + } + }, + "date_detection": false, + "dynamic": "false", + "dynamic_templates": [ + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "agent": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "data_stream": { + "properties": { + "dataset": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "destination": { + "properties": { + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "event": { + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "created": { + "type": "date" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ingested": { + "type": "date" + }, + "kind": { + "ignore_above": 1024, + "type": "keyword" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + }, + "outcome": { + "ignore_above": 1024, + "type": "keyword" + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "sequence": { + "type": "long" + }, + "severity": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "group": { + "properties": { + "Ext": { + "properties": { + "real": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "Ext": { + "properties": { + "variant": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + } + } + }, + "message": { + "type": "text" + }, + "process": { + "properties": { + "Ext": { + "properties": { + "ancestry": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "pid": { + "type": "long" + }, + "thread": { + "properties": { + "id": { + "type": "long" + } + } + } + } + }, + "source": { + "properties": { + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "user": { + "properties": { + "Ext": { + "properties": { + "real": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "Ext": { + "properties": { + "real": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "settings": { + "index": { + "mapping": { + "total_fields": { + "limit": "10000" + } + }, + "number_of_replicas": "0", + "number_of_shards": "1", + "refresh_interval": "-1" + } + } + } +} + +{ + "type": "index", + "value": { + "aliases": { + }, + "index": "ft_logs-endpoint.events.security-default-000002", + "mappings": { + "_meta": { + "managed": true, + "managed_by": "ingest-manager", + "package": { + "name": "endpoint" + } + }, + "date_detection": false, + "dynamic": "false", + "dynamic_templates": [ + { + "strings_as_keyword": { + "mapping": { + "ignore_above": 1024, + "type": "keyword" + }, + "match_mapping_type": "string" + } + } + ], + "properties": { + "@timestamp": { + "type": "date" + }, + "agent": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "data_stream": { + "properties": { + "dataset": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "destination": { + "properties": { + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "event": { + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "created": { + "type": "date" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ingested": { + "type": "date" + }, + "kind": { + "ignore_above": 1024, + "type": "keyword" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + }, + "outcome": { + "ignore_above": 1024, + "type": "keyword" + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "sequence": { + "type": "long" + }, + "severity": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "group": { + "properties": { + "Ext": { + "properties": { + "real": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "Ext": { + "properties": { + "variant": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + } + } + }, + "message": { + "type": "text" + }, + "process": { + "properties": { + "Ext": { + "properties": { + "ancestry": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "entity_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "caseless": { + "ignore_above": 1024, + "type": "keyword" + }, + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "pid": { + "type": "long" + }, + "thread": { + "properties": { + "id": { + "type": "long" + } + } + } + } + }, + "source": { + "properties": { + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "user": { + "properties": { + "Ext": { + "properties": { + "real": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "fields": { + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "Ext": { + "properties": { + "real": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "fields": { + "text": { + "type": "text" + } + }, + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "settings": { + "index": { + "mapping": { + "total_fields": { + "limit": "10000" + } + }, + "number_of_replicas": "0", + "number_of_shards": "1", + "refresh_interval": "-1" + } + } + } +} \ No newline at end of file From 1ff233189fc2bb22dce0c4534eab345421987853 Mon Sep 17 00:00:00 2001 From: Marco Liberati Date: Mon, 14 Dec 2020 15:23:10 +0100 Subject: [PATCH 16/95] [Lens] Better disabled messages for Value labels popup (#85592) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../xy_visualization/xy_config_panel.tsx | 37 ++++++++++++++----- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx index dc6ce285754fc..cc4df1f0f9315 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx @@ -154,6 +154,28 @@ export function LayerContextMenu(props: VisualizationLayerWidgetProps) { ); } +function getValueLabelDisableReason({ + isAreaPercentage, + isHistogramSeries, +}: { + isAreaPercentage: boolean; + isHistogramSeries: boolean; +}): string { + if (isHistogramSeries) { + return i18n.translate('xpack.lens.xyChart.valuesHistogramDisabledHelpText', { + defaultMessage: 'This setting cannot be changed on histograms.', + }); + } + if (isAreaPercentage) { + return i18n.translate('xpack.lens.xyChart.valuesPercentageDisabledHelpText', { + defaultMessage: 'This setting cannot be changed on percentage area charts.', + }); + } + return i18n.translate('xpack.lens.xyChart.valuesStackedDisabledHelpText', { + defaultMessage: 'This setting cannot be changed on stacked or percentage bar charts', + }); +} + export function XyToolbar(props: VisualizationToolbarProps) { const { state, setState, frame } = props; @@ -246,20 +268,17 @@ export function XyToolbar(props: VisualizationToolbarProps) { const isValueLabelsEnabled = !hasNonBarSeries && hasBarNotStacked && !isHistogramSeries; const isFittingEnabled = hasNonBarSeries; + const valueLabelsDisabledReason = getValueLabelDisableReason({ + isAreaPercentage, + isHistogramSeries, + }); + return ( Date: Mon, 14 Dec 2020 16:16:28 +0100 Subject: [PATCH 17/95] Added transaction_ignore_urls as central config for the Python, .NET and Ruby APM agents (#85734) --- .../setting_definitions/general_settings.ts | 2 +- .../agent_configuration/setting_definitions/index.test.ts | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/general_settings.ts b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/general_settings.ts index d3063c9715992..90e98e64814a1 100644 --- a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/general_settings.ts +++ b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/general_settings.ts @@ -254,6 +254,6 @@ export const generalSettings: RawSettingDefinition[] = [ 'Used to restrict requests to certain URLs from being instrumented. This config accepts a comma-separated list of wildcard patterns of URL paths that should be ignored. When an incoming HTTP request is detected, its request path will be tested against each element in this list. For example, adding `/home/index` to this list would match and remove instrumentation from `http://localhost/home/index` as well as `http://whatever.com/home/index?value1=123`', } ), - includeAgents: ['java', 'nodejs'], + includeAgents: ['java', 'nodejs', 'python', 'dotnet', 'ruby'], }, ]; diff --git a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/index.test.ts b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/index.test.ts index e0e7e84810090..88cf3e288abf1 100644 --- a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/index.test.ts +++ b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/index.test.ts @@ -120,6 +120,7 @@ describe('filterByAgent', () => { 'recording', 'sanitize_field_names', 'span_frames_min_duration', + 'transaction_ignore_urls', 'transaction_max_spans', 'transaction_sample_rate', ]); @@ -134,6 +135,7 @@ describe('filterByAgent', () => { 'sanitize_field_names', 'span_frames_min_duration', 'stack_trace_limit', + 'transaction_ignore_urls', 'transaction_max_spans', 'transaction_sample_rate', ]); @@ -148,6 +150,7 @@ describe('filterByAgent', () => { 'log_level', 'recording', 'span_frames_min_duration', + 'transaction_ignore_urls', 'transaction_max_spans', 'transaction_sample_rate', ]); From 0dfcbe92ed0fb7eabd18e9416a63ab406965c5f0 Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Mon, 14 Dec 2020 10:33:59 -0500 Subject: [PATCH 18/95] [SECURITY SOLUTIONS] Ask user to save timeline before leaving the app + bugs (#85693) * fix clicking on host on netwrok detail page * Fetch signal index at plugin level to avoid weird behavior * bing back full screen timeline * Show health check on timeline * fix focus on modal of description and title * fix focus on modal of description and title * allow to know the next appId * if user leave security solution and timeline has not been saved, ask them if they want to save it before leaving * fix test + types * Fix siem signal loading on plugin + UX on timeline with no data * Add a callback to cleaner from solution + test * fix bug + improve prompt leaving msg * update note * css improvements * fix code to be true to our test * miss one test * update test * fix unit test * core review Co-authored-by: Patryk Kopycinski Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Angela Chuang --- ...e-public.appleaveconfirmaction.callback.md | 11 + ...lugin-core-public.appleaveconfirmaction.md | 1 + ...bana-plugin-core-public.appleavehandler.md | 2 +- .../application/application_leave.test.ts | 20 ++ .../public/application/application_leave.tsx | 8 +- .../application/application_service.test.ts | 13 ++ .../application/application_service.tsx | 14 +- src/core/public/application/types.ts | 10 +- src/core/public/public.api.md | 4 +- .../cypress/screens/timeline.ts | 3 +- .../security_solution/public/app/app.tsx | 19 +- .../public/app/home/index.tsx | 6 +- .../security_solution/public/app/index.tsx | 9 +- .../security_solution/public/app/routes.tsx | 12 +- .../common/components/alerts_viewer/index.tsx | 4 +- .../events_viewer/events_viewer.tsx | 6 +- .../common/components/events_viewer/index.tsx | 4 +- .../components/exit_full_screen/index.tsx | 4 +- .../common/components/header_global/index.tsx | 5 +- .../common/components/wrapper_page/index.tsx | 4 +- .../common/containers/sourcerer/index.tsx | 52 ++++- .../containers/use_full_screen/index.tsx | 37 +++- .../public/common/store/reducer.test.ts | 2 + .../public/common/store/reducer.ts | 8 +- .../detection_engine/detection_engine.tsx | 4 +- .../detection_engine/rules/details/index.tsx | 4 +- .../public/hosts/pages/details/index.tsx | 4 +- .../public/hosts/pages/hosts.tsx | 4 +- .../navigation/events_query_tab_body.tsx | 4 +- .../public/network/pages/network.tsx | 4 +- .../security_solution/public/plugin.tsx | 11 + .../flyout/__snapshots__/index.test.tsx.snap | 1 + .../flyout/header/active_timelines.tsx | 55 ++++- .../components/flyout/header/index.tsx | 60 ++++-- .../components/flyout/header/selectors.ts | 16 ++ .../components/flyout/index.test.tsx | 12 +- .../timelines/components/flyout/index.tsx | 65 +++++- .../timelines/components/flyout/selectors.ts | 17 ++ .../components/graph_overlay/index.test.tsx | 32 ++- .../components/graph_overlay/index.tsx | 13 +- .../components/open_timeline/helpers.ts | 16 +- .../timeline/body/column_headers/index.tsx | 25 ++- .../components/timeline/footer/index.tsx | 7 +- .../header/save_timeline_button.test.tsx | 76 ++++--- .../timeline/header/save_timeline_button.tsx | 58 +++-- .../components/timeline/header/selectors.ts | 12 ++ .../header/title_and_description.test.tsx | 68 +++--- .../timeline/header/title_and_description.tsx | 204 ++++++++++-------- .../timelines/components/timeline/index.tsx | 8 +- .../timeline/properties/helpers.test.tsx | 13 -- .../timeline/properties/helpers.tsx | 61 ++---- .../properties/use_create_timeline.tsx | 4 +- .../timeline/query_tab_content/index.test.tsx | 11 +- .../timeline/query_tab_content/index.tsx | 107 ++++----- .../timelines/components/timeline/styles.tsx | 11 +- .../public/timelines/containers/index.tsx | 1 - .../timelines/store/timeline/actions.ts | 5 + .../public/timelines/store/timeline/model.ts | 1 + .../timelines/store/timeline/reducer.ts | 11 + .../network/details/__mocks__/index.ts | 57 ++++- .../factory/network/details/helpers.ts | 22 +- .../factory/network/details/index.test.ts | 2 +- .../apis/security_solution/network_details.ts | 11 +- 63 files changed, 886 insertions(+), 469 deletions(-) create mode 100644 docs/development/core/public/kibana-plugin-core-public.appleaveconfirmaction.callback.md create mode 100644 x-pack/plugins/security_solution/public/timelines/components/flyout/header/selectors.ts create mode 100644 x-pack/plugins/security_solution/public/timelines/components/flyout/selectors.ts create mode 100644 x-pack/plugins/security_solution/public/timelines/components/timeline/header/selectors.ts diff --git a/docs/development/core/public/kibana-plugin-core-public.appleaveconfirmaction.callback.md b/docs/development/core/public/kibana-plugin-core-public.appleaveconfirmaction.callback.md new file mode 100644 index 0000000000000..8ebc9068aa612 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.appleaveconfirmaction.callback.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [AppLeaveConfirmAction](./kibana-plugin-core-public.appleaveconfirmaction.md) > [callback](./kibana-plugin-core-public.appleaveconfirmaction.callback.md) + +## AppLeaveConfirmAction.callback property + +Signature: + +```typescript +callback?: () => void; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.appleaveconfirmaction.md b/docs/development/core/public/kibana-plugin-core-public.appleaveconfirmaction.md index 969d5ddd44c3e..8650cd9868940 100644 --- a/docs/development/core/public/kibana-plugin-core-public.appleaveconfirmaction.md +++ b/docs/development/core/public/kibana-plugin-core-public.appleaveconfirmaction.md @@ -18,6 +18,7 @@ export interface AppLeaveConfirmAction | Property | Type | Description | | --- | --- | --- | +| [callback](./kibana-plugin-core-public.appleaveconfirmaction.callback.md) | () => void | | | [text](./kibana-plugin-core-public.appleaveconfirmaction.text.md) | string | | | [title](./kibana-plugin-core-public.appleaveconfirmaction.title.md) | string | | | [type](./kibana-plugin-core-public.appleaveconfirmaction.type.md) | AppLeaveActionType.confirm | | diff --git a/docs/development/core/public/kibana-plugin-core-public.appleavehandler.md b/docs/development/core/public/kibana-plugin-core-public.appleavehandler.md index a5f8336f6424a..d86f7b7a1a5f9 100644 --- a/docs/development/core/public/kibana-plugin-core-public.appleavehandler.md +++ b/docs/development/core/public/kibana-plugin-core-public.appleavehandler.md @@ -11,5 +11,5 @@ See [AppMountParameters](./kibana-plugin-core-public.appmountparameters.md) for Signature: ```typescript -export declare type AppLeaveHandler = (factory: AppLeaveActionFactory) => AppLeaveAction; +export declare type AppLeaveHandler = (factory: AppLeaveActionFactory, nextAppId?: string) => AppLeaveAction; ``` diff --git a/src/core/public/application/application_leave.test.ts b/src/core/public/application/application_leave.test.ts index b560bbc0cbc25..9d0da6fe0096d 100644 --- a/src/core/public/application/application_leave.test.ts +++ b/src/core/public/application/application_leave.test.ts @@ -35,7 +35,18 @@ describe('getLeaveAction', () => { type: AppLeaveActionType.default, }); }); + + it('returns the default action provided by the handle and nextAppId', () => { + expect(getLeaveAction((actions) => actions.default(), 'futureAppId')).toEqual({ + type: AppLeaveActionType.default, + }); + }); + it('returns the confirm action provided by the handler', () => { + expect(getLeaveAction((actions) => actions.confirm('some message'), 'futureAppId')).toEqual({ + type: AppLeaveActionType.confirm, + text: 'some message', + }); expect(getLeaveAction((actions) => actions.confirm('some message'))).toEqual({ type: AppLeaveActionType.confirm, text: 'some message', @@ -45,5 +56,14 @@ describe('getLeaveAction', () => { text: 'another message', title: 'a title', }); + const callback = jest.fn(); + expect( + getLeaveAction((actions) => actions.confirm('another message', 'a title', callback)) + ).toEqual({ + type: AppLeaveActionType.confirm, + text: 'another message', + title: 'a title', + callback, + }); }); }); diff --git a/src/core/public/application/application_leave.tsx b/src/core/public/application/application_leave.tsx index 7b69d70d3f6f6..e6170daaff0a0 100644 --- a/src/core/public/application/application_leave.tsx +++ b/src/core/public/application/application_leave.tsx @@ -26,8 +26,8 @@ import { } from './types'; const appLeaveActionFactory: AppLeaveActionFactory = { - confirm(text: string, title?: string) { - return { type: AppLeaveActionType.confirm, text, title }; + confirm(text: string, title?: string, callback?: () => void) { + return { type: AppLeaveActionType.confirm, text, title, callback }; }, default() { return { type: AppLeaveActionType.default }; @@ -38,9 +38,9 @@ export function isConfirmAction(action: AppLeaveAction): action is AppLeaveConfi return action.type === AppLeaveActionType.confirm; } -export function getLeaveAction(handler?: AppLeaveHandler): AppLeaveAction { +export function getLeaveAction(handler?: AppLeaveHandler, nextAppId?: string): AppLeaveAction { if (!handler) { return appLeaveActionFactory.default(); } - return handler(appLeaveActionFactory); + return handler(appLeaveActionFactory, nextAppId); } diff --git a/src/core/public/application/application_service.test.ts b/src/core/public/application/application_service.test.ts index cd186f87b3a87..912ab40cbe1db 100644 --- a/src/core/public/application/application_service.test.ts +++ b/src/core/public/application/application_service.test.ts @@ -755,6 +755,19 @@ describe('#start()', () => { `); }); + it('should call private function shouldNavigate with overlays and the nextAppId', async () => { + service.setup(setupDeps); + const shouldNavigateSpy = jest.spyOn(service as any, 'shouldNavigate'); + + const { navigateToApp } = await service.start(startDeps); + + await navigateToApp('myTestApp'); + expect(shouldNavigateSpy).toHaveBeenCalledWith(startDeps.overlays, 'myTestApp'); + + await navigateToApp('myOtherApp'); + expect(shouldNavigateSpy).toHaveBeenCalledWith(startDeps.overlays, 'myOtherApp'); + }); + describe('when `replace` option is true', () => { it('use `history.replace` instead of `history.push`', async () => { service.setup(setupDeps); diff --git a/src/core/public/application/application_service.tsx b/src/core/public/application/application_service.tsx index 4d54d4831698b..67281170957c6 100644 --- a/src/core/public/application/application_service.tsx +++ b/src/core/public/application/application_service.tsx @@ -244,7 +244,9 @@ export class ApplicationService { ) => { const currentAppId = this.currentAppId$.value; const navigatingToSameApp = currentAppId === appId; - const shouldNavigate = navigatingToSameApp ? true : await this.shouldNavigate(overlays); + const shouldNavigate = navigatingToSameApp + ? true + : await this.shouldNavigate(overlays, appId); if (shouldNavigate) { if (path === undefined) { @@ -332,18 +334,24 @@ export class ApplicationService { this.currentActionMenu$.next(currentActionMenu); }; - private async shouldNavigate(overlays: OverlayStart): Promise { + private async shouldNavigate(overlays: OverlayStart, nextAppId: string): Promise { const currentAppId = this.currentAppId$.value; if (currentAppId === undefined) { return true; } - const action = getLeaveAction(this.appInternalStates.get(currentAppId)?.leaveHandler); + const action = getLeaveAction( + this.appInternalStates.get(currentAppId)?.leaveHandler, + nextAppId + ); if (isConfirmAction(action)) { const confirmed = await overlays.openConfirm(action.text, { title: action.title, 'data-test-subj': 'appLeaveConfirmModal', }); if (!confirmed) { + if (action.callback) { + setTimeout(action.callback, 0); + } return false; } } diff --git a/src/core/public/application/types.ts b/src/core/public/application/types.ts index d9f326c7a59ab..c161a7f166541 100644 --- a/src/core/public/application/types.ts +++ b/src/core/public/application/types.ts @@ -578,7 +578,10 @@ export interface AppMountParameters { * * @public */ -export type AppLeaveHandler = (factory: AppLeaveActionFactory) => AppLeaveAction; +export type AppLeaveHandler = ( + factory: AppLeaveActionFactory, + nextAppId?: string +) => AppLeaveAction; /** * Possible type of actions on application leave. @@ -614,6 +617,7 @@ export interface AppLeaveConfirmAction { type: AppLeaveActionType.confirm; text: string; title?: string; + callback?: () => void; } /** @@ -636,8 +640,10 @@ export interface AppLeaveActionFactory { * * @param text The text to display in the confirmation message * @param title (optional) title to display in the confirmation message + * @param callback (optional) to know that the user want to stay on the page + * so we can show to the user the right UX for him to saved his/her/their changes */ - confirm(text: string, title?: string): AppLeaveConfirmAction; + confirm(text: string, title?: string, callback?: () => void): AppLeaveConfirmAction; /** * Returns a default action, resulting on executing the default behavior when * the user tries to leave an application diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index f48fb9092d56d..3852792547062 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -91,6 +91,8 @@ export enum AppLeaveActionType { // // @public export interface AppLeaveConfirmAction { + // (undocumented) + callback?: () => void; // (undocumented) text: string; // (undocumented) @@ -110,7 +112,7 @@ export interface AppLeaveDefaultAction { // Warning: (ae-forgotten-export) The symbol "AppLeaveActionFactory" needs to be exported by the entry point index.d.ts // // @public -export type AppLeaveHandler = (factory: AppLeaveActionFactory) => AppLeaveAction; +export type AppLeaveHandler = (factory: AppLeaveActionFactory, nextAppId?: string) => AppLeaveAction; // @public (undocumented) export interface ApplicationSetup { diff --git a/x-pack/plugins/security_solution/cypress/screens/timeline.ts b/x-pack/plugins/security_solution/cypress/screens/timeline.ts index 9397307684d6a..0f5e8c133f0d0 100644 --- a/x-pack/plugins/security_solution/cypress/screens/timeline.ts +++ b/x-pack/plugins/security_solution/cypress/screens/timeline.ts @@ -40,7 +40,8 @@ export const GRAPH_TAB_BUTTON = '[data-test-subj="timelineTabs-graph"]'; export const HEADER = '[data-test-subj="header"]'; -export const HEADERS_GROUP = '[data-test-subj="headers-group"]'; +export const HEADERS_GROUP = + '[data-test-subj="events-viewer-panel"] [data-test-subj="headers-group"]'; export const ID_HEADER_FIELD = '[data-test-subj="timeline"] [data-test-subj="header-text-_id"]'; diff --git a/x-pack/plugins/security_solution/public/app/app.tsx b/x-pack/plugins/security_solution/public/app/app.tsx index 54b02c374e43f..ffed557f28511 100644 --- a/x-pack/plugins/security_solution/public/app/app.tsx +++ b/x-pack/plugins/security_solution/public/app/app.tsx @@ -14,6 +14,7 @@ import { ThemeProvider } from 'styled-components'; import { EuiErrorBoundary } from '@elastic/eui'; import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; +import { AppLeaveHandler } from '../../../../../src/core/public'; import { ManageUserInfo } from '../detections/components/user_info'; import { DEFAULT_DARK_MODE, APP_NAME } from '../../common/constants'; @@ -28,13 +29,21 @@ import { ApolloClientContext } from '../common/utils/apollo_context'; import { ManageGlobalTimeline } from '../timelines/components/manage_timeline'; import { StartServices } from '../types'; import { PageRouter } from './routes'; + interface StartAppComponent extends AppFrontendLibs { children: React.ReactNode; history: History; + onAppLeave: (handler: AppLeaveHandler) => void; store: Store; } -const StartAppComponent: FC = ({ children, apolloClient, history, store }) => { +const StartAppComponent: FC = ({ + children, + apolloClient, + history, + onAppLeave, + store, +}) => { const { i18n } = useKibana().services; const [darkMode] = useUiSetting$(DEFAULT_DARK_MODE); @@ -57,7 +66,9 @@ const StartAppComponent: FC = ({ children, apolloClient, hist - {children} + + {children} + @@ -78,6 +89,7 @@ const StartApp = memo(StartAppComponent); interface SecurityAppComponentProps extends AppFrontendLibs { children: React.ReactNode; history: History; + onAppLeave: (handler: AppLeaveHandler) => void; services: StartServices; store: Store; } @@ -86,6 +98,7 @@ const SecurityAppComponent: React.FC = ({ children, apolloClient, history, + onAppLeave, services, store, }) => ( @@ -95,7 +108,7 @@ const SecurityAppComponent: React.FC = ({ ...services, }} > - + {children} diff --git a/x-pack/plugins/security_solution/public/app/home/index.tsx b/x-pack/plugins/security_solution/public/app/home/index.tsx index 3b64c1f7f1f65..30c4e87f695b2 100644 --- a/x-pack/plugins/security_solution/public/app/home/index.tsx +++ b/x-pack/plugins/security_solution/public/app/home/index.tsx @@ -23,6 +23,7 @@ import { DETECTIONS_SUB_PLUGIN_ID } from '../../../common/constants'; import { SourcererScopeName } from '../../common/store/sourcerer/model'; import { useUpgradeEndpointPackage } from '../../common/hooks/endpoint/upgrade'; import { useThrottledResizeObserver } from '../../common/components/utils'; +import { AppLeaveHandler } from '../../../../../../src/core/public'; const Main = styled.main.attrs<{ paddingTop: number }>(({ paddingTop }) => ({ style: { @@ -39,9 +40,10 @@ Main.displayName = 'Main'; interface HomePageProps { children: React.ReactNode; + onAppLeave: (handler: AppLeaveHandler) => void; } -const HomePageComponent: React.FC = ({ children }) => { +const HomePageComponent: React.FC = ({ children, onAppLeave }) => { const { application, overlays } = useKibana().services; const subPluginId = useRef(''); const { ref, height = 0 } = useThrottledResizeObserver(300); @@ -87,7 +89,7 @@ const HomePageComponent: React.FC = ({ children }) => { {indicesExist && showTimeline && ( <> - + )} diff --git a/x-pack/plugins/security_solution/public/app/index.tsx b/x-pack/plugins/security_solution/public/app/index.tsx index 4c8e87c4abfba..d45c5393c01d6 100644 --- a/x-pack/plugins/security_solution/public/app/index.tsx +++ b/x-pack/plugins/security_solution/public/app/index.tsx @@ -14,12 +14,19 @@ export const renderApp = ({ apolloClient, element, history, + onAppLeave, services, store, SubPluginRoutes, }: RenderAppProps): (() => void) => { render( - + , element diff --git a/x-pack/plugins/security_solution/public/app/routes.tsx b/x-pack/plugins/security_solution/public/app/routes.tsx index 1d3a59856caa9..ed6d1f319b7e6 100644 --- a/x-pack/plugins/security_solution/public/app/routes.tsx +++ b/x-pack/plugins/security_solution/public/app/routes.tsx @@ -7,20 +7,22 @@ import { History } from 'history'; import React, { FC, memo, useEffect } from 'react'; import { Route, Router, Switch } from 'react-router-dom'; - import { useDispatch } from 'react-redux'; -import { NotFoundPage } from './404'; -import { HomePage } from './home'; + +import { AppLeaveHandler } from '../../../../../src/core/public'; import { ManageRoutesSpy } from '../common/utils/route/manage_spy_routes'; import { RouteCapture } from '../common/components/endpoint/route_capture'; import { AppAction } from '../common/store/actions'; +import { NotFoundPage } from './404'; +import { HomePage } from './home'; interface RouterProps { children: React.ReactNode; history: History; + onAppLeave: (handler: AppLeaveHandler) => void; } -const PageRouterComponent: FC = ({ history, children }) => { +const PageRouterComponent: FC = ({ children, history, onAppLeave }) => { const dispatch = useDispatch<(action: AppAction) => void>(); useEffect(() => { return () => { @@ -39,7 +41,7 @@ const PageRouterComponent: FC = ({ history, children }) => { - {children} + {children} diff --git a/x-pack/plugins/security_solution/public/common/components/alerts_viewer/index.tsx b/x-pack/plugins/security_solution/public/common/components/alerts_viewer/index.tsx index 0dcd29a2d965b..52ab414811cec 100644 --- a/x-pack/plugins/security_solution/public/common/components/alerts_viewer/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/alerts_viewer/index.tsx @@ -7,7 +7,7 @@ import React, { useEffect, useCallback, useMemo } from 'react'; import numeral from '@elastic/numeral'; import { DEFAULT_NUMBER_FORMAT } from '../../../../common/constants'; -import { useFullScreen } from '../../containers/use_full_screen'; +import { useGlobalFullScreen } from '../../containers/use_full_screen'; import { AlertsComponentsProps } from './types'; import { AlertsTable } from './alerts_table'; @@ -30,7 +30,7 @@ const AlertsViewComponent: React.FC = ({ startDate, }) => { const [defaultNumberFormat] = useUiSetting$(DEFAULT_NUMBER_FORMAT); - const { globalFullScreen } = useFullScreen(); + const { globalFullScreen } = useGlobalFullScreen(); const getSubtitle = useCallback( (totalCount: number) => diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx index d6b2efbe43053..4aa8361a0b8e4 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx @@ -36,7 +36,7 @@ import { import { inputsModel } from '../../store'; import { useManageTimeline } from '../../../timelines/components/manage_timeline'; import { ExitFullScreen } from '../exit_full_screen'; -import { useFullScreen } from '../../containers/use_full_screen'; +import { useGlobalFullScreen } from '../../containers/use_full_screen'; import { TimelineExpandedEvent, TimelineId } from '../../../../common/types/timeline'; import { GraphOverlay } from '../../../timelines/components/graph_overlay'; @@ -150,7 +150,7 @@ const EventsViewerComponent: React.FC = ({ graphEventId, }) => { const dispatch = useDispatch(); - const { globalFullScreen, timelineFullScreen } = useFullScreen(); + const { globalFullScreen } = useGlobalFullScreen(); const columnsHeader = isEmpty(columns) ? defaultHeaders : columns; const kibana = useKibana(); const [isQueryLoading, setIsQueryLoading] = useState(false); @@ -286,7 +286,7 @@ const EventsViewerComponent: React.FC = ({ id={!resolverIsShowing(graphEventId) ? id : undefined} height={headerFilterGroup ? COMPACT_HEADER_HEIGHT : EVENTS_VIEWER_HEADER_HEIGHT} subtitle={utilityBar ? undefined : subtitle} - title={timelineFullScreen ? justTitle : titleWithExitFullScreen} + title={globalFullScreen ? titleWithExitFullScreen : justTitle} > {HeaderSectionContent} diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx index 2570a2b6d1f37..3272b0306f9c9 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx @@ -16,7 +16,7 @@ import { SubsetTimelineModel, TimelineModel } from '../../../timelines/store/tim import { Filter } from '../../../../../../../src/plugins/data/public'; import { EventsViewer } from './events_viewer'; import { InspectButtonContainer } from '../inspect'; -import { useFullScreen } from '../../containers/use_full_screen'; +import { useGlobalFullScreen } from '../../containers/use_full_screen'; import { SourcererScopeName } from '../../store/sourcerer/model'; import { useSourcererScope } from '../../containers/sourcerer'; import { EventDetailsFlyout } from './event_details_flyout'; @@ -78,7 +78,7 @@ const StatefulEventsViewerComponent: React.FC = ({ selectedPatterns, loading: isLoadingIndexPattern, } = useSourcererScope(scopeId); - const { globalFullScreen } = useFullScreen(); + const { globalFullScreen } = useGlobalFullScreen(); useEffect(() => { if (createTimeline != null) { diff --git a/x-pack/plugins/security_solution/public/common/components/exit_full_screen/index.tsx b/x-pack/plugins/security_solution/public/common/components/exit_full_screen/index.tsx index cd4740bc8c464..12e7df9a11302 100644 --- a/x-pack/plugins/security_solution/public/common/components/exit_full_screen/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exit_full_screen/index.tsx @@ -8,7 +8,7 @@ import { EuiButton, EuiWindowEvent } from '@elastic/eui'; import React, { useCallback } from 'react'; import styled from 'styled-components'; -import { useFullScreen } from '../../../common/containers/use_full_screen'; +import { useGlobalFullScreen } from '../../../common/containers/use_full_screen'; import * as i18n from './translations'; @@ -17,7 +17,7 @@ const StyledEuiButton = styled(EuiButton)` `; export const ExitFullScreen: React.FC = () => { - const { globalFullScreen, setGlobalFullScreen } = useFullScreen(); + const { globalFullScreen, setGlobalFullScreen } = useGlobalFullScreen(); const exitFullScreen = useCallback(() => { setGlobalFullScreen(false); diff --git a/x-pack/plugins/security_solution/public/common/components/header_global/index.tsx b/x-pack/plugins/security_solution/public/common/components/header_global/index.tsx index 7e8c93e86376a..e8a17d78644df 100644 --- a/x-pack/plugins/security_solution/public/common/components/header_global/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/header_global/index.tsx @@ -11,7 +11,7 @@ import styled from 'styled-components'; import { OutPortal } from 'react-reverse-portal'; import { navTabs } from '../../../app/home/home_navigations'; -import { useFullScreen } from '../../containers/use_full_screen'; +import { useGlobalFullScreen, useTimelineFullScreen } from '../../containers/use_full_screen'; import { SecurityPageName } from '../../../app/types'; import { getAppOverviewUrl } from '../link_to'; import { MlPopover } from '../ml_popover/ml_popover'; @@ -68,7 +68,8 @@ export const HeaderGlobal = React.memo( forwardRef( ({ hideDetectionEngine = false, isFixed = true }, ref) => { const { globalHeaderPortalNode } = useGlobalHeaderPortal(); - const { globalFullScreen, timelineFullScreen } = useFullScreen(); + const { globalFullScreen } = useGlobalFullScreen(); + const { timelineFullScreen } = useTimelineFullScreen(); const search = useGetUrlSearch(navTabs.overview); const { application, http } = useKibana().services; const { navigateToApp } = application; diff --git a/x-pack/plugins/security_solution/public/common/components/wrapper_page/index.tsx b/x-pack/plugins/security_solution/public/common/components/wrapper_page/index.tsx index 23f9a8a6bce01..e1f6310644be0 100644 --- a/x-pack/plugins/security_solution/public/common/components/wrapper_page/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/wrapper_page/index.tsx @@ -9,7 +9,7 @@ import React, { useEffect } from 'react'; import styled from 'styled-components'; import { CommonProps } from '@elastic/eui'; -import { useFullScreen } from '../../containers/use_full_screen'; +import { useGlobalFullScreen } from '../../containers/use_full_screen'; import { gutterTimeline } from '../../lib/helpers'; import { AppGlobalStyle } from '../page/index'; @@ -53,7 +53,7 @@ const WrapperPageComponent: React.FC = ({ noTimeline, ...otherProps }) => { - const { globalFullScreen, setGlobalFullScreen } = useFullScreen(); + const { globalFullScreen, setGlobalFullScreen } = useGlobalFullScreen(); useEffect(() => { setGlobalFullScreen(false); // exit full screen mode on page load }, [setGlobalFullScreen]); diff --git a/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.tsx b/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.tsx index b7938a5f3d755..577d7aa78e35c 100644 --- a/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { useEffect, useMemo } from 'react'; +import { useEffect, useMemo, useRef } from 'react'; import { useDispatch } from 'react-redux'; import { sourcererActions, sourcererSelectors } from '../../store/sourcerer'; @@ -19,7 +19,8 @@ export const useInitSourcerer = ( scopeId: SourcererScopeName.default | SourcererScopeName.detections = SourcererScopeName.default ) => { const dispatch = useDispatch(); - + const initialTimelineSourcerer = useRef(true); + const initialDetectionSourcerer = useRef(true); const { loading: loadingSignalIndex, isSignalIndexExists, signalIndexName } = useUserInfo(); const getConfigIndexPatternsSelector = useMemo( () => sourcererSelectors.configIndexPatternsSelector(), @@ -27,6 +28,12 @@ export const useInitSourcerer = ( ); const ConfigIndexPatterns = useDeepEqualSelector(getConfigIndexPatternsSelector); + const getSignalIndexNameSelector = useMemo( + () => sourcererSelectors.signalIndexNameSelector(), + [] + ); + const signalIndexNameSelector = useDeepEqualSelector(getSignalIndexNameSelector); + const getTimelineSelector = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []); const activeTimeline = useDeepEqualSelector((state) => getTimelineSelector(state, TimelineId.active) @@ -36,42 +43,71 @@ export const useInitSourcerer = ( useIndexFields(SourcererScopeName.timeline); useEffect(() => { - if (!loadingSignalIndex && signalIndexName != null) { + if (!loadingSignalIndex && signalIndexName != null && signalIndexNameSelector == null) { dispatch(sourcererActions.setSignalIndexName({ signalIndexName })); } - }, [dispatch, loadingSignalIndex, signalIndexName]); + }, [dispatch, loadingSignalIndex, signalIndexName, signalIndexNameSelector]); // Related to timeline useEffect(() => { if ( !loadingSignalIndex && signalIndexName != null && - (activeTimeline == null || (activeTimeline != null && activeTimeline.savedObjectId == null)) + signalIndexNameSelector == null && + (activeTimeline == null || + (activeTimeline != null && activeTimeline.savedObjectId == null)) && + initialTimelineSourcerer.current ) { + initialTimelineSourcerer.current = false; dispatch( sourcererActions.setSelectedIndexPatterns({ id: SourcererScopeName.timeline, selectedPatterns: [...ConfigIndexPatterns, signalIndexName], }) ); + } else if (signalIndexNameSelector != null && initialTimelineSourcerer.current) { + initialTimelineSourcerer.current = false; + dispatch( + sourcererActions.setSelectedIndexPatterns({ + id: SourcererScopeName.timeline, + selectedPatterns: [...ConfigIndexPatterns, signalIndexNameSelector], + }) + ); } - }, [activeTimeline, ConfigIndexPatterns, dispatch, loadingSignalIndex, signalIndexName]); + }, [ + activeTimeline, + ConfigIndexPatterns, + dispatch, + loadingSignalIndex, + signalIndexName, + signalIndexNameSelector, + ]); // Related to the detection page useEffect(() => { if ( scopeId === SourcererScopeName.detections && isSignalIndexExists && - signalIndexName != null + signalIndexName != null && + initialDetectionSourcerer.current ) { + initialDetectionSourcerer.current = false; dispatch( sourcererActions.setSelectedIndexPatterns({ id: scopeId, selectedPatterns: [signalIndexName], }) ); + } else if (signalIndexNameSelector != null && initialTimelineSourcerer.current) { + initialDetectionSourcerer.current = false; + dispatch( + sourcererActions.setSelectedIndexPatterns({ + id: scopeId, + selectedPatterns: [signalIndexNameSelector], + }) + ); } - }, [dispatch, isSignalIndexExists, scopeId, signalIndexName]); + }, [dispatch, isSignalIndexExists, scopeId, signalIndexName, signalIndexNameSelector]); }; export const useSourcererScope = (scope: SourcererScopeName = SourcererScopeName.default) => { diff --git a/x-pack/plugins/security_solution/public/common/containers/use_full_screen/index.tsx b/x-pack/plugins/security_solution/public/common/containers/use_full_screen/index.tsx index 8357a9d22739e..874005bf07428 100644 --- a/x-pack/plugins/security_solution/public/common/containers/use_full_screen/index.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/use_full_screen/index.tsx @@ -28,13 +28,20 @@ export const resetScroll = () => { }, 0); }; -export const useFullScreen = () => { +interface GlobalFullScreen { + globalFullScreen: boolean; + setGlobalFullScreen: (fullScreen: boolean) => void; +} + +interface TimelineFullScreen { + timelineFullScreen: boolean; + setTimelineFullScreen: (fullScreen: boolean) => void; +} + +export const useGlobalFullScreen = (): GlobalFullScreen => { const dispatch = useDispatch(); const globalFullScreen = useShallowEqualSelector(inputsSelectors.globalFullScreenSelector) ?? false; - const timelineFullScreen = - useShallowEqualSelector(inputsSelectors.timelineFullScreenSelector) ?? false; - const setGlobalFullScreen = useCallback( (fullScreen: boolean) => { if (fullScreen) { @@ -49,21 +56,31 @@ export const useFullScreen = () => { }, [dispatch] ); + const memoizedReturn = useMemo( + () => ({ + globalFullScreen, + setGlobalFullScreen, + }), + [globalFullScreen, setGlobalFullScreen] + ); + return memoizedReturn; +}; + +export const useTimelineFullScreen = (): TimelineFullScreen => { + const dispatch = useDispatch(); + const timelineFullScreen = + useShallowEqualSelector(inputsSelectors.timelineFullScreenSelector) ?? false; const setTimelineFullScreen = useCallback( (fullScreen: boolean) => dispatch(inputsActions.setFullScreen({ id: 'timeline', fullScreen })), [dispatch] ); - const memoizedReturn = useMemo( () => ({ - globalFullScreen, - setGlobalFullScreen, - setTimelineFullScreen, timelineFullScreen, + setTimelineFullScreen, }), - [globalFullScreen, setGlobalFullScreen, setTimelineFullScreen, timelineFullScreen] + [timelineFullScreen, setTimelineFullScreen] ); - return memoizedReturn; }; diff --git a/x-pack/plugins/security_solution/public/common/store/reducer.test.ts b/x-pack/plugins/security_solution/public/common/store/reducer.test.ts index 3e47478b783eb..fcf7dfec0f2a4 100644 --- a/x-pack/plugins/security_solution/public/common/store/reducer.test.ts +++ b/x-pack/plugins/security_solution/public/common/store/reducer.test.ts @@ -20,6 +20,7 @@ describe('createInitialState', () => { { kibanaIndexPatterns: [{ id: '1234567890987654321', title: 'mock-kibana' }], configIndexPatterns: ['auditbeat-*', 'filebeat'], + signalIndexName: 'siem-signals-default', } ); @@ -32,6 +33,7 @@ describe('createInitialState', () => { { kibanaIndexPatterns: [{ id: '1234567890987654321', title: 'mock-kibana' }], configIndexPatterns: [], + signalIndexName: 'siem-signals-default', } ); diff --git a/x-pack/plugins/security_solution/public/common/store/reducer.ts b/x-pack/plugins/security_solution/public/common/store/reducer.ts index 8d528f4279955..f48bf31e62575 100644 --- a/x-pack/plugins/security_solution/public/common/store/reducer.ts +++ b/x-pack/plugins/security_solution/public/common/store/reducer.ts @@ -34,7 +34,12 @@ export const createInitialState = ( { kibanaIndexPatterns, configIndexPatterns, - }: { kibanaIndexPatterns: KibanaIndexPatterns; configIndexPatterns: string[] } + signalIndexName, + }: { + kibanaIndexPatterns: KibanaIndexPatterns; + configIndexPatterns: string[]; + signalIndexName: string | null; + } ): PreloadedState => { const preloadedState: PreloadedState = { app: initialAppState, @@ -52,6 +57,7 @@ export const createInitialState = ( }, kibanaIndexPatterns, configIndexPatterns, + signalIndexName, }, }; return preloadedState; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx index 13be87846df80..dda35ad26a685 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx @@ -37,7 +37,7 @@ import { DetectionEngineUserUnauthenticated } from './detection_engine_user_unau import * as i18n from './translations'; import { LinkButton } from '../../../common/components/links'; import { useFormatUrl } from '../../../common/components/link_to'; -import { useFullScreen } from '../../../common/containers/use_full_screen'; +import { useGlobalFullScreen } from '../../../common/containers/use_full_screen'; import { Display } from '../../../hosts/pages/display'; import { showGlobalFilters } from '../../../timelines/components/timeline/helpers'; import { timelineSelectors } from '../../../timelines/store/timeline'; @@ -61,7 +61,7 @@ const DetectionEnginePageComponent = () => { const filters = useDeepEqualSelector(getGlobalFiltersQuerySelector); const { to, from, deleteQuery, setQuery } = useGlobalTime(); - const { globalFullScreen } = useFullScreen(); + const { globalFullScreen } = useGlobalFullScreen(); const [ { loading: userInfoLoading, 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 28c7805e968d6..3986e02b5b9b9 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 @@ -79,7 +79,7 @@ import { LinkButton } from '../../../../../common/components/links'; import { useFormatUrl } from '../../../../../common/components/link_to'; import { ExceptionsViewer } from '../../../../../common/components/exceptions/viewer'; import { DEFAULT_INDEX_PATTERN } from '../../../../../../common/constants'; -import { useFullScreen } from '../../../../../common/containers/use_full_screen'; +import { useGlobalFullScreen } from '../../../../../common/containers/use_full_screen'; import { Display } from '../../../../../hosts/pages/display'; import { ExceptionListTypeEnum, ExceptionListIdentifiers } from '../../../../../shared_imports'; import { useRuleAsync } from '../../../../containers/detection_engine/rules/use_rule_async'; @@ -178,7 +178,7 @@ const RuleDetailsPageComponent = () => { const mlCapabilities = useMlCapabilities(); const history = useHistory(); const { formatUrl } = useFormatUrl(SecurityPageName.detections); - const { globalFullScreen } = useFullScreen(); + const { globalFullScreen } = useGlobalFullScreen(); // TODO: Refactor license check + hasMlAdminPermissions to common check const hasMlPermissions = hasMlLicense(mlCapabilities) && hasMlAdminPermissions(mlCapabilities); diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx b/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx index 58474f05bb2b9..7eef46a480707 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx @@ -43,7 +43,7 @@ import { HostDetailsProps } from './types'; import { type } from './utils'; import { getHostDetailsPageFilters } from './helpers'; import { showGlobalFilters } from '../../../timelines/components/timeline/helpers'; -import { useFullScreen } from '../../../common/containers/use_full_screen'; +import { useGlobalFullScreen } from '../../../common/containers/use_full_screen'; import { Display } from '../display'; import { timelineSelectors } from '../../../timelines/store/timeline'; import { TimelineId } from '../../../../common/types/timeline'; @@ -68,7 +68,7 @@ const HostDetailsComponent: React.FC = ({ detailName, hostDeta const filters = useDeepEqualSelector(getGlobalFiltersQuerySelector); const { to, from, deleteQuery, setQuery, isInitializing } = useGlobalTime(); - const { globalFullScreen } = useFullScreen(); + const { globalFullScreen } = useGlobalFullScreen(); const capabilities = useMlCapabilities(); const kibana = useKibana(); diff --git a/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx b/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx index d54891ba573fd..52ec837a09eb6 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx @@ -20,7 +20,7 @@ import { SiemNavigation } from '../../common/components/navigation'; import { HostsKpiComponent } from '../components/kpi_hosts'; import { SiemSearchBar } from '../../common/components/search_bar'; import { WrapperPage } from '../../common/components/wrapper_page'; -import { useFullScreen } from '../../common/containers/use_full_screen'; +import { useGlobalFullScreen } from '../../common/containers/use_full_screen'; import { useGlobalTime } from '../../common/containers/use_global_time'; import { TimelineId } from '../../../common/types/timeline'; import { LastEventIndexKey } from '../../../common/search_strategy'; @@ -66,7 +66,7 @@ const HostsComponent = () => { const filters = useDeepEqualSelector(getGlobalFiltersQuerySelector); const { to, from, deleteQuery, setQuery, isInitializing } = useGlobalTime(); - const { globalFullScreen } = useFullScreen(); + const { globalFullScreen } = useGlobalFullScreen(); const capabilities = useMlCapabilities(); const { uiSettings } = useKibana().services; const { tabName } = useParams<{ tabName: string }>(); diff --git a/x-pack/plugins/security_solution/public/hosts/pages/navigation/events_query_tab_body.tsx b/x-pack/plugins/security_solution/public/hosts/pages/navigation/events_query_tab_body.tsx index e30071ec04f0c..1540ffd59f2de 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/navigation/events_query_tab_body.tsx +++ b/x-pack/plugins/security_solution/public/hosts/pages/navigation/events_query_tab_body.tsx @@ -16,7 +16,7 @@ import { MatrixHistogramConfigs, } from '../../../common/components/matrix_histogram/types'; import { MatrixHistogram } from '../../../common/components/matrix_histogram'; -import { useFullScreen } from '../../../common/containers/use_full_screen'; +import { useGlobalFullScreen } from '../../../common/containers/use_full_screen'; import * as i18n from '../translations'; import { MatrixHistogramType } from '../../../../common/search_strategy/security_solution'; import { useManageTimeline } from '../../../timelines/components/manage_timeline'; @@ -62,7 +62,7 @@ const EventsQueryTabBodyComponent: React.FC = ({ }) => { const dispatch = useDispatch(); const { initializeTimeline } = useManageTimeline(); - const { globalFullScreen } = useFullScreen(); + const { globalFullScreen } = useGlobalFullScreen(); useEffect(() => { initializeTimeline({ id: TimelineId.hostsPageEvents, diff --git a/x-pack/plugins/security_solution/public/network/pages/network.tsx b/x-pack/plugins/security_solution/public/network/pages/network.tsx index f9e30e30472d9..3a095cfb21f0e 100644 --- a/x-pack/plugins/security_solution/public/network/pages/network.tsx +++ b/x-pack/plugins/security_solution/public/network/pages/network.tsx @@ -22,7 +22,7 @@ import { SiemNavigation } from '../../common/components/navigation'; import { NetworkKpiComponent } from '../components/kpi_network'; import { SiemSearchBar } from '../../common/components/search_bar'; import { WrapperPage } from '../../common/components/wrapper_page'; -import { useFullScreen } from '../../common/containers/use_full_screen'; +import { useGlobalFullScreen } from '../../common/containers/use_full_screen'; import { useGlobalTime } from '../../common/containers/use_global_time'; import { LastEventIndexKey } from '../../../common/search_strategy'; import { useKibana } from '../../common/lib/kibana'; @@ -62,7 +62,7 @@ const NetworkComponent = React.memo( const filters = useDeepEqualSelector(getGlobalFiltersQuerySelector); const { to, from, setQuery, isInitializing } = useGlobalTime(); - const { globalFullScreen } = useFullScreen(); + const { globalFullScreen } = useGlobalFullScreen(); const kibana = useKibana(); const { tabName } = useParams<{ tabName: string }>(); diff --git a/x-pack/plugins/security_solution/public/plugin.tsx b/x-pack/plugins/security_solution/public/plugin.tsx index 4f37b5b15d73a..0b5093ff50c39 100644 --- a/x-pack/plugins/security_solution/public/plugin.tsx +++ b/x-pack/plugins/security_solution/public/plugin.tsx @@ -41,6 +41,7 @@ import { APP_CASES_PATH, APP_PATH, DEFAULT_INDEX_KEY, + DETECTION_ENGINE_INDEX_URL, } from '../common/constants'; import { SecurityPageName } from './app/types'; @@ -435,6 +436,15 @@ export class Plugin implements IPlugin `; diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/header/active_timelines.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/header/active_timelines.tsx index 749bc4b1f010d..6a51c7180587f 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/flyout/header/active_timelines.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/header/active_timelines.tsx @@ -4,44 +4,49 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiFlexGroup, EuiFlexItem, EuiButtonEmpty } from '@elastic/eui'; -import React, { useCallback } from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiButtonEmpty, EuiHealth, EuiToolTip } from '@elastic/eui'; +import React, { useCallback, useMemo } from 'react'; import { useDispatch } from 'react-redux'; import { isEmpty } from 'lodash/fp'; import styled from 'styled-components'; +import { FormattedRelative } from '@kbn/i18n/react'; -import { TimelineType } from '../../../../../common/types/timeline'; +import { TimelineStatus, TimelineType } from '../../../../../common/types/timeline'; import { TimelineEventsCountBadge } from '../../../../common/hooks/use_timeline_events_count'; import { UNTITLED_TIMELINE, UNTITLED_TEMPLATE } from '../../timeline/properties/translations'; import { timelineActions } from '../../../store/timeline'; +import * as i18n from './translations'; const ButtonWrapper = styled(EuiFlexItem)` flex-direction: row; align-items: center; `; +const EuiHealthStyled = styled(EuiHealth)` + display: block; +`; + interface ActiveTimelinesProps { timelineId: string; + timelineStatus: TimelineStatus; timelineTitle: string; timelineType: TimelineType; isOpen: boolean; + updated?: number; } const StyledEuiButtonEmpty = styled(EuiButtonEmpty)` > span { padding: 0; - - > span { - display: flex; - flex-direction: row; - } } `; const ActiveTimelinesComponent: React.FC = ({ timelineId, + timelineStatus, timelineType, timelineTitle, + updated, isOpen, }) => { const dispatch = useDispatch(); @@ -57,17 +62,47 @@ const ActiveTimelinesComponent: React.FC = ({ ? UNTITLED_TEMPLATE : UNTITLED_TIMELINE; + const tooltipContent = useMemo(() => { + if (timelineStatus === TimelineStatus.draft) { + return <>{i18n.UNSAVED}; + } + return ( + <> + {i18n.AUTOSAVED}{' '} + + + ); + }, [timelineStatus, updated]); + return ( - {title} - {!isOpen && } + + + + + + + {title} + {!isOpen && ( + + + + )} + diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.tsx index 368cb53eccc34..063e968a6c51a 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.tsx @@ -32,6 +32,8 @@ import { InspectButton } from '../../../../common/components/inspect'; import { ActiveTimelines } from './active_timelines'; import * as i18n from './translations'; import * as commonI18n from '../../timeline/properties/translations'; +import { getTimelineStatusByIdSelector } from './selectors'; +import { TimelineTabs } from '../../../store/timeline/model'; // to hide side borders const StyledPanel = styled(EuiPanel)` @@ -49,9 +51,27 @@ interface FlyoutHeaderPanelProps { const FlyoutHeaderPanelComponent: React.FC = ({ timelineId }) => { const dispatch = useDispatch(); const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []); - const { dataProviders, kqlQuery, title, timelineType, show } = useDeepEqualSelector((state) => + const { + activeTab, + dataProviders, + kqlQuery, + title, + timelineType, + status: timelineStatus, + updated, + show, + } = useDeepEqualSelector((state) => pick( - ['dataProviders', 'kqlQuery', 'title', 'timelineType', 'show'], + [ + 'activeTab', + 'dataProviders', + 'kqlQuery', + 'status', + 'title', + 'timelineType', + 'updated', + 'show', + ], getTimeline(state, timelineId) ?? timelineDefaults ) ); @@ -67,29 +87,33 @@ const FlyoutHeaderPanelComponent: React.FC = ({ timeline return ( - + {show && ( - - - + {activeTab === TimelineTabs.query && ( + + + + )} = ({ timelineId const TimelineDescription = React.memo(TimelineDescriptionComponent); const TimelineStatusInfoComponent: React.FC = ({ timelineId }) => { - const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []); + const getTimelineStatus = useMemo(() => getTimelineStatusByIdSelector(), []); const { status: timelineStatus, updated } = useDeepEqualSelector((state) => - pick(['status', 'updated'], getTimeline(state, timelineId) ?? timelineDefaults) + getTimelineStatus(state, timelineId) ); const isUnsaved = useMemo(() => timelineStatus === TimelineStatus.draft, [timelineStatus]); @@ -198,16 +222,16 @@ const TimelineStatusInfoComponent: React.FC = ({ timelineId } const TimelineStatusInfo = React.memo(TimelineStatusInfoComponent); const FlyoutHeaderComponent: React.FC = ({ timelineId }) => ( - + - + - + diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/header/selectors.ts b/x-pack/plugins/security_solution/public/timelines/components/flyout/header/selectors.ts new file mode 100644 index 0000000000000..634fa5a775f1a --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/header/selectors.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { createSelector } from 'reselect'; + +import { TimelineStatus } from '../../../../../common/types/timeline'; +import { timelineSelectors } from '../../../store/timeline'; + +export const getTimelineStatusByIdSelector = () => + createSelector(timelineSelectors.selectTimeline, (timeline) => ({ + status: timeline?.status ?? TimelineStatus.draft, + updated: timeline?.updated ?? undefined, + })); diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/index.test.tsx index 5d118b357c8ef..41e2a569f41bb 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/flyout/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/index.test.tsx @@ -40,6 +40,10 @@ jest.mock('../timeline', () => ({ describe('Flyout', () => { const state: State = mockGlobalState; const { storage } = createSecuritySolutionStorageMock(); + const props = { + onAppLeave: jest.fn(), + timelineId: 'test', + }; beforeEach(() => { mockDispatch.mockClear(); @@ -49,7 +53,7 @@ describe('Flyout', () => { test('it renders correctly against snapshot', () => { const wrapper = shallow( - + ); expect(wrapper.find('Flyout')).toMatchSnapshot(); @@ -58,7 +62,7 @@ describe('Flyout', () => { test('it renders the default flyout state as a bottom bar', () => { const wrapper = mount( - + ); @@ -79,7 +83,7 @@ describe('Flyout', () => { const wrapper = mount( - + ); @@ -91,7 +95,7 @@ describe('Flyout', () => { test('should call the onOpen when the mouse is clicked for rendering', () => { const wrapper = mount( - + ); diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/index.tsx index a1e61b9fa4ae6..0636b76ef61bc 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/flyout/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/index.tsx @@ -4,14 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useMemo } from 'react'; +import { i18n } from '@kbn/i18n'; +import React, { useEffect, useMemo } from 'react'; +import { useDispatch } from 'react-redux'; import styled from 'styled-components'; +import { AppLeaveHandler } from '../../../../../../../src/core/public'; +import { TimelineId, TimelineStatus } from '../../../../common/types/timeline'; +import { useDeepEqualSelector } from '../../../common/hooks/use_selector'; +import { timelineActions } from '../../store/timeline'; +import { TimelineTabs } from '../../store/timeline/model'; import { FlyoutBottomBar } from './bottom_bar'; import { Pane } from './pane'; -import { timelineSelectors } from '../../store/timeline'; -import { useShallowEqualSelector } from '../../../common/hooks/use_selector'; -import { timelineDefaults } from '../../store/timeline/defaults'; +import { getTimelineShowStatusByIdSelector } from './selectors'; const Visible = styled.div<{ show?: boolean }>` visibility: ${({ show }) => (show ? 'visible' : 'hidden')}; @@ -21,14 +26,58 @@ Visible.displayName = 'Visible'; interface OwnProps { timelineId: string; + onAppLeave: (handler: AppLeaveHandler) => void; } -const FlyoutComponent: React.FC = ({ timelineId }) => { - const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []); - const show = useShallowEqualSelector( - (state) => (getTimeline(state, timelineId) ?? timelineDefaults).show +const FlyoutComponent: React.FC = ({ timelineId, onAppLeave }) => { + const dispatch = useDispatch(); + const getTimelineShowStatus = useMemo(() => getTimelineShowStatusByIdSelector(), []); + const { show, status: timelineStatus, updated } = useDeepEqualSelector((state) => + getTimelineShowStatus(state, timelineId) ); + useEffect(() => { + onAppLeave((actions, nextAppId) => { + if (show) { + dispatch(timelineActions.showTimeline({ id: TimelineId.active, show: false })); + } + // Confirm when the user has made any changes to a timeline + if ( + !(nextAppId ?? '').includes('securitySolution') && + timelineStatus === TimelineStatus.draft && + updated != null + ) { + const showSaveTimelineModal = () => { + dispatch(timelineActions.showTimeline({ id: TimelineId.active, show: true })); + dispatch( + timelineActions.setActiveTabTimeline({ + id: TimelineId.active, + activeTab: TimelineTabs.query, + }) + ); + dispatch( + timelineActions.toggleModalSaveTimeline({ + id: TimelineId.active, + showModalSaveTimeline: true, + }) + ); + }; + + return actions.confirm( + i18n.translate('xpack.securitySolution.timeline.unsavedWorkMessage', { + defaultMessage: 'Leave Timeline with unsaved work?', + }), + i18n.translate('xpack.securitySolution.timeline.unsavedWorkTitle', { + defaultMessage: 'Unsaved changes', + }), + showSaveTimelineModal + ); + } else { + return actions.default(); + } + }); + }, [dispatch, onAppLeave, show, timelineStatus, updated]); + return ( <> diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/selectors.ts b/x-pack/plugins/security_solution/public/timelines/components/flyout/selectors.ts new file mode 100644 index 0000000000000..ca811afd164f6 --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/selectors.ts @@ -0,0 +1,17 @@ +/* + * 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 { createSelector } from 'reselect'; + +import { TimelineStatus } from '../../../../common/types/timeline'; +import { timelineSelectors } from '../../store/timeline'; + +export const getTimelineShowStatusByIdSelector = () => + createSelector(timelineSelectors.selectTimeline, (timeline) => ({ + status: timeline?.status ?? TimelineStatus.draft, + show: timeline?.show ?? false, + updated: timeline?.updated ?? undefined, + })); diff --git a/x-pack/plugins/security_solution/public/timelines/components/graph_overlay/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/graph_overlay/index.test.tsx index 3d5e548e726e5..ececded801b45 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/graph_overlay/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/graph_overlay/index.test.tsx @@ -8,7 +8,10 @@ import { waitFor } from '@testing-library/react'; import { mount } from 'enzyme'; import React from 'react'; -import { useFullScreen } from '../../../common/containers/use_full_screen'; +import { + useGlobalFullScreen, + useTimelineFullScreen, +} from '../../../common/containers/use_full_screen'; import { mockTimelineModel, TestProviders } from '../../../common/mock'; import { TimelineId } from '../../../../common/types/timeline'; @@ -20,17 +23,20 @@ jest.mock('../../../common/hooks/use_selector', () => ({ })); jest.mock('../../../common/containers/use_full_screen', () => ({ - useFullScreen: jest.fn(), + useGlobalFullScreen: jest.fn(), + useTimelineFullScreen: jest.fn(), })); describe('GraphOverlay', () => { beforeEach(() => { - (useFullScreen as jest.Mock).mockReturnValue({ - timelineFullScreen: false, - setTimelineFullScreen: jest.fn(), + (useGlobalFullScreen as jest.Mock).mockReturnValue({ globalFullScreen: false, setGlobalFullScreen: jest.fn(), }); + (useTimelineFullScreen as jest.Mock).mockReturnValue({ + timelineFullScreen: false, + setTimelineFullScreen: jest.fn(), + }); }); describe('when used in an events viewer (i.e. in the Detections view, or the Host > Events view)', () => { @@ -51,12 +57,14 @@ describe('GraphOverlay', () => { }); test('it has a calculated width that makes room for the Timeline flyout button when isEventViewer is true in full screen mode', async () => { - (useFullScreen as jest.Mock).mockReturnValue({ - timelineFullScreen: false, - setTimelineFullScreen: jest.fn(), + (useGlobalFullScreen as jest.Mock).mockReturnValue({ globalFullScreen: true, // <-- true when an events viewer is in full screen mode setGlobalFullScreen: jest.fn(), }); + (useTimelineFullScreen as jest.Mock).mockReturnValue({ + timelineFullScreen: false, + setTimelineFullScreen: jest.fn(), + }); const wrapper = mount( @@ -89,12 +97,14 @@ describe('GraphOverlay', () => { }); test('it has 100% width when isEventViewer is false and the active timeline is in full screen mode', async () => { - (useFullScreen as jest.Mock).mockReturnValue({ - timelineFullScreen: true, // <-- true when the active timeline is in full screen mode - setTimelineFullScreen: jest.fn(), + (useGlobalFullScreen as jest.Mock).mockReturnValue({ globalFullScreen: false, setGlobalFullScreen: jest.fn(), }); + (useTimelineFullScreen as jest.Mock).mockReturnValue({ + timelineFullScreen: true, // <-- true when the active timeline is in full screen mode + setTimelineFullScreen: jest.fn(), + }); const wrapper = mount( diff --git a/x-pack/plugins/security_solution/public/timelines/components/graph_overlay/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/graph_overlay/index.tsx index 069f46c40e6af..8fac8fec0b61d 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/graph_overlay/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/graph_overlay/index.tsx @@ -20,7 +20,10 @@ import styled from 'styled-components'; import { FULL_SCREEN } from '../timeline/body/column_headers/translations'; import { EXIT_FULL_SCREEN } from '../../../common/components/exit_full_screen/translations'; import { DEFAULT_INDEX_KEY, FULL_SCREEN_TOGGLED_CLASS_NAME } from '../../../../common/constants'; -import { useFullScreen } from '../../../common/containers/use_full_screen'; +import { + useGlobalFullScreen, + useTimelineFullScreen, +} from '../../../common/containers/use_full_screen'; import { useDeepEqualSelector } from '../../../common/hooks/use_selector'; import { TimelineId } from '../../../../common/types/timeline'; import { timelineSelectors } from '../../store/timeline'; @@ -114,12 +117,8 @@ const GraphOverlayComponent: React.FC = ({ isEventViewer, timelineId } (state) => (getTimeline(state, timelineId) ?? timelineDefaults).graphEventId ); - const { - timelineFullScreen, - setTimelineFullScreen, - globalFullScreen, - setGlobalFullScreen, - } = useFullScreen(); + const { globalFullScreen, setGlobalFullScreen } = useGlobalFullScreen(); + const { timelineFullScreen, setTimelineFullScreen } = useTimelineFullScreen(); const fullScreen = useMemo( () => isFullScreen({ globalFullScreen, timelineId, timelineFullScreen }), diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts index df12194e264de..37de75fd736af 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts @@ -399,13 +399,15 @@ export const dispatchUpdateTimeline = (dispatch: Dispatch): DispatchUpdateTimeli to, ruleNote, }: UpdateTimeline): (() => void) => () => { - dispatch( - sourcererActions.initTimelineIndexPatterns({ - id: SourcererScopeName.timeline, - selectedPatterns: timeline.indexNames, - eventType: timeline.eventType, - }) - ); + if (!isEmpty(timeline.indexNames)) { + dispatch( + sourcererActions.initTimelineIndexPatterns({ + id: SourcererScopeName.timeline, + selectedPatterns: timeline.indexNames, + eventType: timeline.eventType, + }) + ); + } if ( timeline.status === TimelineStatus.immutable && timeline.timelineType === TimelineType.template diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/index.tsx index 54cb4d5d14462..e3808514856e7 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/index.tsx @@ -27,7 +27,10 @@ import { } from '../../../../../common/components/drag_and_drop/helpers'; import { EXIT_FULL_SCREEN } from '../../../../../common/components/exit_full_screen/translations'; import { FULL_SCREEN_TOGGLED_CLASS_NAME } from '../../../../../../common/constants'; -import { useFullScreen } from '../../../../../common/containers/use_full_screen'; +import { + useGlobalFullScreen, + useTimelineFullScreen, +} from '../../../../../common/containers/use_full_screen'; import { TimelineId } from '../../../../../../common/types/timeline'; import { OnSelectAll } from '../../events'; import { DEFAULT_ICON_BUTTON_WIDTH } from '../../helpers'; @@ -50,8 +53,16 @@ import * as i18n from './translations'; import { timelineActions } from '../../../../store/timeline'; const SortingColumnsContainer = styled.div` - .euiPopover .euiButtonEmpty .euiButtonContent .euiButtonEmpty__text { - display: none; + button { + color: ${({ theme }) => theme.eui.euiColorPrimary}; + } + + .euiPopover .euiButtonEmpty .euiButtonContent { + padding: 0; + + .euiButtonEmpty__text { + display: none; + } } `; @@ -115,12 +126,8 @@ export const ColumnHeadersComponent = ({ }: Props) => { const dispatch = useDispatch(); const [draggingIndex, setDraggingIndex] = useState(null); - const { - timelineFullScreen, - setTimelineFullScreen, - globalFullScreen, - setGlobalFullScreen, - } = useFullScreen(); + const { globalFullScreen, setGlobalFullScreen } = useGlobalFullScreen(); + const { timelineFullScreen, setTimelineFullScreen } = useTimelineFullScreen(); const toggleFullScreen = useCallback(() => { if (timelineId === TimelineId.active) { diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/footer/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/footer/index.tsx index 17d57b46d730c..c66a6e830ccf7 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/footer/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/footer/index.tsx @@ -130,6 +130,9 @@ export const EventsCountComponent = ({ serverSideEventCount: number; footerText: string; }) => { + const totalCount = useMemo(() => (serverSideEventCount > 0 ? serverSideEventCount : 0), [ + serverSideEventCount, + ]); return (

- + - {serverSideEventCount} + {totalCount} {' '} {documentType} diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/header/save_timeline_button.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/header/save_timeline_button.test.tsx index e9dc312ee8d19..18b2ebc2ec253 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/header/save_timeline_button.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/header/save_timeline_button.test.tsx @@ -3,13 +3,10 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - import React from 'react'; -import { shallow, mount } from 'enzyme'; - +import { mount } from 'enzyme'; import { SaveTimelineButton } from './save_timeline_button'; -import { act } from '@testing-library/react-hooks'; - +import { TestProviders } from '../../../../common/mock'; jest.mock('react-redux', () => { const actual = jest.requireActual('react-redux'); return { @@ -17,60 +14,59 @@ jest.mock('react-redux', () => { useDispatch: jest.fn(), }; }); - +jest.mock('../../../../common/lib/kibana'); jest.mock('./title_and_description'); - describe('SaveTimelineButton', () => { const props = { + initialFocus: 'title' as const, timelineId: 'timeline-1', - showOverlay: false, toolTip: 'tooltip message', - toggleSaveTimeline: jest.fn(), - onSaveTimeline: jest.fn(), - updateTitle: jest.fn(), - updateDescription: jest.fn(), }; test('Show tooltip', () => { - const component = shallow(); + const component = mount( + + + + ); expect(component.find('[data-test-subj="save-timeline-btn-tooltip"]').exists()).toEqual(true); }); - test('Hide tooltip', () => { - const testProps = { - ...props, - showOverlay: true, - }; - const component = mount(); + const component = mount( + + + + ); component.find('[data-test-subj="save-timeline-button-icon"]').first().simulate('click'); - - act(() => { - expect(component.find('[data-test-subj="save-timeline-btn-tooltip"]').exists()).toEqual( - false - ); - }); + expect(component.find('[data-test-subj="save-timeline-btn-tooltip"]').exists()).toEqual(false); }); - test('should show a button with pencil icon', () => { - const component = shallow(); - expect(component.find('[data-test-subj="save-timeline-button-icon"]').prop('iconType')).toEqual( - 'pencil' + const component = mount( + + + ); + expect( + component.find('[data-test-subj="save-timeline-button-icon"]').first().prop('iconType') + ).toEqual('pencil'); }); - test('should not show a modal when showOverlay equals false', () => { - const component = shallow(); + const component = mount( + + + + ); expect(component.find('[data-test-subj="save-timeline-modal"]').exists()).toEqual(false); }); - test('should show a modal when showOverlay equals true', () => { - const testProps = { - ...props, - showOverlay: true, - }; - const component = mount(); + const component = mount( + + + + ); + expect(component.find('[data-test-subj="save-timeline-btn-tooltip"]').exists()).toEqual(true); + expect(component.find('[data-test-subj="save-timeline-modal-comp"]').exists()).toEqual(false); component.find('[data-test-subj="save-timeline-button-icon"]').first().simulate('click'); - act(() => { - expect(component.find('[data-test-subj="save-timeline-modal"]').exists()).toEqual(true); - }); + expect(component.find('[data-test-subj="save-timeline-btn-tooltip"]').exists()).toEqual(false); + expect(component.find('[data-test-subj="save-timeline-modal-comp"]').exists()).toEqual(true); }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/header/save_timeline_button.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/header/save_timeline_button.tsx index f3bd4a88ca236..46898a8daaf89 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/header/save_timeline_button.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/header/save_timeline_button.tsx @@ -4,53 +4,69 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiButtonIcon, EuiOverlayMask, EuiModal, EuiToolTip } from '@elastic/eui'; - +import { EuiButtonIcon, EuiToolTip } from '@elastic/eui'; import React, { useCallback, useMemo, useState } from 'react'; -import { NOTES_PANEL_WIDTH } from '../properties/notes_size'; +import { useDispatch } from 'react-redux'; +import { TimelineId } from '../../../../../common/types/timeline'; +import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; +import { timelineActions } from '../../../store/timeline'; +import { getTimelineSaveModalByIdSelector } from './selectors'; import { TimelineTitleAndDescription } from './title_and_description'; import { EDIT } from './translations'; export interface SaveTimelineComponentProps { + initialFocus: 'title' | 'description'; timelineId: string; toolTip?: string; } export const SaveTimelineButton = React.memo( - ({ timelineId, toolTip }) => { + ({ initialFocus, timelineId, toolTip }) => { + const dispatch = useDispatch(); + const getTimelineSaveModal = useMemo(() => getTimelineSaveModalByIdSelector(), []); + const show = useDeepEqualSelector((state) => getTimelineSaveModal(state, timelineId)); const [showSaveTimelineOverlay, setShowSaveTimelineOverlay] = useState(false); - const onToggleSaveTimeline = useCallback(() => { - setShowSaveTimelineOverlay((prevShowSaveTimelineOverlay) => !prevShowSaveTimelineOverlay); + + const closeSaveTimeline = useCallback(() => { + setShowSaveTimelineOverlay(false); + if (show) { + dispatch( + timelineActions.toggleModalSaveTimeline({ + id: TimelineId.active, + showModalSaveTimeline: false, + }) + ); + } + }, [dispatch, setShowSaveTimelineOverlay, show]); + + const openSaveTimeline = useCallback(() => { + setShowSaveTimelineOverlay(true); }, [setShowSaveTimelineOverlay]); const saveTimelineButtonIcon = useMemo( () => ( ), - [onToggleSaveTimeline] + [openSaveTimeline] ); - return showSaveTimelineOverlay ? ( + return (initialFocus === 'title' && show) || showSaveTimelineOverlay ? ( <> {saveTimelineButtonIcon} - - - - - + ) : ( diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/header/selectors.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/header/selectors.ts new file mode 100644 index 0000000000000..8aa895e68dc7e --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/header/selectors.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 { createSelector } from 'reselect'; + +import { timelineSelectors } from '../../../store/timeline'; + +export const getTimelineSaveModalByIdSelector = () => + createSelector(timelineSelectors.selectTimeline, (timeline) => timeline?.showSaveModal ?? false); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/header/title_and_description.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/header/title_and_description.test.tsx index cb31765bd9c37..2b8ec62199478 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/header/title_and_description.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/header/title_and_description.test.tsx @@ -8,8 +8,7 @@ import React from 'react'; import { shallow } from 'enzyme'; import { TimelineTitleAndDescription } from './title_and_description'; import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; -import { useCreateTimelineButton } from '../properties/use_create_timeline'; -import { TimelineType } from '../../../../../common/types/timeline'; +import { TimelineStatus, TimelineType } from '../../../../../common/types/timeline'; import * as i18n from './translations'; jest.mock('../../../../common/hooks/use_selector', () => ({ @@ -17,7 +16,7 @@ jest.mock('../../../../common/hooks/use_selector', () => ({ })); jest.mock('../properties/use_create_timeline', () => ({ - useCreateTimelineButton: jest.fn(), + useCreateTimeline: jest.fn(), })); jest.mock('react-redux', () => { @@ -31,8 +30,10 @@ jest.mock('react-redux', () => { describe('TimelineTitleAndDescription', () => { describe('save timeline', () => { const props = { + initialFocus: 'title' as const, + closeSaveTimeline: jest.fn(), + openSaveTimeline: jest.fn(), timelineId: 'timeline-1', - toggleSaveTimeline: jest.fn(), onSaveTimeline: jest.fn(), updateTitle: jest.fn(), updateDescription: jest.fn(), @@ -44,22 +45,18 @@ describe('TimelineTitleAndDescription', () => { (useDeepEqualSelector as jest.Mock).mockReturnValue({ description: '', isSaving: true, - savedObjectId: null, + status: TimelineStatus.draft, title: 'my timeline', timelineType: TimelineType.default, }); - (useCreateTimelineButton as jest.Mock).mockReturnValue({ - getButton: mockGetButton, - }); }); afterEach(() => { (useDeepEqualSelector as jest.Mock).mockReset(); - (useCreateTimelineButton as jest.Mock).mockReset(); mockGetButton.mockClear(); }); - test('show proress bar while saving', () => { + test('show process bar while saving', () => { const component = shallow(); expect(component.find('[data-test-subj="progress-bar"]').exists()).toEqual(true); }); @@ -75,7 +72,7 @@ describe('TimelineTitleAndDescription', () => { (useDeepEqualSelector as jest.Mock).mockReturnValue({ description: '', isSaving: true, - savedObjectId: null, + status: TimelineStatus.draft, title: 'my timeline', timelineType: TimelineType.template, }); @@ -108,6 +105,9 @@ describe('TimelineTitleAndDescription', () => { describe('update timeline', () => { const props = { + initialFocus: 'title' as const, + closeSaveTimeline: jest.fn(), + openSaveTimeline: jest.fn(), timelineId: 'timeline-1', toggleSaveTimeline: jest.fn(), onSaveTimeline: jest.fn(), @@ -121,22 +121,18 @@ describe('TimelineTitleAndDescription', () => { (useDeepEqualSelector as jest.Mock).mockReturnValue({ description: 'xxxx', isSaving: true, - savedObjectId: '1234', + status: TimelineStatus.active, title: 'my timeline', timelineType: TimelineType.default, }); - (useCreateTimelineButton as jest.Mock).mockReturnValue({ - getButton: mockGetButton, - }); }); afterEach(() => { (useDeepEqualSelector as jest.Mock).mockReset(); - (useCreateTimelineButton as jest.Mock).mockReset(); mockGetButton.mockClear(); }); - test('show proress bar while saving', () => { + test('show process bar while saving', () => { const component = shallow(); expect(component.find('[data-test-subj="progress-bar"]').exists()).toEqual(true); }); @@ -152,7 +148,7 @@ describe('TimelineTitleAndDescription', () => { (useDeepEqualSelector as jest.Mock).mockReturnValue({ description: 'xxxx', isSaving: true, - savedObjectId: '1234', + status: TimelineStatus.active, title: 'my timeline', timelineType: TimelineType.template, }); @@ -180,6 +176,9 @@ describe('TimelineTitleAndDescription', () => { describe('showWarning', () => { const props = { + initialFocus: 'title' as const, + closeSaveTimeline: jest.fn(), + openSaveTimeline: jest.fn(), timelineId: 'timeline-1', toggleSaveTimeline: jest.fn(), onSaveTimeline: jest.fn(), @@ -194,19 +193,15 @@ describe('TimelineTitleAndDescription', () => { (useDeepEqualSelector as jest.Mock).mockReturnValue({ description: '', isSaving: true, - savedObjectId: null, + status: TimelineStatus.draft, title: 'my timeline', timelineType: TimelineType.default, showWarnging: true, }); - (useCreateTimelineButton as jest.Mock).mockReturnValue({ - getButton: mockGetButton, - }); }); afterEach(() => { (useDeepEqualSelector as jest.Mock).mockReset(); - (useCreateTimelineButton as jest.Mock).mockReset(); mockGetButton.mockClear(); }); @@ -217,34 +212,23 @@ describe('TimelineTitleAndDescription', () => { test('Show discardTimelineButton', () => { const component = shallow(); - expect(component.find('[data-test-subj="mock-discard-button"]').exists()).toEqual(true); - }); - - test('get discardTimelineButton with correct props', () => { - shallow(); - expect(mockGetButton).toBeCalledWith({ - title: i18n.DISCARD_TIMELINE, - outline: true, - iconType: '', - fill: false, - }); + expect(component.find('[data-test-subj="close-button"]').dive().text()).toEqual( + 'Discard Timeline' + ); }); test('get discardTimelineTemplateButton with correct props', () => { (useDeepEqualSelector as jest.Mock).mockReturnValue({ description: 'xxxx', isSaving: true, - savedObjectId: null, + status: TimelineStatus.draft, title: 'my timeline', timelineType: TimelineType.template, }); - shallow(); - expect(mockGetButton).toBeCalledWith({ - title: i18n.DISCARD_TIMELINE_TEMPLATE, - outline: true, - iconType: '', - fill: false, - }); + const component = shallow(); + expect(component.find('[data-test-subj="close-button"]').dive().text()).toEqual( + 'Discard Timeline Template' + ); }); test('Show saveButton', () => { diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/header/title_and_description.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/header/title_and_description.tsx index 72e7778347f44..87d4fcdb7075f 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/header/title_and_description.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/header/title_and_description.tsx @@ -9,6 +9,8 @@ import { EuiFlexGroup, EuiFormRow, EuiFlexItem, + EuiOverlayMask, + EuiModal, EuiModalBody, EuiModalHeader, EuiSpacer, @@ -18,19 +20,23 @@ import { import React, { useCallback, useEffect, useMemo, useState, useRef } from 'react'; import { useDispatch } from 'react-redux'; import styled from 'styled-components'; -import { TimelineType } from '../../../../../common/types/timeline'; + +import { TimelineId, TimelineStatus, TimelineType } from '../../../../../common/types/timeline'; import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; import { timelineActions, timelineSelectors } from '../../../../timelines/store/timeline'; import { TimelineInput } from '../../../store/timeline/actions'; import { Description, Name } from '../properties/helpers'; +import { NOTES_PANEL_WIDTH } from '../properties/notes_size'; import { TIMELINE_TITLE, DESCRIPTION, OPTIONAL } from '../properties/translations'; -import { useCreateTimelineButton } from '../properties/use_create_timeline'; +import { useCreateTimeline } from '../properties/use_create_timeline'; import * as i18n from './translations'; interface TimelineTitleAndDescriptionProps { - showWarning?: boolean; + closeSaveTimeline: () => void; + initialFocus: 'title' | 'description'; + openSaveTimeline: () => void; timelineId: string; - toggleSaveTimeline: () => void; + showWarning?: boolean; } const Wrapper = styled(EuiModalBody)` @@ -61,16 +67,18 @@ const usePrevious = (value: unknown) => { // the modal is used as a reminder for users to save / discard // the unsaved timeline / template export const TimelineTitleAndDescription = React.memo( - ({ timelineId, toggleSaveTimeline, showWarning }) => { + ({ closeSaveTimeline, initialFocus, openSaveTimeline, timelineId, showWarning }) => { // TODO: Refactor to use useForm() instead const [isFormSubmitted, setFormSubmitted] = useState(false); const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []); const timeline = useDeepEqualSelector((state) => getTimeline(state, timelineId)); - - const { isSaving, savedObjectId, title, timelineType } = timeline; - + const { isSaving, status, title, timelineType } = timeline; const prevIsSaving = usePrevious(isSaving); const dispatch = useDispatch(); + const handleCreateNewTimeline = useCreateTimeline({ + timelineId: TimelineId.active, + timelineType: TimelineType.default, + }); const onSaveTimeline = useCallback( (args: TimelineInput) => dispatch(timelineActions.saveTimeline(args)), [dispatch] @@ -85,30 +93,30 @@ export const TimelineTitleAndDescription = React.memo - getButton({ - title: - timelineType === TimelineType.template - ? i18n.DISCARD_TIMELINE_TEMPLATE - : i18n.DISCARD_TIMELINE, - outline: true, - iconType: '', - fill: false, - }), - [getButton, timelineType] - ); + const handleCancel = useCallback(() => { + if (showWarning) { + handleCreateNewTimeline(); + } + closeSaveTimeline(); + }, [closeSaveTimeline, handleCreateNewTimeline, showWarning]); + + const closeModalText = useMemo(() => { + if (status === TimelineStatus.draft && showWarning) { + return timelineType === TimelineType.template + ? i18n.DISCARD_TIMELINE_TEMPLATE + : i18n.DISCARD_TIMELINE; + } + return i18n.CLOSE_MODAL; + }, [showWarning, status, timelineType]); useEffect(() => { if (isFormSubmitted && !isSaving && prevIsSaving) { - toggleSaveTimeline(); + closeSaveTimeline(); } - }, [isFormSubmitted, isSaving, prevIsSaving, toggleSaveTimeline]); + }, [isFormSubmitted, isSaving, prevIsSaving, closeSaveTimeline]); const modalHeader = - savedObjectId == null + status === TimelineStatus.draft ? timelineType === TimelineType.template ? i18n.SAVE_TIMELINE_TEMPLATE : i18n.SAVE_TIMELINE @@ -117,7 +125,7 @@ export const TimelineTitleAndDescription = React.memo - {isSaving && ( - - )} - {modalHeader} - - - {showWarning && ( + + + {isSaving && ( + + )} + {modalHeader} + + + {showWarning && ( + + + + + )} - - + + + + - )} - - - - - - - - - - - - - - - - {savedObjectId == null && showWarning ? ( - discardTimelineButton - ) : ( + + + + + + + + + - {i18n.CLOSE_MODAL} + {closeModalText} - )} - - - - {saveButtonTitle} - - - - - - + + + + {saveButtonTitle} + + + + + + + ); } ); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/index.tsx index 4e6bca7fd9625..5a1d2ef7a1800 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/index.tsx @@ -21,7 +21,8 @@ import { useDeepEqualSelector, useShallowEqualSelector } from '../../../common/h import { activeTimeline } from '../../containers/active_timeline_context'; import * as i18n from './translations'; import { TabsContent } from './tabs_content'; -import { TimelineContainer } from './styles'; +import { HideShowContainer, TimelineContainer } from './styles'; +import { useTimelineFullScreen } from '../../../common/containers/use_full_screen'; const TimelineTemplateBadge = styled.div` background: ${({ theme }) => theme.eui.euiColorVis3_behindText}; @@ -55,6 +56,7 @@ const StatefulTimelineComponent: React.FC = ({ timelineId }) => { getTimeline(state, timelineId) ?? timelineDefaults ) ); + const { timelineFullScreen } = useTimelineFullScreen(); useEffect(() => { if (!savedObjectId) { @@ -79,7 +81,9 @@ const StatefulTimelineComponent: React.FC = ({ timelineId }) => { )} - + + + diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.test.tsx index 6eb9286871b68..3a75922ab72bd 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.test.tsx @@ -105,19 +105,6 @@ describe('Description', () => { ).toEqual(i18n.DESCRIPTION_TOOL_TIP); }); - test('should not render textarea if isTextArea is false', () => { - const component = mount( - - - - ); - expect(component.find('[data-test-subj="timeline-description-textarea"]').exists()).toEqual( - false - ); - - expect(component.find('[data-test-subj="timeline-description-input"]').exists()).toEqual(true); - }); - test('should render textarea if isTextArea is true', () => { const testProps = { ...props, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.tsx index 673efa1857cb8..d17399a0fb180 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.tsx @@ -4,16 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - EuiBadge, - EuiButton, - EuiButtonIcon, - EuiFieldText, - EuiToolTip, - EuiTextArea, -} from '@elastic/eui'; +import { EuiBadge, EuiButton, EuiButtonIcon, EuiToolTip, EuiTextArea } from '@elastic/eui'; import { pick } from 'lodash/fp'; -import React, { useCallback, useEffect, useMemo, useRef } from 'react'; +import React, { useCallback, useMemo } from 'react'; import styled from 'styled-components'; import { useDispatch } from 'react-redux'; @@ -77,8 +70,8 @@ AddToFavoritesButtonComponent.displayName = 'AddToFavoritesButtonComponent'; export const AddToFavoritesButton = React.memo(AddToFavoritesButtonComponent); interface DescriptionProps { + autoFocus?: boolean; timelineId: string; - isTextArea?: boolean; disableAutoSave?: boolean; disableTooltip?: boolean; disabled?: boolean; @@ -86,8 +79,8 @@ interface DescriptionProps { export const Description = React.memo( ({ + autoFocus = false, timelineId, - isTextArea = false, disableAutoSave = false, disableTooltip = false, disabled = false, @@ -113,28 +106,21 @@ export const Description = React.memo( ); const inputField = useMemo( - () => - isTextArea ? ( - - ) : ( - - ), - [description, isTextArea, onDescriptionChanged, disabled] + () => ( + + ), + [autoFocus, description, onDescriptionChanged, disabled] ); + return ( {disableTooltip ? ( @@ -170,7 +156,6 @@ export const Name = React.memo( timelineId, }) => { const dispatch = useDispatch(); - const timelineNameRef = useRef(null); const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []); const { title, timelineType } = useDeepEqualSelector((state) => @@ -185,15 +170,10 @@ export const Name = React.memo( [dispatch, timelineId, disableAutoSave] ); - useEffect(() => { - if (autoFocus && timelineNameRef && timelineNameRef.current) { - timelineNameRef.current.focus(); - } - }, [autoFocus]); - const nameField = useMemo( () => ( ( } spellCheck={true} value={title} - inputRef={timelineNameRef} /> ), - [handleChange, timelineType, title, disabled] + [autoFocus, handleChange, timelineType, title, disabled] ); return ( diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/use_create_timeline.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/use_create_timeline.tsx index 7fab0374d791d..12845477e0f39 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/use_create_timeline.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/use_create_timeline.tsx @@ -9,7 +9,7 @@ import { EuiButton, EuiButtonEmpty } from '@elastic/eui'; import { defaultHeaders } from '../body/column_headers/default_headers'; import { timelineActions } from '../../../store/timeline'; -import { useFullScreen } from '../../../../common/containers/use_full_screen'; +import { useTimelineFullScreen } from '../../../../common/containers/use_full_screen'; import { TimelineId, TimelineType, @@ -34,7 +34,7 @@ export const useCreateTimeline = ({ timelineId, timelineType, closeGearMenu }: P [] ); const existingIndexNames = useDeepEqualSelector(existingIndexNamesSelector); - const { timelineFullScreen, setTimelineFullScreen } = useFullScreen(); + const { timelineFullScreen, setTimelineFullScreen } = useTimelineFullScreen(); const globalTimeRange = useDeepEqualSelector(inputsSelectors.globalTimeRangeSelector); const createTimeline = useCallback( ({ id, show }) => { diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.test.tsx index 962e09d1a6237..d045cc6160c9c 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.test.tsx @@ -145,7 +145,7 @@ describe('Timeline', () => { expect(wrapper.find('[data-test-subj="events-table"]').exists()).toEqual(true); }); - test('it does NOT render the timeline table when the source is loading', () => { + test('it does render the timeline table when the source is loading with no events', () => { (useSourcererScope as jest.Mock).mockReturnValue({ browserFields: {}, docValueFields: [], @@ -159,7 +159,8 @@ describe('Timeline', () => { ); - expect(wrapper.find('[data-test-subj="events-table"]').exists()).toEqual(false); + expect(wrapper.find('[data-test-subj="events-table"]').exists()).toEqual(true); + expect(wrapper.find('[data-test-subj="events"]').exists()).toEqual(false); }); test('it does NOT render the timeline table when start is empty', () => { @@ -169,7 +170,8 @@ describe('Timeline', () => { ); - expect(wrapper.find('[data-test-subj="events-table"]').exists()).toEqual(false); + expect(wrapper.find('[data-test-subj="events-table"]').exists()).toEqual(true); + expect(wrapper.find('[data-test-subj="events"]').exists()).toEqual(false); }); test('it does NOT render the timeline table when end is empty', () => { @@ -179,7 +181,8 @@ describe('Timeline', () => { ); - expect(wrapper.find('[data-test-subj="events-table"]').exists()).toEqual(false); + expect(wrapper.find('[data-test-subj="events-table"]').exists()).toEqual(true); + expect(wrapper.find('[data-test-subj="events"]').exists()).toEqual(false); }); test('it does NOT render the paging footer when you do NOT have any data providers', () => { diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx index 8da3c257a5db8..e93d23a816911 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx @@ -23,7 +23,7 @@ import deepEqual from 'fast-deep-equal'; import { InPortal } from 'react-reverse-portal'; import { timelineActions, timelineSelectors } from '../../../store/timeline'; -import { Direction } from '../../../../../common/search_strategy'; +import { Direction, TimelineItem } from '../../../../../common/search_strategy'; import { useTimelineEvents } from '../../../containers/index'; import { useKibana } from '../../../../common/lib/kibana'; import { defaultHeaders } from '../body/column_headers/default_headers'; @@ -48,6 +48,8 @@ import { useTimelineEventsCountPortal } from '../../../../common/hooks/use_timel import { TimelineModel } from '../../../../timelines/store/timeline/model'; import { EventDetails } from '../event_details'; import { TimelineDatePickerLock } from '../date_picker_lock'; +import { HideShowContainer } from '../styles'; +import { useTimelineFullScreen } from '../../../../common/containers/use_full_screen'; import { activeTimeline } from '../../../containers/active_timeline_context'; import { ToggleExpandedEvent } from '../../../store/timeline/actions'; @@ -143,6 +145,8 @@ interface OwnProps { timelineId: string; } +const EMPTY_EVENTS: TimelineItem[] = []; + export type Props = OwnProps & PropsFromRedux; export const QueryTabContentComponent: React.FC = ({ @@ -168,6 +172,7 @@ export const QueryTabContentComponent: React.FC = ({ updateEventTypeAndIndexesName, }) => { const { timelineEventsCountPortalNode } = useTimelineEventsCountPortal(); + const { timelineFullScreen } = useTimelineFullScreen(); const { browserFields, docValueFields, @@ -200,6 +205,11 @@ export const QueryTabContentComponent: React.FC = ({ [browserFields, dataProviders, esQueryConfig, filters, indexPattern, kqlMode, kqlQuery] ); + const isBlankTimeline: boolean = useMemo( + () => isEmpty(dataProviders) && isEmpty(filters) && isEmpty(kqlQuery.query), + [dataProviders, filters, kqlQuery] + ); + const canQueryTimeline = useMemo( () => combinedQueries != null && @@ -287,67 +297,66 @@ export const QueryTabContentComponent: React.FC = ({ /> - - - - - - - + + + + + + + + + +
+ + + +
+ + -
-
-
- - - -
- - +
+ + + + - - - {canQueryTimeline ? ( - - - - - + + + {!isBlankTimeline && (
+ } + description={ + + {' '} + + + } + fullWidth + titleSize="xs" + switchProps={{ + 'data-test-subj': `${phase}-readonlySwitch`, + path: `_meta.${phase}.readonlyEnabled`, + }} + > +
+ + ); +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/warm_phase/warm_phase.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/warm_phase/warm_phase.tsx index d572e7a2ed341..36a39eb7c110f 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/warm_phase/warm_phase.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/warm_phase/warm_phase.tsx @@ -27,6 +27,7 @@ import { SetPriorityInputField, DataTierAllocationField, ShrinkField, + ReadonlyField, } from '../shared_fields'; const i18nTexts = { @@ -173,6 +174,9 @@ export const WarmPhase: FunctionComponent = () => { {!isUsingSearchableSnapshotInHotPhase && } {!isUsingSearchableSnapshotInHotPhase && } + + + {/* Data tier allocation section */} { hot: { useRollover: Boolean(hot?.actions?.rollover), bestCompression: hot?.actions?.forcemerge?.index_codec === 'best_compression', + readonlyEnabled: Boolean(hot?.actions?.readonly), }, warm: { enabled: Boolean(warm), warmPhaseOnRollover: warm === undefined ? true : Boolean(warm.min_age === '0ms'), bestCompression: warm?.actions?.forcemerge?.index_codec === 'best_compression', dataTierAllocationType: determineDataTierAllocationType(warm?.actions), + readonlyEnabled: Boolean(warm?.actions?.readonly), }, cold: { enabled: Boolean(cold), diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/deserializer_and_serializer.test.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/deserializer_and_serializer.test.ts index 518a205b12303..b494e87b0bf6f 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/deserializer_and_serializer.test.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/deserializer_and_serializer.test.ts @@ -44,6 +44,7 @@ const originalPolicy: SerializedPolicy = { index_codec: 'best_compression', max_num_segments: 22, }, + readonly: {}, set_priority: { priority: 1, }, @@ -63,6 +64,7 @@ const originalPolicy: SerializedPolicy = { some: 'value', }, }, + readonly: {}, set_priority: { priority: 10, }, @@ -170,6 +172,22 @@ describe('deserializer and serializer', () => { expect(result.phases.warm!.actions.forcemerge).toBeUndefined(); }); + it('removes the readonly action if it is disabled in hot', () => { + formInternal._meta.hot.readonlyEnabled = false; + + const result = serializer(formInternal); + + expect(result.phases.hot!.actions.readonly).toBeUndefined(); + }); + + it('removes the readonly action if it is disabled in warm', () => { + formInternal._meta.warm.readonlyEnabled = false; + + const result = serializer(formInternal); + + expect(result.phases.warm!.actions.readonly).toBeUndefined(); + }); + it('removes set priority if it is disabled in the form', () => { delete formInternal.phases.hot!.actions.set_priority; delete formInternal.phases.warm!.actions.set_priority; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/schema.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/schema.ts index 73a868c392f32..a292a888e78c4 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/schema.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/schema.ts @@ -48,6 +48,10 @@ export const schema: FormSchema = { label: i18nTexts.editPolicy.bestCompressionFieldLabel, helpText: i18nTexts.editPolicy.bestCompressionFieldHelpText, }, + readonlyEnabled: { + defaultValue: false, + label: i18nTexts.editPolicy.readonlyEnabledFieldLabel, + }, }, warm: { enabled: { @@ -76,6 +80,10 @@ export const schema: FormSchema = { allocationNodeAttribute: { label: i18nTexts.editPolicy.allocationNodeAttributeFieldLabel, }, + readonlyEnabled: { + defaultValue: false, + label: i18nTexts.editPolicy.readonlyEnabledFieldLabel, + }, }, cold: { enabled: { diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/serializer/serializer.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/serializer/serializer.ts index 91e175d49de25..75935f149534e 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/serializer/serializer.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/serializer/serializer.ts @@ -68,9 +68,16 @@ export const createSerializer = (originalPolicy?: SerializedPolicy) => ( if (_meta.hot.bestCompression && hotPhaseActions.forcemerge) { hotPhaseActions.forcemerge.index_codec = 'best_compression'; } + + if (_meta.hot.readonlyEnabled) { + hotPhaseActions.readonly = hotPhaseActions.readonly ?? {}; + } else { + delete hotPhaseActions.readonly; + } } else { delete hotPhaseActions.rollover; delete hotPhaseActions.forcemerge; + delete hotPhaseActions.readonly; } if (!updatedPolicy.phases.hot!.actions?.set_priority) { @@ -117,6 +124,12 @@ export const createSerializer = (originalPolicy?: SerializedPolicy) => ( warmPhase.actions.forcemerge!.index_codec = 'best_compression'; } + if (_meta.warm.readonlyEnabled) { + warmPhase.actions.readonly = warmPhase.actions.readonly ?? {}; + } else { + delete warmPhase.actions.readonly; + } + if (!updatedPolicy.phases.warm?.actions?.set_priority) { delete warmPhase.actions.set_priority; } diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/i18n_texts.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/i18n_texts.ts index 75bd3c3e217af..f30a40fdd2bb9 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/i18n_texts.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/i18n_texts.ts @@ -31,6 +31,9 @@ export const i18nTexts = { forceMergeEnabledFieldLabel: i18n.translate('xpack.indexLifecycleMgmt.forcemerge.enableLabel', { defaultMessage: 'Force merge data', }), + readonlyEnabledFieldLabel: i18n.translate('xpack.indexLifecycleMgmt.readonlyFieldLabel', { + defaultMessage: 'Make index read only', + }), maxNumSegmentsFieldLabel: i18n.translate( 'xpack.indexLifecycleMgmt.forceMerge.numberOfSegmentsLabel', { diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/types.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/types.ts index 7d512936290af..f04acea0bbf0a 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/types.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/types.ts @@ -25,11 +25,13 @@ interface HotPhaseMetaFields extends ForcemergeFields { useRollover: boolean; maxStorageSizeUnit?: string; maxAgeUnit?: string; + readonlyEnabled: boolean; } interface WarmPhaseMetaFields extends DataAllocationMetaFields, MinAgeField, ForcemergeFields { enabled: boolean; warmPhaseOnRollover: boolean; + readonlyEnabled: boolean; } interface ColdPhaseMetaFields extends DataAllocationMetaFields, MinAgeField { From 1b3a1bb3852142ebc992cb12f9bf4fc14113cabf Mon Sep 17 00:00:00 2001 From: Sonja Krause-Harder Date: Mon, 14 Dec 2020 17:36:25 +0100 Subject: [PATCH 24/95] Don't rollback on saved objects conflict errors. (#85131) --- .../plugins/fleet/server/errors/handlers.ts | 5 +- x-pack/plugins/fleet/server/errors/index.ts | 1 + .../services/epm/packages/_install_package.ts | 298 +++++++++--------- 3 files changed, 161 insertions(+), 143 deletions(-) diff --git a/x-pack/plugins/fleet/server/errors/handlers.ts b/x-pack/plugins/fleet/server/errors/handlers.ts index 222554e97eb91..fecfcf145ca99 100644 --- a/x-pack/plugins/fleet/server/errors/handlers.ts +++ b/x-pack/plugins/fleet/server/errors/handlers.ts @@ -20,6 +20,7 @@ import { PackageNotFoundError, AgentPolicyNameExistsError, PackageUnsupportedMediaTypeError, + ConcurrentInstallOperationError, } from './index'; type IngestErrorHandler = ( @@ -69,7 +70,9 @@ const getHTTPResponseCode = (error: IngestManagerError): number => { if (error instanceof PackageUnsupportedMediaTypeError) { return 415; // Unsupported Media Type } - + if (error instanceof ConcurrentInstallOperationError) { + return 409; // Conflict + } return 400; // Bad Request }; diff --git a/x-pack/plugins/fleet/server/errors/index.ts b/x-pack/plugins/fleet/server/errors/index.ts index fad4eef66215d..700750761def4 100644 --- a/x-pack/plugins/fleet/server/errors/index.ts +++ b/x-pack/plugins/fleet/server/errors/index.ts @@ -30,3 +30,4 @@ export class PackageInvalidArchiveError extends IngestManagerError {} export class PackageCacheError extends IngestManagerError {} export class PackageOperationNotSupportedError extends IngestManagerError {} export class FleetAdminUserInvalidError extends IngestManagerError {} +export class ConcurrentInstallOperationError extends IngestManagerError {} diff --git a/x-pack/plugins/fleet/server/services/epm/packages/_install_package.ts b/x-pack/plugins/fleet/server/services/epm/packages/_install_package.ts index b0a76016e8eee..5e6ecad9b72f1 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/_install_package.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/_install_package.ts @@ -30,6 +30,7 @@ import { deleteKibanaSavedObjectsAssets } from './remove'; import { installTransform } from '../elasticsearch/transform/install'; import { createInstallation, saveKibanaAssetsRefs, updateVersion } from './install'; import { saveArchiveEntries } from '../archive/storage'; +import { ConcurrentInstallOperationError } from '../../../errors'; // this is only exported for testing // use a leading underscore to indicate it's not the supported path @@ -53,163 +54,176 @@ export async function _installPackage({ installSource: InstallSource; }): Promise { const { name: pkgName, version: pkgVersion } = packageInfo; - // if some installation already exists - if (installedPkg) { - // if the installation is currently running, don't try to install - // instead, only return already installed assets - if ( - installedPkg.attributes.install_status === 'installing' && - Date.now() - Date.parse(installedPkg.attributes.install_started_at) < - MAX_TIME_COMPLETE_INSTALL - ) { - let assets: AssetReference[] = []; - assets = assets.concat(installedPkg.attributes.installed_es); - assets = assets.concat(installedPkg.attributes.installed_kibana); - return assets; + try { + // if some installation already exists + if (installedPkg) { + // if the installation is currently running, don't try to install + // instead, only return already installed assets + if ( + installedPkg.attributes.install_status === 'installing' && + Date.now() - Date.parse(installedPkg.attributes.install_started_at) < + MAX_TIME_COMPLETE_INSTALL + ) { + throw new ConcurrentInstallOperationError( + `Concurrent installation or upgrade of ${pkgName || 'unknown'}-${ + pkgVersion || 'unknown' + } detected, aborting.` + ); + } else { + // if no installation is running, or the installation has been running longer than MAX_TIME_COMPLETE_INSTALL + // (it might be stuck) update the saved object and proceed + await savedObjectsClient.update(PACKAGES_SAVED_OBJECT_TYPE, pkgName, { + install_version: pkgVersion, + install_status: 'installing', + install_started_at: new Date().toISOString(), + install_source: installSource, + }); + } } else { - // if no installation is running, or the installation has been running longer than MAX_TIME_COMPLETE_INSTALL - // (it might be stuck) update the saved object and proceed - await savedObjectsClient.update(PACKAGES_SAVED_OBJECT_TYPE, pkgName, { - install_version: pkgVersion, - install_status: 'installing', - install_started_at: new Date().toISOString(), - install_source: installSource, + await createInstallation({ + savedObjectsClient, + packageInfo, + installSource, }); } - } else { - await createInstallation({ - savedObjectsClient, - packageInfo, - installSource, - }); - } - // kick off `installIndexPatterns` & `installKibanaAssets` as early as possible because they're the longest running operations - // we don't `await` here because we don't want to delay starting the many other `install*` functions - // however, without an `await` or a `.catch` we haven't defined how to handle a promise rejection - // we define it many lines and potentially seconds of wall clock time later in - // `await Promise.all([installKibanaAssetsPromise, installIndexPatternPromise]);` - // if we encounter an error before we there, we'll have an "unhandled rejection" which causes its own problems - // the program will log something like this _and exit/crash_ - // Unhandled Promise rejection detected: - // RegistryResponseError or some other error - // Terminating process... - // server crashed with status code 1 - // - // add a `.catch` to prevent the "unhandled rejection" case - // in that `.catch`, set something that indicates a failure - // check for that failure later and act accordingly (throw, ignore, return) - let installIndexPatternError; - const installIndexPatternPromise = installIndexPatterns( - savedObjectsClient, - pkgName, - pkgVersion, - installSource - ).catch((reason) => (installIndexPatternError = reason)); - const kibanaAssets = await getKibanaAssets(paths); - if (installedPkg) - await deleteKibanaSavedObjectsAssets( + // kick off `installIndexPatterns` & `installKibanaAssets` as early as possible because they're the longest running operations + // we don't `await` here because we don't want to delay starting the many other `install*` functions + // however, without an `await` or a `.catch` we haven't defined how to handle a promise rejection + // we define it many lines and potentially seconds of wall clock time later in + // `await Promise.all([installKibanaAssetsPromise, installIndexPatternPromise]);` + // if we encounter an error before we there, we'll have an "unhandled rejection" which causes its own problems + // the program will log something like this _and exit/crash_ + // Unhandled Promise rejection detected: + // RegistryResponseError or some other error + // Terminating process... + // server crashed with status code 1 + // + // add a `.catch` to prevent the "unhandled rejection" case + // in that `.catch`, set something that indicates a failure + // check for that failure later and act accordingly (throw, ignore, return) + let installIndexPatternError; + const installIndexPatternPromise = installIndexPatterns( savedObjectsClient, - installedPkg.attributes.installed_kibana + pkgName, + pkgVersion, + installSource + ).catch((reason) => (installIndexPatternError = reason)); + const kibanaAssets = await getKibanaAssets(paths); + if (installedPkg) + await deleteKibanaSavedObjectsAssets( + savedObjectsClient, + installedPkg.attributes.installed_kibana + ); + // save new kibana refs before installing the assets + const installedKibanaAssetsRefs = await saveKibanaAssetsRefs( + savedObjectsClient, + pkgName, + kibanaAssets ); - // save new kibana refs before installing the assets - const installedKibanaAssetsRefs = await saveKibanaAssetsRefs( - savedObjectsClient, - pkgName, - kibanaAssets - ); - let installKibanaAssetsError; - const installKibanaAssetsPromise = installKibanaAssets({ - savedObjectsClient, - pkgName, - kibanaAssets, - }).catch((reason) => (installKibanaAssetsError = reason)); - - // the rest of the installation must happen in sequential order - // currently only the base package has an ILM policy - // at some point ILM policies can be installed/modified - // per data stream and we should then save them - await installILMPolicy(paths, callCluster); - - // installs versionized pipelines without removing currently installed ones - const installedPipelines = await installPipelines( - packageInfo, - paths, - callCluster, - savedObjectsClient - ); - // install or update the templates referencing the newly installed pipelines - const installedTemplates = await installTemplates( - packageInfo, - callCluster, - paths, - savedObjectsClient - ); - - // update current backing indices of each data stream - await updateCurrentWriteIndices(callCluster, installedTemplates); + let installKibanaAssetsError; + const installKibanaAssetsPromise = installKibanaAssets({ + savedObjectsClient, + pkgName, + kibanaAssets, + }).catch((reason) => (installKibanaAssetsError = reason)); - const installedTransforms = await installTransform( - packageInfo, - paths, - callCluster, - savedObjectsClient - ); + // the rest of the installation must happen in sequential order + // currently only the base package has an ILM policy + // at some point ILM policies can be installed/modified + // per data stream and we should then save them + await installILMPolicy(paths, callCluster); - // if this is an update or retrying an update, delete the previous version's pipelines - if ((installType === 'update' || installType === 'reupdate') && installedPkg) { - await deletePreviousPipelines( + // installs versionized pipelines without removing currently installed ones + const installedPipelines = await installPipelines( + packageInfo, + paths, callCluster, - savedObjectsClient, - pkgName, - installedPkg.attributes.version + savedObjectsClient ); - } - // pipelines from a different version may have installed during a failed update - if (installType === 'rollback' && installedPkg) { - await deletePreviousPipelines( + // install or update the templates referencing the newly installed pipelines + const installedTemplates = await installTemplates( + packageInfo, callCluster, - savedObjectsClient, - pkgName, - installedPkg.attributes.install_version + paths, + savedObjectsClient ); - } - const installedTemplateRefs = installedTemplates.map((template) => ({ - id: template.templateName, - type: ElasticsearchAssetType.indexTemplate, - })); - // make sure the assets are installed (or didn't error) - if (installIndexPatternError) throw installIndexPatternError; - if (installKibanaAssetsError) throw installKibanaAssetsError; - await Promise.all([installKibanaAssetsPromise, installIndexPatternPromise]); + // update current backing indices of each data stream + await updateCurrentWriteIndices(callCluster, installedTemplates); - const packageAssetResults = await saveArchiveEntries({ - savedObjectsClient, - paths, - packageInfo, - installSource, - }); - const packageAssetRefs: PackageAssetReference[] = packageAssetResults.saved_objects.map( - (result) => ({ - id: result.id, - type: ASSETS_SAVED_OBJECT_TYPE, - }) - ); + const installedTransforms = await installTransform( + packageInfo, + paths, + callCluster, + savedObjectsClient + ); - // update to newly installed version when all assets are successfully installed - if (installedPkg) await updateVersion(savedObjectsClient, pkgName, pkgVersion); + // if this is an update or retrying an update, delete the previous version's pipelines + if ((installType === 'update' || installType === 'reupdate') && installedPkg) { + await deletePreviousPipelines( + callCluster, + savedObjectsClient, + pkgName, + installedPkg.attributes.version + ); + } + // pipelines from a different version may have installed during a failed update + if (installType === 'rollback' && installedPkg) { + await deletePreviousPipelines( + callCluster, + savedObjectsClient, + pkgName, + installedPkg.attributes.install_version + ); + } + const installedTemplateRefs = installedTemplates.map((template) => ({ + id: template.templateName, + type: ElasticsearchAssetType.indexTemplate, + })); - await savedObjectsClient.update(PACKAGES_SAVED_OBJECT_TYPE, pkgName, { - install_version: pkgVersion, - install_status: 'installed', - package_assets: packageAssetRefs, - }); + // make sure the assets are installed (or didn't error) + if (installIndexPatternError) throw installIndexPatternError; + if (installKibanaAssetsError) throw installKibanaAssetsError; + await Promise.all([installKibanaAssetsPromise, installIndexPatternPromise]); - return [ - ...installedKibanaAssetsRefs, - ...installedPipelines, - ...installedTemplateRefs, - ...installedTransforms, - ]; + const packageAssetResults = await saveArchiveEntries({ + savedObjectsClient, + paths, + packageInfo, + installSource, + }); + const packageAssetRefs: PackageAssetReference[] = packageAssetResults.saved_objects.map( + (result) => ({ + id: result.id, + type: ASSETS_SAVED_OBJECT_TYPE, + }) + ); + + // update to newly installed version when all assets are successfully installed + if (installedPkg) await updateVersion(savedObjectsClient, pkgName, pkgVersion); + + await savedObjectsClient.update(PACKAGES_SAVED_OBJECT_TYPE, pkgName, { + install_version: pkgVersion, + install_status: 'installed', + package_assets: packageAssetRefs, + }); + + return [ + ...installedKibanaAssetsRefs, + ...installedPipelines, + ...installedTemplateRefs, + ...installedTransforms, + ]; + } catch (err) { + if (savedObjectsClient.errors.isConflictError(err)) { + throw new ConcurrentInstallOperationError( + `Concurrent installation or upgrade of ${pkgName || 'unknown'}-${ + pkgVersion || 'unknown' + } detected, aborting. Original error: ${err.message}` + ); + } else { + throw err; + } + } } From a6e6b62658fe9d1783720781326e5c481ff0f284 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Mon, 14 Dec 2020 17:51:18 +0100 Subject: [PATCH 25/95] [ML] Transforms: Support for missing_bucket in transform advanced pivot editor (#85758) Adds support for missing_bucket with group-by configurations in the advanced editor for pivot configurations. Previously, the editor would allow you to add the setting, but it would be stripped from the config once the transform gets created. --- .../transform/common/types/pivot_group_by.ts | 3 + .../public/app/common/pivot_group_by.ts | 3 + .../public/app/common/request.test.ts | 97 ++++++++++--------- .../transform/public/app/common/request.ts | 10 ++ 4 files changed, 67 insertions(+), 46 deletions(-) diff --git a/x-pack/plugins/transform/common/types/pivot_group_by.ts b/x-pack/plugins/transform/common/types/pivot_group_by.ts index bfaf17a32b580..3d1a833b1b562 100644 --- a/x-pack/plugins/transform/common/types/pivot_group_by.ts +++ b/x-pack/plugins/transform/common/types/pivot_group_by.ts @@ -12,6 +12,7 @@ export type GenericAgg = object; export interface TermsAgg { terms: { field: EsFieldName; + missing_bucket?: boolean; }; } @@ -19,6 +20,7 @@ export interface HistogramAgg { histogram: { field: EsFieldName; interval: string; + missing_bucket?: boolean; }; } @@ -26,6 +28,7 @@ export interface DateHistogramAgg { date_histogram: { field: EsFieldName; calendar_interval: string; + missing_bucket?: boolean; }; } diff --git a/x-pack/plugins/transform/public/app/common/pivot_group_by.ts b/x-pack/plugins/transform/public/app/common/pivot_group_by.ts index 2c2bac369c72d..281aee0805161 100644 --- a/x-pack/plugins/transform/public/app/common/pivot_group_by.ts +++ b/x-pack/plugins/transform/public/app/common/pivot_group_by.ts @@ -52,17 +52,20 @@ interface GroupByDateHistogram extends GroupByConfigBase { agg: PIVOT_SUPPORTED_GROUP_BY_AGGS.DATE_HISTOGRAM; field: EsFieldName; calendar_interval: string; + missing_bucket?: boolean; } interface GroupByHistogram extends GroupByConfigBase { agg: PIVOT_SUPPORTED_GROUP_BY_AGGS.HISTOGRAM; field: EsFieldName; interval: string; + missing_bucket?: boolean; } interface GroupByTerms extends GroupByConfigBase { agg: PIVOT_SUPPORTED_GROUP_BY_AGGS.TERMS; field: EsFieldName; + missing_bucket?: boolean; } export type GroupByConfigWithInterval = GroupByDateHistogram | GroupByHistogram; diff --git a/x-pack/plugins/transform/public/app/common/request.test.ts b/x-pack/plugins/transform/public/app/common/request.test.ts index 46ace2c3315a5..cc58308a165c8 100644 --- a/x-pack/plugins/transform/public/app/common/request.test.ts +++ b/x-pack/plugins/transform/public/app/common/request.test.ts @@ -18,6 +18,7 @@ import { getPreviewTransformRequestBody, getCreateTransformRequestBody, getCreateTransformSettingsRequestBody, + getMissingBucketConfig, getPivotQuery, isDefaultQuery, isMatchAllQuery, @@ -28,6 +29,20 @@ import { const simpleQuery: PivotQuery = { query_string: { query: 'airline:AAL' } }; +const groupByTerms: PivotGroupByConfig = { + agg: PIVOT_SUPPORTED_GROUP_BY_AGGS.TERMS, + field: 'the-group-by-field', + aggName: 'the-group-by-agg-name', + dropDownName: 'the-group-by-drop-down-name', +}; + +const aggsAvg: PivotAggsConfig = { + agg: PIVOT_SUPPORTED_AGGS.AVG, + field: 'the-agg-field', + aggName: 'the-agg-agg-name', + dropDownName: 'the-agg-drop-down-name', +}; + describe('Transform: Common', () => { test('isMatchAllQuery()', () => { expect(isMatchAllQuery(defaultQuery)).toBe(false); @@ -47,6 +62,16 @@ describe('Transform: Common', () => { expect(isDefaultQuery(simpleQuery)).toBe(false); }); + test('getMissingBucketConfig()', () => { + expect(getMissingBucketConfig(groupByTerms)).toEqual({}); + expect(getMissingBucketConfig({ ...groupByTerms, ...{ missing_bucket: true } })).toEqual({ + missing_bucket: true, + }); + expect(getMissingBucketConfig({ ...groupByTerms, ...{ missing_bucket: false } })).toEqual({ + missing_bucket: false, + }); + }); + test('getPivotQuery()', () => { const query = getPivotQuery('the-query'); @@ -60,22 +85,8 @@ describe('Transform: Common', () => { test('getPreviewTransformRequestBody()', () => { const query = getPivotQuery('the-query'); - const groupBy: PivotGroupByConfig[] = [ - { - agg: PIVOT_SUPPORTED_GROUP_BY_AGGS.TERMS, - field: 'the-group-by-field', - aggName: 'the-group-by-agg-name', - dropDownName: 'the-group-by-drop-down-name', - }, - ]; - const aggs: PivotAggsConfig[] = [ - { - agg: PIVOT_SUPPORTED_AGGS.AVG, - field: 'the-agg-field', - aggName: 'the-agg-agg-name', - dropDownName: 'the-agg-drop-down-name', - }, - ]; + const groupBy: PivotGroupByConfig[] = [groupByTerms]; + const aggs: PivotAggsConfig[] = [aggsAvg]; const request = getPreviewTransformRequestBody('the-index-pattern-title', query, groupBy, aggs); expect(request).toEqual({ @@ -92,22 +103,8 @@ describe('Transform: Common', () => { test('getPreviewTransformRequestBody() with comma-separated index pattern', () => { const query = getPivotQuery('the-query'); - const groupBy: PivotGroupByConfig[] = [ - { - agg: PIVOT_SUPPORTED_GROUP_BY_AGGS.TERMS, - field: 'the-group-by-field', - aggName: 'the-group-by-agg-name', - dropDownName: 'the-group-by-drop-down-name', - }, - ]; - const aggs: PivotAggsConfig[] = [ - { - agg: PIVOT_SUPPORTED_AGGS.AVG, - field: 'the-agg-field', - aggName: 'the-agg-agg-name', - dropDownName: 'the-agg-drop-down-name', - }, - ]; + const groupBy: PivotGroupByConfig[] = [groupByTerms]; + const aggs: PivotAggsConfig[] = [aggsAvg]; const request = getPreviewTransformRequestBody( 'the-index-pattern-title,the-other-title', query, @@ -127,22 +124,30 @@ describe('Transform: Common', () => { }); }); + test('getPreviewTransformRequestBody() with missing_buckets config', () => { + const query = getPivotQuery('the-query'); + const groupBy: PivotGroupByConfig[] = [{ ...groupByTerms, ...{ missing_bucket: true } }]; + const aggs: PivotAggsConfig[] = [aggsAvg]; + const request = getPreviewTransformRequestBody('the-index-pattern-title', query, groupBy, aggs); + + expect(request).toEqual({ + pivot: { + aggregations: { 'the-agg-agg-name': { avg: { field: 'the-agg-field' } } }, + group_by: { + 'the-group-by-agg-name': { terms: { field: 'the-group-by-field', missing_bucket: true } }, + }, + }, + source: { + index: ['the-index-pattern-title'], + query: { query_string: { default_operator: 'AND', query: 'the-query' } }, + }, + }); + }); + test('getCreateTransformRequestBody()', () => { - const groupBy: PivotGroupByConfig = { - agg: PIVOT_SUPPORTED_GROUP_BY_AGGS.TERMS, - field: 'the-group-by-field', - aggName: 'the-group-by-agg-name', - dropDownName: 'the-group-by-drop-down-name', - }; - const agg: PivotAggsConfig = { - agg: PIVOT_SUPPORTED_AGGS.AVG, - field: 'the-agg-field', - aggName: 'the-agg-agg-name', - dropDownName: 'the-agg-drop-down-name', - }; const pivotState: StepDefineExposedState = { - aggList: { 'the-agg-name': agg }, - groupByList: { 'the-group-by-name': groupBy }, + aggList: { 'the-agg-name': aggsAvg }, + groupByList: { 'the-group-by-name': groupByTerms }, isAdvancedPivotEditorEnabled: false, isAdvancedSourceEditorEnabled: false, sourceConfigUpdated: false, diff --git a/x-pack/plugins/transform/public/app/common/request.ts b/x-pack/plugins/transform/public/app/common/request.ts index 8ee235baf7c5a..d92a4ee258757 100644 --- a/x-pack/plugins/transform/public/app/common/request.ts +++ b/x-pack/plugins/transform/public/app/common/request.ts @@ -30,6 +30,7 @@ import { isGroupByDateHistogram, isGroupByHistogram, isGroupByTerms, + GroupByConfigWithUiSupport, PivotGroupByConfig, } from '../common'; @@ -71,6 +72,12 @@ export function isDefaultQuery(query: PivotQuery): boolean { return isSimpleQuery(query) && query.query_string.query === '*'; } +export const getMissingBucketConfig = ( + g: GroupByConfigWithUiSupport +): { missing_bucket?: boolean } => { + return g.missing_bucket !== undefined ? { missing_bucket: g.missing_bucket } : {}; +}; + export function getPreviewTransformRequestBody( indexPatternTitle: IndexPattern['title'], query: PivotQuery, @@ -95,6 +102,7 @@ export function getPreviewTransformRequestBody( const termsAgg: TermsAgg = { terms: { field: g.field, + ...getMissingBucketConfig(g), }, }; request.pivot.group_by[g.aggName] = termsAgg; @@ -103,6 +111,7 @@ export function getPreviewTransformRequestBody( histogram: { field: g.field, interval: g.interval, + ...getMissingBucketConfig(g), }, }; request.pivot.group_by[g.aggName] = histogramAgg; @@ -111,6 +120,7 @@ export function getPreviewTransformRequestBody( date_histogram: { field: g.field, calendar_interval: g.calendar_interval, + ...getMissingBucketConfig(g), }, }; request.pivot.group_by[g.aggName] = dateHistogramAgg; From 8b6831a55a30e20d0845c02ae88935c43754af74 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Mon, 14 Dec 2020 10:05:59 -0700 Subject: [PATCH 26/95] [Maps] fix color-style disappears when mapping by percentiles when breaks are identical (#85654) * [Maps] fix color-style disappears when mapping by percentiles when breaks are identical * tslint Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../properties/dynamic_style_property.test.ts | 47 +++++++++++++++++++ .../properties/dynamic_style_property.tsx | 30 ++++++++---- 2 files changed, 68 insertions(+), 9 deletions(-) create mode 100644 x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_style_property.test.ts diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_style_property.test.ts b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_style_property.test.ts new file mode 100644 index 0000000000000..dabd8cd4cf4ee --- /dev/null +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_style_property.test.ts @@ -0,0 +1,47 @@ +/* + * 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 { percentilesValuesToFieldMeta } from './dynamic_style_property'; + +describe('percentilesValuesToFieldMeta', () => { + test('should return null when values is not defined', () => { + expect(percentilesValuesToFieldMeta(undefined)).toBeNull(); + expect(percentilesValuesToFieldMeta({})).toBeNull(); + }); + + test('should convert values to percentiles field meta', () => { + expect(percentilesValuesToFieldMeta(undefined)).toBeNull(); + expect( + percentilesValuesToFieldMeta({ + values: { + '25.0': 375.0, + '50.0': 400.0, + '75.0': 550.0, + }, + }) + ).toEqual([ + { percentile: '25.0', value: 375.0 }, + { percentile: '50.0', value: 400.0 }, + { percentile: '75.0', value: 550.0 }, + ]); + }); + + test('should remove duplicated percentile percentilesValuesToFieldMeta', () => { + expect(percentilesValuesToFieldMeta(undefined)).toBeNull(); + expect( + percentilesValuesToFieldMeta({ + values: { + '25.0': 375.0, + '50.0': 375.0, + '75.0': 550.0, + }, + }) + ).toEqual([ + { percentile: '25.0', value: 375.0 }, + { percentile: '75.0', value: 550.0 }, + ]); + }); +}); diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_style_property.tsx b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_style_property.tsx index 2f2ddd7d539cf..882247e375ddc 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_style_property.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_style_property.tsx @@ -28,6 +28,7 @@ import { import { CategoryFieldMeta, FieldMetaOptions, + PercentilesFieldMeta, RangeFieldMeta, StyleMetaData, } from '../../../../../common/descriptor_types'; @@ -144,15 +145,8 @@ export class DynamicStyleProperty const styleMetaData = styleMetaDataRequest.getData() as StyleMetaData; const percentiles = styleMetaData[`${this._field.getRootName()}_percentiles`] as | undefined - | { values?: { [key: string]: number } }; - return percentiles !== undefined && percentiles.values !== undefined - ? Object.keys(percentiles.values).map((key) => { - return { - percentile: key, - value: percentiles.values![key], - }; - }) - : null; + | PercentilesValues; + return percentilesValuesToFieldMeta(percentiles); } getCategoryFieldMeta() { @@ -499,3 +493,21 @@ export function getNumericalMbFeatureStateValue(value: RawValue) { const valueAsFloat = parseFloat(value); return isNaN(valueAsFloat) ? null : valueAsFloat; } + +interface PercentilesValues { + values?: { [key: string]: number }; +} +export function percentilesValuesToFieldMeta( + percentiles?: PercentilesValues | undefined +): PercentilesFieldMeta | null { + if (percentiles === undefined || percentiles.values === undefined) { + return null; + } + const percentilesFieldMeta = Object.keys(percentiles.values).map((key) => { + return { + percentile: key, + value: percentiles.values![key], + }; + }); + return _.uniqBy(percentilesFieldMeta, 'value'); +} From 6ae00fb5146476b9e6fe5e8ad115f59ff96f715d Mon Sep 17 00:00:00 2001 From: Chris Roberson Date: Mon, 14 Dec 2020 12:12:11 -0500 Subject: [PATCH 27/95] [Monitoring] Convert Beats-related server files that read from _source to typescript (#85193) * Beats to TS * PR feedback * Fix types * Fix types * Fix failing test Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../monitoring/server/lib/apm/get_apm_info.ts | 22 ++++-- .../monitoring/server/lib/apm/get_apms.ts | 18 +++-- .../server/lib/beats/get_beat_summary.ts | 14 +++- .../lib/beats/{get_beats.js => get_beats.ts} | 77 +++++++++++++------ x-pack/plugins/monitoring/server/types.ts | 4 +- 5 files changed, 92 insertions(+), 43 deletions(-) rename x-pack/plugins/monitoring/server/lib/beats/{get_beats.js => get_beats.ts} (64%) diff --git a/x-pack/plugins/monitoring/server/lib/apm/get_apm_info.ts b/x-pack/plugins/monitoring/server/lib/apm/get_apm_info.ts index 4ca708e9d2832..7d471d528595e 100644 --- a/x-pack/plugins/monitoring/server/lib/apm/get_apm_info.ts +++ b/x-pack/plugins/monitoring/server/lib/apm/get_apm_info.ts @@ -21,17 +21,23 @@ export function handleResponse(response: ElasticsearchResponse, apmUuid: string) return {}; } - const firstStats = response.hits.hits[0].inner_hits.first_hit.hits.hits[0]._source.beats_stats; - const stats = response.hits.hits[0]._source.beats_stats; + const firstHit = response.hits.hits[0]; - if (!firstStats || !stats) { - return {}; + let firstStats = null; + const stats = firstHit._source.beats_stats ?? {}; + + if ( + firstHit.inner_hits?.first_hit?.hits?.hits && + firstHit.inner_hits?.first_hit?.hits?.hits.length > 0 && + firstHit.inner_hits.first_hit.hits.hits[0]._source.beats_stats + ) { + firstStats = firstHit.inner_hits.first_hit.hits.hits[0]._source.beats_stats; } - const eventsTotalFirst = firstStats.metrics?.libbeat?.pipeline?.events?.total; - const eventsEmittedFirst = firstStats.metrics?.libbeat?.pipeline?.events?.published; - const eventsDroppedFirst = firstStats.metrics?.libbeat?.pipeline?.events?.dropped; - const bytesWrittenFirst = firstStats.metrics?.libbeat?.output?.write?.bytes; + const eventsTotalFirst = firstStats?.metrics?.libbeat?.pipeline?.events?.total; + const eventsEmittedFirst = firstStats?.metrics?.libbeat?.pipeline?.events?.published; + const eventsDroppedFirst = firstStats?.metrics?.libbeat?.pipeline?.events?.dropped; + const bytesWrittenFirst = firstStats?.metrics?.libbeat?.output?.write?.bytes; const eventsTotalLast = stats.metrics?.libbeat?.pipeline?.events?.total; const eventsEmittedLast = stats.metrics?.libbeat?.pipeline?.events?.published; diff --git a/x-pack/plugins/monitoring/server/lib/apm/get_apms.ts b/x-pack/plugins/monitoring/server/lib/apm/get_apms.ts index f6df94f8de138..7677677ea5e75 100644 --- a/x-pack/plugins/monitoring/server/lib/apm/get_apms.ts +++ b/x-pack/plugins/monitoring/server/lib/apm/get_apms.ts @@ -24,9 +24,13 @@ export function handleResponse(response: ElasticsearchResponse, start: number, e return accum; } - const earliestStats = hit.inner_hits.earliest.hits.hits[0]._source.beats_stats; - if (!earliestStats) { - return accum; + let earliestStats = null; + if ( + hit.inner_hits?.earliest?.hits?.hits && + hit.inner_hits?.earliest?.hits?.hits.length > 0 && + hit.inner_hits.earliest.hits.hits[0]._source.beats_stats + ) { + earliestStats = hit.inner_hits.earliest.hits.hits[0]._source.beats_stats; } const uuid = stats?.beat?.uuid; @@ -41,7 +45,7 @@ export function handleResponse(response: ElasticsearchResponse, start: number, e // add the beat const rateOptions = { hitTimestamp: stats.timestamp, - earliestHitTimestamp: earliestStats.timestamp, + earliestHitTimestamp: earliestStats?.timestamp, timeWindowMin: start, timeWindowMax: end, }; @@ -54,14 +58,14 @@ export function handleResponse(response: ElasticsearchResponse, start: number, e const { rate: totalEventsRate } = calculateRate({ latestTotal: stats.metrics?.libbeat?.pipeline?.events?.total, - earliestTotal: earliestStats.metrics?.libbeat?.pipeline?.events?.total, + earliestTotal: earliestStats?.metrics?.libbeat?.pipeline?.events?.total, ...rateOptions, }); const errorsWrittenLatest = stats.metrics?.libbeat?.output?.write?.errors ?? 0; - const errorsWrittenEarliest = earliestStats.metrics?.libbeat?.output?.write?.errors ?? 0; + const errorsWrittenEarliest = earliestStats?.metrics?.libbeat?.output?.write?.errors ?? 0; const errorsReadLatest = stats.metrics?.libbeat?.output?.read?.errors ?? 0; - const errorsReadEarliest = earliestStats.metrics?.libbeat?.output?.read?.errors ?? 0; + const errorsReadEarliest = earliestStats?.metrics?.libbeat?.output?.read?.errors ?? 0; const errors = getDiffCalculation( errorsWrittenLatest + errorsReadLatest, errorsWrittenEarliest + errorsReadEarliest diff --git a/x-pack/plugins/monitoring/server/lib/beats/get_beat_summary.ts b/x-pack/plugins/monitoring/server/lib/beats/get_beat_summary.ts index 57325673a131a..80b5efda4047a 100644 --- a/x-pack/plugins/monitoring/server/lib/beats/get_beat_summary.ts +++ b/x-pack/plugins/monitoring/server/lib/beats/get_beat_summary.ts @@ -18,8 +18,18 @@ export function handleResponse(response: ElasticsearchResponse, beatUuid: string return {}; } - const firstStats = response.hits.hits[0].inner_hits.first_hit.hits.hits[0]._source.beats_stats; - const stats = response.hits.hits[0]._source.beats_stats; + const firstHit = response.hits.hits[0]; + + let firstStats = null; + if ( + firstHit.inner_hits?.first_hit?.hits?.hits && + firstHit.inner_hits?.first_hit?.hits?.hits.length > 0 && + firstHit.inner_hits.first_hit.hits.hits[0]._source.beats_stats + ) { + firstStats = firstHit.inner_hits.first_hit.hits.hits[0]._source.beats_stats; + } + + const stats = firstHit._source.beats_stats ?? {}; const eventsTotalFirst = firstStats?.metrics?.libbeat?.pipeline?.events?.total ?? null; const eventsEmittedFirst = firstStats?.metrics?.libbeat?.pipeline?.events?.published ?? null; diff --git a/x-pack/plugins/monitoring/server/lib/beats/get_beats.js b/x-pack/plugins/monitoring/server/lib/beats/get_beats.ts similarity index 64% rename from x-pack/plugins/monitoring/server/lib/beats/get_beats.js rename to x-pack/plugins/monitoring/server/lib/beats/get_beats.ts index af4b6c31a3e5e..beda4334b4937 100644 --- a/x-pack/plugins/monitoring/server/lib/beats/get_beats.js +++ b/x-pack/plugins/monitoring/server/lib/beats/get_beats.ts @@ -5,18 +5,39 @@ */ import moment from 'moment'; -import { upperFirst, get } from 'lodash'; +import { upperFirst } from 'lodash'; +// @ts-ignore import { checkParam } from '../error_missing_required'; +// @ts-ignore import { createBeatsQuery } from './create_beats_query'; +// @ts-ignore import { calculateRate } from '../calculate_rate'; +// @ts-ignore import { getDiffCalculation } from './_beats_stats'; +import { ElasticsearchResponse, LegacyRequest } from '../../types'; + +interface Beat { + uuid: string | undefined; + name: string | undefined; + type: string | undefined; + output: string | undefined; + total_events_rate: number; + bytes_sent_rate: number; + memory: number | undefined; + version: string | undefined; + errors: any; +} -export function handleResponse(response, start, end) { - const hits = get(response, 'hits.hits', []); - const initial = { ids: new Set(), beats: [] }; +export function handleResponse(response: ElasticsearchResponse, start: number, end: number) { + const hits = response.hits?.hits ?? []; + const initial: { ids: Set; beats: Beat[] } = { ids: new Set(), beats: [] }; const { beats } = hits.reduce((accum, hit) => { - const stats = get(hit, '_source.beats_stats'); - const uuid = get(stats, 'beat.uuid'); + const stats = hit._source.beats_stats; + const uuid = stats?.beat?.uuid; + + if (!uuid) { + return accum; + } // skip this duplicated beat, newer one was already added if (accum.ids.has(uuid)) { @@ -25,47 +46,55 @@ export function handleResponse(response, start, end) { // add another beat summary accum.ids.add(uuid); - const earliestStats = get(hit, 'inner_hits.earliest.hits.hits[0]._source.beats_stats'); + + let earliestStats = null; + if ( + hit.inner_hits?.earliest?.hits?.hits && + hit.inner_hits?.earliest?.hits?.hits.length > 0 && + hit.inner_hits.earliest.hits.hits[0]._source.beats_stats + ) { + earliestStats = hit.inner_hits.earliest.hits.hits[0]._source.beats_stats; + } // add the beat const rateOptions = { - hitTimestamp: get(stats, 'timestamp'), - earliestHitTimestamp: get(earliestStats, 'timestamp'), + hitTimestamp: stats?.timestamp, + earliestHitTimestamp: earliestStats?.timestamp, timeWindowMin: start, timeWindowMax: end, }; const { rate: bytesSentRate } = calculateRate({ - latestTotal: get(stats, 'metrics.libbeat.output.write.bytes'), - earliestTotal: get(earliestStats, 'metrics.libbeat.output.write.bytes'), + latestTotal: stats?.metrics?.libbeat?.output?.write?.bytes, + earliestTotal: earliestStats?.metrics?.libbeat?.output?.write?.bytes, ...rateOptions, }); const { rate: totalEventsRate } = calculateRate({ - latestTotal: get(stats, 'metrics.libbeat.pipeline.events.total'), - earliestTotal: get(earliestStats, 'metrics.libbeat.pipeline.events.total'), + latestTotal: stats?.metrics?.libbeat?.pipeline?.events?.total, + earliestTotal: earliestStats?.metrics?.libbeat?.pipeline?.events?.total, ...rateOptions, }); - const errorsWrittenLatest = get(stats, 'metrics.libbeat.output.write.errors'); - const errorsWrittenEarliest = get(earliestStats, 'metrics.libbeat.output.write.errors'); - const errorsReadLatest = get(stats, 'metrics.libbeat.output.read.errors'); - const errorsReadEarliest = get(earliestStats, 'metrics.libbeat.output.read.errors'); + const errorsWrittenLatest = stats?.metrics?.libbeat?.output?.write?.errors ?? 0; + const errorsWrittenEarliest = earliestStats?.metrics?.libbeat?.output?.write?.errors ?? 0; + const errorsReadLatest = stats?.metrics?.libbeat?.output?.read?.errors ?? 0; + const errorsReadEarliest = earliestStats?.metrics?.libbeat?.output?.read?.errors ?? 0; const errors = getDiffCalculation( errorsWrittenLatest + errorsReadLatest, errorsWrittenEarliest + errorsReadEarliest ); accum.beats.push({ - uuid: get(stats, 'beat.uuid'), - name: get(stats, 'beat.name'), - type: upperFirst(get(stats, 'beat.type')), - output: upperFirst(get(stats, 'metrics.libbeat.output.type')), + uuid: stats?.beat?.uuid, + name: stats?.beat?.name, + type: upperFirst(stats?.beat?.type), + output: upperFirst(stats?.metrics?.libbeat?.output?.type), total_events_rate: totalEventsRate, bytes_sent_rate: bytesSentRate, errors, - memory: get(stats, 'metrics.beat.memstats.memory_alloc'), - version: get(stats, 'beat.version'), + memory: stats?.metrics?.beat?.memstats?.memory_alloc, + version: stats?.beat?.version, }); return accum; @@ -74,7 +103,7 @@ export function handleResponse(response, start, end) { return beats; } -export async function getBeats(req, beatsIndexPattern, clusterUuid) { +export async function getBeats(req: LegacyRequest, beatsIndexPattern: string, clusterUuid: string) { checkParam(beatsIndexPattern, 'beatsIndexPattern in getBeats'); const config = req.server.config(); diff --git a/x-pack/plugins/monitoring/server/types.ts b/x-pack/plugins/monitoring/server/types.ts index 73eea99467c59..84b331df8ba42 100644 --- a/x-pack/plugins/monitoring/server/types.ts +++ b/x-pack/plugins/monitoring/server/types.ts @@ -121,9 +121,9 @@ export interface ElasticsearchResponse { export interface ElasticsearchResponseHit { _source: ElasticsearchSource; - inner_hits: { + inner_hits?: { [field: string]: { - hits: { + hits?: { hits: ElasticsearchResponseHit[]; total: { value: number; From 7a5c3b482c39b1d026381df2bf4e60eddf787487 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Mon, 14 Dec 2020 18:29:18 +0100 Subject: [PATCH 28/95] [APM] Fix Transaction duration distribution barchart clickarea (#84394) * [APM] select transaction distribution by clicking on the entire bucket * fixing margins and bucket click * changing annotation color * adding tooltip placement bottom * addressing pr comments Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../app/TransactionDetails/Distribution/index.tsx | 15 +++++++-------- .../public/hooks/use_chart_theme.tsx | 6 ++++++ 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/Distribution/index.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/Distribution/index.tsx index 309cde4dd9f65..8ab09eccd9bdb 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/Distribution/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/Distribution/index.tsx @@ -7,10 +7,9 @@ import { Axis, Chart, - ElementClickListener, - GeometryValue, HistogramBarSeries, Position, + ProjectionClickListener, RectAnnotation, ScaleType, Settings, @@ -24,11 +23,11 @@ import d3 from 'd3'; import { isEmpty } from 'lodash'; import React from 'react'; import { ValuesType } from 'utility-types'; -import { APIReturnType } from '../../../../services/rest/createCallApmApi'; import { useTheme } from '../../../../../../observability/public'; import { getDurationFormatter } from '../../../../../common/utils/formatters'; import type { IUrlParams } from '../../../../context/url_params_context/types'; import { FETCH_STATUS } from '../../../../hooks/use_fetcher'; +import { APIReturnType } from '../../../../services/rest/createCallApmApi'; import { unit } from '../../../../style/variables'; import { ChartContainer } from '../../../shared/charts/chart_container'; import { EmptyMessage } from '../../../shared/EmptyMessage'; @@ -145,10 +144,9 @@ export function TransactionDistribution({ }, }; - const onBarClick: ElementClickListener = (elements) => { - const chartPoint = elements[0][0] as GeometryValue; + const onBarClick: ProjectionClickListener = ({ x }) => { const clickedBucket = distribution?.buckets.find((bucket) => { - return bucket.key === chartPoint.x; + return bucket.key === x; }); if (clickedBucket) { onBucketClick(clickedBucket); @@ -194,10 +192,11 @@ export function TransactionDistribution({ {selectedBucket && ( diff --git a/x-pack/plugins/observability/public/hooks/use_chart_theme.tsx b/x-pack/plugins/observability/public/hooks/use_chart_theme.tsx index 3880dcdcde0be..d672525f1a937 100644 --- a/x-pack/plugins/observability/public/hooks/use_chart_theme.tsx +++ b/x-pack/plugins/observability/public/hooks/use_chart_theme.tsx @@ -14,6 +14,12 @@ export function useChartTheme() { return { ...baseChartTheme, + chartMargins: { + left: 10, + right: 10, + top: 10, + bottom: 10, + }, background: { ...baseChartTheme.background, color: 'transparent', From fde0fe52ed518714b07c741a6102c5d33066db24 Mon Sep 17 00:00:00 2001 From: Gidi Meir Morris Date: Mon, 14 Dec 2020 17:30:49 +0000 Subject: [PATCH 29/95] removed unnecessary field (#85792) --- .../sections/action_connector_form/action_type_menu.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_menu.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_menu.tsx index 3264f22bb928f..e1955cc3db786 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_menu.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_menu.tsx @@ -86,7 +86,6 @@ export const ActionTypeMenu = ({ selectMessage: actionTypeModel ? actionTypeModel.selectMessage : '', actionType, name: actionType.name, - typeName: id.replace('.', ''), }; }); From e7500034b0d497611a784464f4426efcdc8447c6 Mon Sep 17 00:00:00 2001 From: Tyler Smalley Date: Mon, 14 Dec 2020 09:33:10 -0800 Subject: [PATCH 30/95] Removes un-used test_utils directory (#85783) Signed-off-by: Tyler Smalley --- src/test_utils/jest.config.js | 24 ------------------------ 1 file changed, 24 deletions(-) delete mode 100644 src/test_utils/jest.config.js diff --git a/src/test_utils/jest.config.js b/src/test_utils/jest.config.js deleted file mode 100644 index b7e77413598c0..0000000000000 --- a/src/test_utils/jest.config.js +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -module.exports = { - preset: '@kbn/test', - rootDir: '../..', - roots: ['/src/test_utils'], -}; From 9986aff82ec253b9551eddda80cccda34f9e3831 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Mon, 14 Dec 2020 18:38:15 +0100 Subject: [PATCH 31/95] [APM] Alerting: Show preview as chart of threshold (#84080) --- .../action_menu/alerting_popover_flyout.tsx | 2 +- .../index.tsx | 0 .../alerting/chart_preview/index.tsx | 112 ++++++++++++++++ .../index.stories.tsx | 0 .../index.tsx | 45 +++++-- .../apm/public/components/alerting/fields.tsx | 2 +- .../apm/public/components/alerting/helper.ts | 17 +++ .../alerting/register_apm_alerts.ts | 8 +- .../index.tsx | 5 +- .../popover_expression}/index.tsx | 0 .../service_alert_trigger.test.tsx | 33 +++++ .../index.stories.tsx | 0 .../index.tsx | 78 +++++++++-- .../index.tsx | 4 +- .../select_anomaly_severity.test.tsx | 0 .../select_anomaly_severity.tsx | 0 .../index.tsx | 59 +++++++-- .../chart_preview/get_transaction_duration.ts | 93 +++++++++++++ .../get_transaction_error_count.ts | 63 +++++++++ .../get_transaction_error_rate.ts | 84 ++++++++++++ .../apm/server/routes/alerts/chart_preview.ts | 72 ++++++++++ .../apm/server/routes/create_apm_api.ts | 12 +- .../basic/tests/alerts/chart_preview.ts | 124 ++++++++++++++++++ .../apm_api_integration/basic/tests/index.ts | 4 + 24 files changed, 774 insertions(+), 43 deletions(-) rename x-pack/plugins/apm/public/components/alerting/{AlertingFlyout => alerting_flyout}/index.tsx (100%) create mode 100644 x-pack/plugins/apm/public/components/alerting/chart_preview/index.tsx rename x-pack/plugins/apm/public/components/alerting/{ErrorCountAlertTrigger => error_count_alert_trigger}/index.stories.tsx (100%) rename x-pack/plugins/apm/public/components/alerting/{ErrorCountAlertTrigger => error_count_alert_trigger}/index.tsx (65%) create mode 100644 x-pack/plugins/apm/public/components/alerting/helper.ts rename x-pack/plugins/apm/public/components/alerting/{ServiceAlertTrigger => service_alert_trigger}/index.tsx (92%) rename x-pack/plugins/apm/public/components/alerting/{ServiceAlertTrigger/PopoverExpression => service_alert_trigger/popover_expression}/index.tsx (100%) create mode 100644 x-pack/plugins/apm/public/components/alerting/service_alert_trigger/service_alert_trigger.test.tsx rename x-pack/plugins/apm/public/components/alerting/{TransactionDurationAlertTrigger => transaction_duration_alert_trigger}/index.stories.tsx (100%) rename x-pack/plugins/apm/public/components/alerting/{TransactionDurationAlertTrigger => transaction_duration_alert_trigger}/index.tsx (70%) rename x-pack/plugins/apm/public/components/alerting/{TransactionDurationAnomalyAlertTrigger => transaction_duration_anomaly_alert_trigger}/index.tsx (96%) rename x-pack/plugins/apm/public/components/alerting/{TransactionDurationAnomalyAlertTrigger => transaction_duration_anomaly_alert_trigger}/select_anomaly_severity.test.tsx (100%) rename x-pack/plugins/apm/public/components/alerting/{TransactionDurationAnomalyAlertTrigger => transaction_duration_anomaly_alert_trigger}/select_anomaly_severity.tsx (100%) rename x-pack/plugins/apm/public/components/alerting/{TransactionErrorRateAlertTrigger => transaction_error_rate_alert_trigger}/index.tsx (71%) create mode 100644 x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_duration.ts create mode 100644 x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_count.ts create mode 100644 x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_rate.ts create mode 100644 x-pack/plugins/apm/server/routes/alerts/chart_preview.ts create mode 100644 x-pack/test/apm_api_integration/basic/tests/alerts/chart_preview.ts diff --git a/x-pack/plugins/apm/public/application/action_menu/alerting_popover_flyout.tsx b/x-pack/plugins/apm/public/application/action_menu/alerting_popover_flyout.tsx index 394b4caea3e7b..395233735a9d5 100644 --- a/x-pack/plugins/apm/public/application/action_menu/alerting_popover_flyout.tsx +++ b/x-pack/plugins/apm/public/application/action_menu/alerting_popover_flyout.tsx @@ -14,7 +14,7 @@ import { i18n } from '@kbn/i18n'; import React, { useState } from 'react'; import { IBasePath } from '../../../../../../src/core/public'; import { AlertType } from '../../../common/alert_types'; -import { AlertingFlyout } from '../../components/alerting/AlertingFlyout'; +import { AlertingFlyout } from '../../components/alerting/alerting_flyout'; const alertLabel = i18n.translate('xpack.apm.home.alertsMenu.alerts', { defaultMessage: 'Alerts', diff --git a/x-pack/plugins/apm/public/components/alerting/AlertingFlyout/index.tsx b/x-pack/plugins/apm/public/components/alerting/alerting_flyout/index.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/alerting/AlertingFlyout/index.tsx rename to x-pack/plugins/apm/public/components/alerting/alerting_flyout/index.tsx diff --git a/x-pack/plugins/apm/public/components/alerting/chart_preview/index.tsx b/x-pack/plugins/apm/public/components/alerting/chart_preview/index.tsx new file mode 100644 index 0000000000000..1ed5748cd757e --- /dev/null +++ b/x-pack/plugins/apm/public/components/alerting/chart_preview/index.tsx @@ -0,0 +1,112 @@ +/* + * 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 { + AnnotationDomainTypes, + Axis, + BarSeries, + Chart, + LineAnnotation, + niceTimeFormatter, + Position, + RectAnnotation, + RectAnnotationDatum, + ScaleType, + Settings, + TickFormatter, +} from '@elastic/charts'; +import { EuiSpacer } from '@elastic/eui'; +import React from 'react'; +import { Coordinate } from '../../../../typings/timeseries'; +import { useTheme } from '../../../hooks/use_theme'; + +interface ChartPreviewProps { + yTickFormat?: TickFormatter; + data?: Coordinate[]; + threshold: number; +} + +export function ChartPreview({ + data = [], + yTickFormat, + threshold, +}: ChartPreviewProps) { + const theme = useTheme(); + const thresholdOpacity = 0.3; + const timestamps = data.map((d) => d.x); + const xMin = Math.min(...timestamps); + const xMax = Math.max(...timestamps); + const xFormatter = niceTimeFormatter([xMin, xMax]); + + // Make the maximum Y value either the actual max or 20% more than the threshold + const values = data.map((d) => d.y ?? 0); + const yMax = Math.max(...values, threshold * 1.2); + + const style = { + fill: theme.eui.euiColorVis9, + line: { + strokeWidth: 2, + stroke: theme.eui.euiColorVis9, + opacity: 1, + }, + opacity: thresholdOpacity, + }; + + const rectDataValues: RectAnnotationDatum[] = [ + { + coordinates: { + x0: null, + x1: null, + y0: threshold, + y1: null, + }, + }, + ]; + + return ( + <> + + + + + + + + + + + ); +} diff --git a/x-pack/plugins/apm/public/components/alerting/ErrorCountAlertTrigger/index.stories.tsx b/x-pack/plugins/apm/public/components/alerting/error_count_alert_trigger/index.stories.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/alerting/ErrorCountAlertTrigger/index.stories.tsx rename to x-pack/plugins/apm/public/components/alerting/error_count_alert_trigger/index.stories.tsx diff --git a/x-pack/plugins/apm/public/components/alerting/ErrorCountAlertTrigger/index.tsx b/x-pack/plugins/apm/public/components/alerting/error_count_alert_trigger/index.tsx similarity index 65% rename from x-pack/plugins/apm/public/components/alerting/ErrorCountAlertTrigger/index.tsx rename to x-pack/plugins/apm/public/components/alerting/error_count_alert_trigger/index.tsx index efa792ff44273..cce973f8587da 100644 --- a/x-pack/plugins/apm/public/components/alerting/ErrorCountAlertTrigger/index.tsx +++ b/x-pack/plugins/apm/public/components/alerting/error_count_alert_trigger/index.tsx @@ -8,12 +8,17 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import { useParams } from 'react-router-dom'; import { ForLastExpression } from '../../../../../triggers_actions_ui/public'; -import { ALERT_TYPES_CONFIG, AlertType } from '../../../../common/alert_types'; +import { AlertType, ALERT_TYPES_CONFIG } from '../../../../common/alert_types'; import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values'; -import { useEnvironmentsFetcher } from '../../../hooks/use_environments_fetcher'; +import { asInteger } from '../../../../common/utils/formatters'; import { useUrlParams } from '../../../context/url_params_context/use_url_params'; -import { EnvironmentField, ServiceField, IsAboveField } from '../fields'; -import { ServiceAlertTrigger } from '../ServiceAlertTrigger'; +import { useEnvironmentsFetcher } from '../../../hooks/use_environments_fetcher'; +import { useFetcher } from '../../../hooks/use_fetcher'; +import { callApmApi } from '../../../services/rest/createCallApmApi'; +import { ChartPreview } from '../chart_preview'; +import { EnvironmentField, IsAboveField, ServiceField } from '../fields'; +import { getAbsoluteTimeRange } from '../helper'; +import { ServiceAlertTrigger } from '../service_alert_trigger'; export interface AlertParams { windowSize: number; @@ -40,6 +45,23 @@ export function ErrorCountAlertTrigger(props: Props) { end, }); + const { threshold, windowSize, windowUnit, environment } = alertParams; + + const { data } = useFetcher(() => { + if (windowSize && windowUnit) { + return callApmApi({ + endpoint: 'GET /api/apm/alerts/chart_preview/transaction_error_count', + params: { + query: { + ...getAbsoluteTimeRange(windowSize, windowUnit), + environment, + serviceName, + }, + }, + }); + } + }, [windowSize, windowUnit, environment, serviceName]); + const defaults = { threshold: 25, windowSize: 1, @@ -64,14 +86,14 @@ export function ErrorCountAlertTrigger(props: Props) { unit={i18n.translate('xpack.apm.errorCountAlertTrigger.errors', { defaultMessage: ' errors', })} - onChange={(value) => setAlertParams('threshold', value)} + onChange={(value) => setAlertParams('threshold', value || 0)} />, - setAlertParams('windowSize', windowSize || '') + onChangeWindowSize={(timeWindowSize) => + setAlertParams('windowSize', timeWindowSize || '') } - onChangeWindowUnit={(windowUnit) => - setAlertParams('windowUnit', windowUnit) + onChangeWindowUnit={(timeWindowUnit) => + setAlertParams('windowUnit', timeWindowUnit) } timeWindowSize={params.windowSize} timeWindowUnit={params.windowUnit} @@ -82,6 +104,10 @@ export function ErrorCountAlertTrigger(props: Props) { />, ]; + const chartPreview = ( + + ); + return ( ); } diff --git a/x-pack/plugins/apm/public/components/alerting/fields.tsx b/x-pack/plugins/apm/public/components/alerting/fields.tsx index 858604d2baa2a..9e814bb1b58c5 100644 --- a/x-pack/plugins/apm/public/components/alerting/fields.tsx +++ b/x-pack/plugins/apm/public/components/alerting/fields.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { EuiSelectOption } from '@elastic/eui'; import { getEnvironmentLabel } from '../../../common/environment_filter_values'; -import { PopoverExpression } from './ServiceAlertTrigger/PopoverExpression'; +import { PopoverExpression } from './service_alert_trigger/popover_expression'; const ALL_OPTION = i18n.translate('xpack.apm.alerting.fields.all_option', { defaultMessage: 'All', diff --git a/x-pack/plugins/apm/public/components/alerting/helper.ts b/x-pack/plugins/apm/public/components/alerting/helper.ts new file mode 100644 index 0000000000000..fd3aebc7495a1 --- /dev/null +++ b/x-pack/plugins/apm/public/components/alerting/helper.ts @@ -0,0 +1,17 @@ +/* + * 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 datemath from '@elastic/datemath'; + +export function getAbsoluteTimeRange(windowSize: number, windowUnit: string) { + const now = new Date().toISOString(); + + return { + start: + datemath.parse(`now-${windowSize}${windowUnit}`)?.toISOString() ?? now, + end: now, + }; +} diff --git a/x-pack/plugins/apm/public/components/alerting/register_apm_alerts.ts b/x-pack/plugins/apm/public/components/alerting/register_apm_alerts.ts index 988e335af5b7c..6dc2cb3163b1f 100644 --- a/x-pack/plugins/apm/public/components/alerting/register_apm_alerts.ts +++ b/x-pack/plugins/apm/public/components/alerting/register_apm_alerts.ts @@ -25,7 +25,7 @@ export function registerApmAlerts( documentationUrl(docLinks) { return `${docLinks.ELASTIC_WEBSITE_URL}guide/en/kibana/${docLinks.DOC_LINK_VERSION}/apm-alerts.html`; }, - alertParamsExpression: lazy(() => import('./ErrorCountAlertTrigger')), + alertParamsExpression: lazy(() => import('./error_count_alert_trigger')), validate: () => ({ errors: [], }), @@ -60,7 +60,7 @@ export function registerApmAlerts( return `${docLinks.ELASTIC_WEBSITE_URL}guide/en/kibana/${docLinks.DOC_LINK_VERSION}/apm-alerts.html`; }, alertParamsExpression: lazy( - () => import('./TransactionDurationAlertTrigger') + () => import('./transaction_duration_alert_trigger') ), validate: () => ({ errors: [], @@ -97,7 +97,7 @@ export function registerApmAlerts( return `${docLinks.ELASTIC_WEBSITE_URL}guide/en/kibana/${docLinks.DOC_LINK_VERSION}/apm-alerts.html`; }, alertParamsExpression: lazy( - () => import('./TransactionErrorRateAlertTrigger') + () => import('./transaction_error_rate_alert_trigger') ), validate: () => ({ errors: [], @@ -134,7 +134,7 @@ export function registerApmAlerts( return `${docLinks.ELASTIC_WEBSITE_URL}guide/en/kibana/${docLinks.DOC_LINK_VERSION}/apm-alerts.html`; }, alertParamsExpression: lazy( - () => import('./TransactionDurationAnomalyAlertTrigger') + () => import('./transaction_duration_anomaly_alert_trigger') ), validate: () => ({ errors: [], diff --git a/x-pack/plugins/apm/public/components/alerting/ServiceAlertTrigger/index.tsx b/x-pack/plugins/apm/public/components/alerting/service_alert_trigger/index.tsx similarity index 92% rename from x-pack/plugins/apm/public/components/alerting/ServiceAlertTrigger/index.tsx rename to x-pack/plugins/apm/public/components/alerting/service_alert_trigger/index.tsx index b4d3e8f3ad241..0a12f79bf61a9 100644 --- a/x-pack/plugins/apm/public/components/alerting/ServiceAlertTrigger/index.tsx +++ b/x-pack/plugins/apm/public/components/alerting/service_alert_trigger/index.tsx @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +import { EuiFlexGrid, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import React, { useEffect } from 'react'; -import { EuiSpacer, EuiFlexGrid, EuiFlexItem } from '@elastic/eui'; import { useParams } from 'react-router-dom'; interface Props { @@ -14,6 +14,7 @@ interface Props { setAlertProperty: (key: string, value: any) => void; defaults: Record; fields: React.ReactNode[]; + chartPreview?: React.ReactNode; } export function ServiceAlertTrigger(props: Props) { @@ -25,6 +26,7 @@ export function ServiceAlertTrigger(props: Props) { setAlertProperty, alertTypeName, defaults, + chartPreview, } = props; const params: Record = { @@ -61,6 +63,7 @@ export function ServiceAlertTrigger(props: Props) { ))} + {chartPreview} ); diff --git a/x-pack/plugins/apm/public/components/alerting/ServiceAlertTrigger/PopoverExpression/index.tsx b/x-pack/plugins/apm/public/components/alerting/service_alert_trigger/popover_expression/index.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/alerting/ServiceAlertTrigger/PopoverExpression/index.tsx rename to x-pack/plugins/apm/public/components/alerting/service_alert_trigger/popover_expression/index.tsx diff --git a/x-pack/plugins/apm/public/components/alerting/service_alert_trigger/service_alert_trigger.test.tsx b/x-pack/plugins/apm/public/components/alerting/service_alert_trigger/service_alert_trigger.test.tsx new file mode 100644 index 0000000000000..72611043bbed3 --- /dev/null +++ b/x-pack/plugins/apm/public/components/alerting/service_alert_trigger/service_alert_trigger.test.tsx @@ -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 { render } from '@testing-library/react'; +import React, { ReactNode } from 'react'; +import { MemoryRouter } from 'react-router-dom'; +import { ServiceAlertTrigger } from './'; + +function Wrapper({ children }: { children?: ReactNode }) { + return {children}; +} + +describe('ServiceAlertTrigger', () => { + it('renders', () => { + expect(() => + render( + {}} + setAlertProperty={() => {}} + />, + { + wrapper: Wrapper, + } + ) + ).not.toThrowError(); + }); +}); diff --git a/x-pack/plugins/apm/public/components/alerting/TransactionDurationAlertTrigger/index.stories.tsx b/x-pack/plugins/apm/public/components/alerting/transaction_duration_alert_trigger/index.stories.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/alerting/TransactionDurationAlertTrigger/index.stories.tsx rename to x-pack/plugins/apm/public/components/alerting/transaction_duration_alert_trigger/index.stories.tsx diff --git a/x-pack/plugins/apm/public/components/alerting/TransactionDurationAlertTrigger/index.tsx b/x-pack/plugins/apm/public/components/alerting/transaction_duration_alert_trigger/index.tsx similarity index 70% rename from x-pack/plugins/apm/public/components/alerting/TransactionDurationAlertTrigger/index.tsx rename to x-pack/plugins/apm/public/components/alerting/transaction_duration_alert_trigger/index.tsx index 3566850aa24c4..f18e407cc58dd 100644 --- a/x-pack/plugins/apm/public/components/alerting/TransactionDurationAlertTrigger/index.tsx +++ b/x-pack/plugins/apm/public/components/alerting/transaction_duration_alert_trigger/index.tsx @@ -4,24 +4,31 @@ * you may not use this file except in compliance with the Elastic License. */ import { EuiSelect } from '@elastic/eui'; -import { useParams } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; import { map } from 'lodash'; import React from 'react'; +import { useParams } from 'react-router-dom'; +import { useFetcher } from '../../../../../observability/public'; import { ForLastExpression } from '../../../../../triggers_actions_ui/public'; import { ALERT_TYPES_CONFIG } from '../../../../common/alert_types'; -import { useEnvironmentsFetcher } from '../../../hooks/use_environments_fetcher'; -import { useUrlParams } from '../../../context/url_params_context/use_url_params'; -import { ServiceAlertTrigger } from '../ServiceAlertTrigger'; -import { PopoverExpression } from '../ServiceAlertTrigger/PopoverExpression'; import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values'; +import { TimeSeries } from '../../../../typings/timeseries'; +import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; +import { useEnvironmentsFetcher } from '../../../hooks/use_environments_fetcher'; +import { callApmApi } from '../../../services/rest/createCallApmApi'; +import { getResponseTimeTickFormatter } from '../../shared/charts/transaction_charts/helper'; +import { useFormatter } from '../../shared/charts/transaction_charts/use_formatter'; +import { ChartPreview } from '../chart_preview'; import { EnvironmentField, + IsAboveField, ServiceField, TransactionTypeField, - IsAboveField, } from '../fields'; -import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; +import { getAbsoluteTimeRange } from '../helper'; +import { ServiceAlertTrigger } from '../service_alert_trigger'; +import { PopoverExpression } from '../service_alert_trigger/popover_expression'; interface AlertParams { windowSize: number; @@ -63,14 +70,58 @@ interface Props { export function TransactionDurationAlertTrigger(props: Props) { const { setAlertParams, alertParams, setAlertProperty } = props; const { urlParams } = useUrlParams(); - const { transactionTypes } = useApmServiceContext(); + const { transactionTypes, transactionType } = useApmServiceContext(); const { serviceName } = useParams<{ serviceName?: string }>(); - const { start, end, transactionType } = urlParams; + const { start, end } = urlParams; const { environmentOptions } = useEnvironmentsFetcher({ serviceName, start, end, }); + const { + aggregationType, + environment, + threshold, + windowSize, + windowUnit, + } = alertParams; + + const { data } = useFetcher(() => { + if (windowSize && windowUnit) { + return callApmApi({ + endpoint: 'GET /api/apm/alerts/chart_preview/transaction_duration', + params: { + query: { + ...getAbsoluteTimeRange(windowSize, windowUnit), + aggregationType, + environment, + serviceName, + transactionType: alertParams.transactionType, + }, + }, + }); + } + }, [ + aggregationType, + environment, + serviceName, + alertParams.transactionType, + windowSize, + windowUnit, + ]); + + const { formatter } = useFormatter([{ data: data ?? [] } as TimeSeries]); + const yTickFormat = getResponseTimeTickFormatter(formatter); + // The threshold from the form is in ms. Convert to µs. + const thresholdMs = threshold * 1000; + + const chartPreview = ( + + ); if (!transactionTypes.length || !serviceName) { return null; @@ -81,9 +132,7 @@ export function TransactionDurationAlertTrigger(props: Props) { aggregationType: 'avg', windowSize: 5, windowUnit: 'm', - - // use the current transaction type or default to the first in the list - transactionType: transactionType || transactionTypes[0], + transactionType, environment: urlParams.environment || ENVIRONMENT_ALL.value, }; @@ -127,7 +176,7 @@ export function TransactionDurationAlertTrigger(props: Props) { unit={i18n.translate('xpack.apm.transactionDurationAlertTrigger.ms', { defaultMessage: 'ms', })} - onChange={(value) => setAlertParams('threshold', value)} + onChange={(value) => setAlertParams('threshold', value || 0)} />, @@ -148,8 +197,9 @@ export function TransactionDurationAlertTrigger(props: Props) { return ( diff --git a/x-pack/plugins/apm/public/components/alerting/TransactionDurationAnomalyAlertTrigger/index.tsx b/x-pack/plugins/apm/public/components/alerting/transaction_duration_anomaly_alert_trigger/index.tsx similarity index 96% rename from x-pack/plugins/apm/public/components/alerting/TransactionDurationAnomalyAlertTrigger/index.tsx rename to x-pack/plugins/apm/public/components/alerting/transaction_duration_anomaly_alert_trigger/index.tsx index ff5939c601375..10c4bbff08396 100644 --- a/x-pack/plugins/apm/public/components/alerting/TransactionDurationAnomalyAlertTrigger/index.tsx +++ b/x-pack/plugins/apm/public/components/alerting/transaction_duration_anomaly_alert_trigger/index.tsx @@ -11,8 +11,8 @@ import { ANOMALY_SEVERITY } from '../../../../../ml/common'; import { ALERT_TYPES_CONFIG } from '../../../../common/alert_types'; import { useEnvironmentsFetcher } from '../../../hooks/use_environments_fetcher'; import { useUrlParams } from '../../../context/url_params_context/use_url_params'; -import { ServiceAlertTrigger } from '../ServiceAlertTrigger'; -import { PopoverExpression } from '../ServiceAlertTrigger/PopoverExpression'; +import { ServiceAlertTrigger } from '../service_alert_trigger'; +import { PopoverExpression } from '../service_alert_trigger/popover_expression'; import { AnomalySeverity, SelectAnomalySeverity, diff --git a/x-pack/plugins/apm/public/components/alerting/TransactionDurationAnomalyAlertTrigger/select_anomaly_severity.test.tsx b/x-pack/plugins/apm/public/components/alerting/transaction_duration_anomaly_alert_trigger/select_anomaly_severity.test.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/alerting/TransactionDurationAnomalyAlertTrigger/select_anomaly_severity.test.tsx rename to x-pack/plugins/apm/public/components/alerting/transaction_duration_anomaly_alert_trigger/select_anomaly_severity.test.tsx diff --git a/x-pack/plugins/apm/public/components/alerting/TransactionDurationAnomalyAlertTrigger/select_anomaly_severity.tsx b/x-pack/plugins/apm/public/components/alerting/transaction_duration_anomaly_alert_trigger/select_anomaly_severity.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/alerting/TransactionDurationAnomalyAlertTrigger/select_anomaly_severity.tsx rename to x-pack/plugins/apm/public/components/alerting/transaction_duration_anomaly_alert_trigger/select_anomaly_severity.tsx diff --git a/x-pack/plugins/apm/public/components/alerting/TransactionErrorRateAlertTrigger/index.tsx b/x-pack/plugins/apm/public/components/alerting/transaction_error_rate_alert_trigger/index.tsx similarity index 71% rename from x-pack/plugins/apm/public/components/alerting/TransactionErrorRateAlertTrigger/index.tsx rename to x-pack/plugins/apm/public/components/alerting/transaction_error_rate_alert_trigger/index.tsx index f723febde389d..9707df9e86335 100644 --- a/x-pack/plugins/apm/public/components/alerting/TransactionErrorRateAlertTrigger/index.tsx +++ b/x-pack/plugins/apm/public/components/alerting/transaction_error_rate_alert_trigger/index.tsx @@ -3,22 +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 { useParams } from 'react-router-dom'; import React from 'react'; +import { useParams } from 'react-router-dom'; import { ForLastExpression } from '../../../../../triggers_actions_ui/public'; -import { ALERT_TYPES_CONFIG, AlertType } from '../../../../common/alert_types'; -import { useEnvironmentsFetcher } from '../../../hooks/use_environments_fetcher'; -import { useUrlParams } from '../../../context/url_params_context/use_url_params'; -import { ServiceAlertTrigger } from '../ServiceAlertTrigger'; - +import { AlertType, ALERT_TYPES_CONFIG } from '../../../../common/alert_types'; import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values'; +import { asPercent } from '../../../../common/utils/formatters'; +import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; +import { useEnvironmentsFetcher } from '../../../hooks/use_environments_fetcher'; +import { useFetcher } from '../../../hooks/use_fetcher'; +import { callApmApi } from '../../../services/rest/createCallApmApi'; +import { ChartPreview } from '../chart_preview'; import { - ServiceField, - TransactionTypeField, EnvironmentField, IsAboveField, + ServiceField, + TransactionTypeField, } from '../fields'; -import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; +import { getAbsoluteTimeRange } from '../helper'; +import { ServiceAlertTrigger } from '../service_alert_trigger'; interface AlertParams { windowSize: number; @@ -47,6 +51,32 @@ export function TransactionErrorRateAlertTrigger(props: Props) { end, }); + const { threshold, windowSize, windowUnit, environment } = alertParams; + + const thresholdAsPercent = (threshold ?? 0) / 100; + + const { data } = useFetcher(() => { + if (windowSize && windowUnit) { + return callApmApi({ + endpoint: 'GET /api/apm/alerts/chart_preview/transaction_error_rate', + params: { + query: { + ...getAbsoluteTimeRange(windowSize, windowUnit), + environment, + serviceName, + transactionType: alertParams.transactionType, + }, + }, + }); + } + }, [ + alertParams.transactionType, + environment, + serviceName, + windowSize, + windowUnit, + ]); + if (serviceName && !transactionTypes.length) { return null; } @@ -79,7 +109,7 @@ export function TransactionErrorRateAlertTrigger(props: Props) { setAlertParams('threshold', value)} + onChange={(value) => setAlertParams('threshold', value || 0)} />, @@ -97,6 +127,14 @@ export function TransactionErrorRateAlertTrigger(props: Props) { />, ]; + const chartPreview = ( + asPercent(d, 1)} + threshold={thresholdAsPercent} + /> + ); + return ( ); } diff --git a/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_duration.ts b/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_duration.ts new file mode 100644 index 0000000000000..37e3a2f201fb9 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_duration.ts @@ -0,0 +1,93 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { MetricsAggregationResponsePart } from '../../../../../../typings/elasticsearch/aggregations'; +import { + PROCESSOR_EVENT, + SERVICE_NAME, + TRANSACTION_DURATION, + TRANSACTION_TYPE, +} from '../../../../common/elasticsearch_fieldnames'; +import { ProcessorEvent } from '../../../../common/processor_event'; +import { rangeFilter } from '../../../../common/utils/range_filter'; +import { AlertParams } from '../../../routes/alerts/chart_preview'; +import { getEnvironmentUiFilterES } from '../../helpers/convert_ui_filters/get_environment_ui_filter_es'; +import { getBucketSize } from '../../helpers/get_bucket_size'; +import { Setup, SetupTimeRange } from '../../helpers/setup_request'; + +export async function getTransactionDurationChartPreview({ + alertParams, + setup, +}: { + alertParams: AlertParams; + setup: Setup & SetupTimeRange; +}) { + const { apmEventClient, start, end } = setup; + const { + aggregationType, + environment, + serviceName, + transactionType, + } = alertParams; + + const query = { + bool: { + filter: [ + { range: rangeFilter(start, end) }, + { term: { [PROCESSOR_EVENT]: ProcessorEvent.transaction } }, + ...(serviceName ? [{ term: { [SERVICE_NAME]: serviceName } }] : []), + ...(transactionType + ? [{ term: { [TRANSACTION_TYPE]: transactionType } }] + : []), + ...getEnvironmentUiFilterES(environment), + ], + }, + }; + + const { intervalString } = getBucketSize({ start, end, numBuckets: 20 }); + + const aggs = { + timeseries: { + date_histogram: { + field: '@timestamp', + fixed_interval: intervalString, + }, + aggs: { + agg: + aggregationType === 'avg' + ? { avg: { field: TRANSACTION_DURATION } } + : { + percentiles: { + field: TRANSACTION_DURATION, + percents: [aggregationType === '95th' ? 95 : 99], + }, + }, + }, + }, + }; + const params = { + apm: { events: [ProcessorEvent.transaction] }, + body: { size: 0, query, aggs }, + }; + const resp = await apmEventClient.search(params); + + if (!resp.aggregations) { + return []; + } + + return resp.aggregations.timeseries.buckets.map((bucket) => { + const percentilesKey = aggregationType === '95th' ? '95.0' : '99.0'; + const x = bucket.key; + const y = + aggregationType === 'avg' + ? (bucket.agg as MetricsAggregationResponsePart).value + : (bucket.agg as { values: Record }).values[ + percentilesKey + ]; + + return { x, y }; + }); +} diff --git a/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_count.ts b/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_count.ts new file mode 100644 index 0000000000000..28316298aeaad --- /dev/null +++ b/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_count.ts @@ -0,0 +1,63 @@ +/* + * 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 { SERVICE_NAME } from '../../../../common/elasticsearch_fieldnames'; +import { ProcessorEvent } from '../../../../common/processor_event'; +import { rangeFilter } from '../../../../common/utils/range_filter'; +import { AlertParams } from '../../../routes/alerts/chart_preview'; +import { getEnvironmentUiFilterES } from '../../helpers/convert_ui_filters/get_environment_ui_filter_es'; +import { getBucketSize } from '../../helpers/get_bucket_size'; +import { Setup, SetupTimeRange } from '../../helpers/setup_request'; + +export async function getTransactionErrorCountChartPreview({ + setup, + alertParams, +}: { + setup: Setup & SetupTimeRange; + alertParams: AlertParams; +}) { + const { apmEventClient, start, end } = setup; + const { serviceName, environment } = alertParams; + + const query = { + bool: { + filter: [ + { range: rangeFilter(start, end) }, + ...(serviceName ? [{ term: { [SERVICE_NAME]: serviceName } }] : []), + ...getEnvironmentUiFilterES(environment), + ], + }, + }; + + const { intervalString } = getBucketSize({ start, end, numBuckets: 20 }); + + const aggs = { + timeseries: { + date_histogram: { + field: '@timestamp', + fixed_interval: intervalString, + }, + }, + }; + + const params = { + apm: { events: [ProcessorEvent.error] }, + body: { size: 0, query, aggs }, + }; + + const resp = await apmEventClient.search(params); + + if (!resp.aggregations) { + return []; + } + + return resp.aggregations.timeseries.buckets.map((bucket) => { + return { + x: bucket.key, + y: bucket.doc_count, + }; + }); +} diff --git a/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_rate.ts b/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_rate.ts new file mode 100644 index 0000000000000..fae43ef148cfa --- /dev/null +++ b/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_rate.ts @@ -0,0 +1,84 @@ +/* + * 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 { + PROCESSOR_EVENT, + SERVICE_NAME, + TRANSACTION_TYPE, +} from '../../../../common/elasticsearch_fieldnames'; +import { ProcessorEvent } from '../../../../common/processor_event'; +import { rangeFilter } from '../../../../common/utils/range_filter'; +import { AlertParams } from '../../../routes/alerts/chart_preview'; +import { getEnvironmentUiFilterES } from '../../helpers/convert_ui_filters/get_environment_ui_filter_es'; +import { getBucketSize } from '../../helpers/get_bucket_size'; +import { Setup, SetupTimeRange } from '../../helpers/setup_request'; +import { + calculateTransactionErrorPercentage, + getOutcomeAggregation, +} from '../../helpers/transaction_error_rate'; + +export async function getTransactionErrorRateChartPreview({ + setup, + alertParams, +}: { + setup: Setup & SetupTimeRange; + alertParams: AlertParams; +}) { + const { apmEventClient, start, end } = setup; + const { serviceName, environment, transactionType } = alertParams; + + const query = { + bool: { + filter: [ + { range: rangeFilter(start, end) }, + { term: { [PROCESSOR_EVENT]: ProcessorEvent.transaction } }, + ...(serviceName ? [{ term: { [SERVICE_NAME]: serviceName } }] : []), + ...(transactionType + ? [{ term: { [TRANSACTION_TYPE]: transactionType } }] + : []), + ...getEnvironmentUiFilterES(environment), + ], + }, + }; + + const outcomes = getOutcomeAggregation({ + searchAggregatedTransactions: false, + }); + + const { intervalString } = getBucketSize({ start, end, numBuckets: 20 }); + + const aggs = { + outcomes, + timeseries: { + date_histogram: { + field: '@timestamp', + fixed_interval: intervalString, + }, + aggs: { outcomes }, + }, + }; + + const params = { + apm: { events: [ProcessorEvent.transaction] }, + body: { size: 0, query, aggs }, + }; + + const resp = await apmEventClient.search(params); + + if (!resp.aggregations) { + return []; + } + + return resp.aggregations.timeseries.buckets.map((bucket) => { + const errorPercentage = calculateTransactionErrorPercentage( + bucket.outcomes + ); + return { + x: bucket.key, + y: errorPercentage, + }; + }); +} diff --git a/x-pack/plugins/apm/server/routes/alerts/chart_preview.ts b/x-pack/plugins/apm/server/routes/alerts/chart_preview.ts new file mode 100644 index 0000000000000..dc8bf45de091b --- /dev/null +++ b/x-pack/plugins/apm/server/routes/alerts/chart_preview.ts @@ -0,0 +1,72 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import * as t from 'io-ts'; +import { getTransactionDurationChartPreview } from '../../lib/alerts/chart_preview/get_transaction_duration'; +import { getTransactionErrorCountChartPreview } from '../../lib/alerts/chart_preview/get_transaction_error_count'; +import { getTransactionErrorRateChartPreview } from '../../lib/alerts/chart_preview/get_transaction_error_rate'; +import { setupRequest } from '../../lib/helpers/setup_request'; +import { createRoute } from '../create_route'; +import { rangeRt } from '../default_api_types'; + +const alertParamsRt = t.intersection([ + t.partial({ + aggregationType: t.union([ + t.literal('avg'), + t.literal('95th'), + t.literal('99th'), + ]), + serviceName: t.string, + environment: t.string, + transactionType: t.string, + }), + rangeRt, +]); + +export type AlertParams = t.TypeOf; + +export const transactionErrorRateChartPreview = createRoute({ + endpoint: 'GET /api/apm/alerts/chart_preview/transaction_error_rate', + params: t.type({ query: alertParamsRt }), + options: { tags: ['access:apm'] }, + handler: async ({ context, request }) => { + const setup = await setupRequest(context, request); + const { _debug, ...alertParams } = context.params.query; + + return getTransactionErrorRateChartPreview({ + setup, + alertParams, + }); + }, +}); + +export const transactionErrorCountChartPreview = createRoute({ + endpoint: 'GET /api/apm/alerts/chart_preview/transaction_error_count', + params: t.type({ query: alertParamsRt }), + options: { tags: ['access:apm'] }, + handler: async ({ context, request }) => { + const setup = await setupRequest(context, request); + const { _debug, ...alertParams } = context.params.query; + return getTransactionErrorCountChartPreview({ + setup, + alertParams, + }); + }, +}); + +export const transactionDurationChartPreview = createRoute({ + endpoint: 'GET /api/apm/alerts/chart_preview/transaction_duration', + params: t.type({ query: alertParamsRt }), + options: { tags: ['access:apm'] }, + handler: async ({ context, request }) => { + const setup = await setupRequest(context, request); + const { _debug, ...alertParams } = context.params.query; + + return getTransactionDurationChartPreview({ + alertParams, + setup, + }); + }, +}); 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 d34e67083b037..b09175a6841f8 100644 --- a/x-pack/plugins/apm/server/routes/create_apm_api.ts +++ b/x-pack/plugins/apm/server/routes/create_apm_api.ts @@ -101,6 +101,11 @@ import { rumVisitorsBreakdownRoute, rumWebCoreVitals, } from './rum_client'; +import { + transactionErrorRateChartPreview, + transactionErrorCountChartPreview, + transactionDurationChartPreview, +} from './alerts/chart_preview'; const createApmApi = () => { const api = createApi() @@ -204,7 +209,12 @@ const createApmApi = () => { .add(rumJSErrors) .add(rumUrlSearch) .add(rumLongTaskMetrics) - .add(rumHasDataRoute); + .add(rumHasDataRoute) + + // Alerting + .add(transactionErrorCountChartPreview) + .add(transactionDurationChartPreview) + .add(transactionErrorRateChartPreview); return api; }; diff --git a/x-pack/test/apm_api_integration/basic/tests/alerts/chart_preview.ts b/x-pack/test/apm_api_integration/basic/tests/alerts/chart_preview.ts new file mode 100644 index 0000000000000..3119de47a8635 --- /dev/null +++ b/x-pack/test/apm_api_integration/basic/tests/alerts/chart_preview.ts @@ -0,0 +1,124 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { format } from 'url'; +import archives from '../../../common/archives_metadata'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const supertest = getService('supertest'); + const archiveName = 'apm_8.0.0'; + const { end } = archives[archiveName]; + const start = new Date(Date.parse(end) - 600000).toISOString(); + + describe('Alerting chart previews', () => { + describe('GET /api/apm/alerts/chart_preview/transaction_error_rate', () => { + const url = format({ + pathname: '/api/apm/alerts/chart_preview/transaction_error_rate', + query: { + start, + end, + transactionType: 'request', + serviceName: 'opbeans-java', + }, + }); + + describe('when data is not loaded', () => { + it('handles the empty state', async () => { + const response = await supertest.get(url); + + expect(response.status).to.be(200); + expect(response.body).to.eql([]); + }); + }); + + describe('when data is loaded', () => { + before(() => esArchiver.load(archiveName)); + after(() => esArchiver.unload(archiveName)); + + it('returns the correct data', async () => { + const response = await supertest.get(url); + + expect(response.status).to.be(200); + expect( + response.body.some((item: { x: number; y: number | null }) => item.x && item.y) + ).to.equal(true); + }); + }); + }); + + describe('GET /api/apm/alerts/chart_preview/transaction_error_count', () => { + const url = format({ + pathname: '/api/apm/alerts/chart_preview/transaction_error_count', + query: { + start, + end, + serviceName: 'opbeans-java', + }, + }); + + describe('when data is not loaded', () => { + it('handles the empty state', async () => { + const response = await supertest.get(url); + + expect(response.status).to.be(200); + expect(response.body).to.eql([]); + }); + }); + + describe('when data is loaded', () => { + before(() => esArchiver.load(archiveName)); + after(() => esArchiver.unload(archiveName)); + + it('returns the correct data', async () => { + const response = await supertest.get(url); + + expect(response.status).to.be(200); + expect( + response.body.some((item: { x: number; y: number | null }) => item.x && item.y) + ).to.equal(true); + }); + }); + }); + + describe('GET /api/apm/alerts/chart_preview/transaction_duration', () => { + const url = format({ + pathname: '/api/apm/alerts/chart_preview/transaction_duration', + query: { + start, + end, + serviceName: 'opbeans-java', + transactionType: 'request', + }, + }); + + describe('when data is not loaded', () => { + it('handles the empty state', async () => { + const response = await supertest.get(url); + + expect(response.status).to.be(200); + expect(response.body).to.eql([]); + }); + }); + + describe('when data is loaded', () => { + before(() => esArchiver.load(archiveName)); + after(() => esArchiver.unload(archiveName)); + + it('returns the correct data', async () => { + const response = await supertest.get(url); + + expect(response.status).to.be(200); + expect( + response.body.some((item: { x: number; y: number | null }) => item.x && item.y) + ).to.equal(true); + }); + }); + }); + }); +} 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 3e625688e2459..c0156d92439f0 100644 --- a/x-pack/test/apm_api_integration/basic/tests/index.ts +++ b/x-pack/test/apm_api_integration/basic/tests/index.ts @@ -11,6 +11,10 @@ export default function apmApiIntegrationTests({ loadTestFile }: FtrProviderCont loadTestFile(require.resolve('./feature_controls')); + describe('Alerts', function () { + loadTestFile(require.resolve('./alerts/chart_preview')); + }); + describe('Service Maps', function () { loadTestFile(require.resolve('./service_maps/service_maps')); }); From 5304e88c3e6b98711418a06ff670bd9efcd41f2a Mon Sep 17 00:00:00 2001 From: Marco Liberati Date: Mon, 14 Dec 2020 18:43:25 +0100 Subject: [PATCH 32/95] [Lens] Fix chart twitching on flyout open (#85430) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../plugins/lens/public/pie_visualization/visualization.scss | 3 +++ x-pack/plugins/lens/public/xy_visualization/expression.scss | 3 +++ 2 files changed, 6 insertions(+) diff --git a/x-pack/plugins/lens/public/pie_visualization/visualization.scss b/x-pack/plugins/lens/public/pie_visualization/visualization.scss index d9ff75d849708..a8890208596b6 100644 --- a/x-pack/plugins/lens/public/pie_visualization/visualization.scss +++ b/x-pack/plugins/lens/public/pie_visualization/visualization.scss @@ -1,4 +1,7 @@ .lnsPieExpression__container { height: 100%; width: 100%; + // the FocusTrap is adding extra divs which are making the visualization redraw twice + // with a visible glitch. This make the chart library resilient to this extra reflow + overflow-x: hidden; } diff --git a/x-pack/plugins/lens/public/xy_visualization/expression.scss b/x-pack/plugins/lens/public/xy_visualization/expression.scss index 579f66f99b9fb..68f5e9863d2bb 100644 --- a/x-pack/plugins/lens/public/xy_visualization/expression.scss +++ b/x-pack/plugins/lens/public/xy_visualization/expression.scss @@ -1,6 +1,9 @@ .lnsXyExpression__container { height: 100%; width: 100%; + // the FocusTrap is adding extra divs which are making the visualization redraw twice + // with a visible glitch. This make the chart library resilient to this extra reflow + overflow-x: hidden; } .lnsChart__empty { From 12c40f7906c7ac0debff36920f0ba9f85db2030b Mon Sep 17 00:00:00 2001 From: Andrea Del Rio Date: Mon, 14 Dec 2020 13:03:16 -0500 Subject: [PATCH 33/95] Remove feature_directory directory and link (#84464) --- .../components/feature_directory.js | 164 ------------------ .../public/application/components/home_app.js | 4 - .../overview_page_footer.test.tsx.snap | 42 ++--- .../overview_page_footer.test.tsx | 2 +- .../overview_page_footer.tsx | 28 +-- .../translations/translations/ja-JP.json | 6 - .../translations/translations/zh-CN.json | 6 - 7 files changed, 17 insertions(+), 235 deletions(-) delete mode 100644 src/plugins/home/public/application/components/feature_directory.js diff --git a/src/plugins/home/public/application/components/feature_directory.js b/src/plugins/home/public/application/components/feature_directory.js deleted file mode 100644 index 36ececcdfd8df..0000000000000 --- a/src/plugins/home/public/application/components/feature_directory.js +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import React from 'react'; -import PropTypes from 'prop-types'; -import { Synopsis } from './synopsis'; -import { - EuiTabs, - EuiTab, - EuiFlexItem, - EuiFlexGrid, - EuiPage, - EuiPageBody, - EuiTitle, - EuiSpacer, -} from '@elastic/eui'; - -import { FeatureCatalogueCategory } from '../../services'; - -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { createAppNavigationHandler } from './app_navigation_handler'; - -const ALL_TAB_ID = 'all'; -const OTHERS_TAB_ID = 'others'; - -const isOtherCategory = (directory) => { - return ( - directory.category !== FeatureCatalogueCategory.DATA && - directory.category !== FeatureCatalogueCategory.ADMIN - ); -}; - -export class FeatureDirectory extends React.Component { - constructor(props) { - super(props); - - this.tabs = [ - { - id: ALL_TAB_ID, - name: i18n.translate('home.directory.tabs.allTitle', { defaultMessage: 'All' }), - }, - { - id: FeatureCatalogueCategory.DATA, - name: i18n.translate('home.directory.tabs.dataTitle', { - defaultMessage: 'Data Exploration & Visualization', - }), - }, - { - id: FeatureCatalogueCategory.ADMIN, - name: i18n.translate('home.directory.tabs.administrativeTitle', { - defaultMessage: 'Administrative', - }), - }, - ]; - if (props.directories.some(isOtherCategory)) { - this.tabs.push({ - id: OTHERS_TAB_ID, - name: i18n.translate('home.directory.tabs.otherTitle', { defaultMessage: 'Other' }), - }); - } - - this.state = { - selectedTabId: ALL_TAB_ID, - }; - } - - onSelectedTabChanged = (id) => { - this.setState({ - selectedTabId: id, - }); - }; - - renderTabs = () => { - return this.tabs.map((tab, index) => ( - this.onSelectedTabChanged(tab.id)} - isSelected={tab.id === this.state.selectedTabId} - key={index} - > - {tab.name} - - )); - }; - - renderDirectories = () => { - return this.props.directories - .filter((directory) => { - if (this.state.selectedTabId === ALL_TAB_ID) { - return true; - } - if (this.state.selectedTabId === OTHERS_TAB_ID) { - return isOtherCategory(directory); - } - return this.state.selectedTabId === directory.category; - }) - .map((directory) => { - return ( - - - - ); - }); - }; - - render() { - return ( - - - -

- -

-
- - {this.renderTabs()} - - {this.renderDirectories()} -
-
- ); - } -} - -FeatureDirectory.propTypes = { - addBasePath: PropTypes.func.isRequired, - directories: PropTypes.arrayOf( - PropTypes.shape({ - id: PropTypes.string.isRequired, - title: PropTypes.string.isRequired, - description: PropTypes.string.isRequired, - icon: PropTypes.string.isRequired, - path: PropTypes.string.isRequired, - showOnHomePage: PropTypes.bool.isRequired, - category: PropTypes.string.isRequired, - order: PropTypes.number, - }) - ), -}; diff --git a/src/plugins/home/public/application/components/home_app.js b/src/plugins/home/public/application/components/home_app.js index 734100fe584ab..2ea96ad904b21 100644 --- a/src/plugins/home/public/application/components/home_app.js +++ b/src/plugins/home/public/application/components/home_app.js @@ -21,7 +21,6 @@ import React from 'react'; import { I18nProvider } from '@kbn/i18n/react'; import PropTypes from 'prop-types'; import { Home } from './home'; -import { FeatureDirectory } from './feature_directory'; import { TutorialDirectory } from './tutorial_directory'; import { Tutorial } from './tutorial/tutorial'; import { HashRouter as Router, Switch, Route } from 'react-router-dom'; @@ -78,9 +77,6 @@ export function HomeApp({ directories, solutions }) { - - - - -
-
- - - - - + +
diff --git a/src/plugins/kibana_react/public/overview_page/overview_page_footer/overview_page_footer.test.tsx b/src/plugins/kibana_react/public/overview_page/overview_page_footer/overview_page_footer.test.tsx index f90ecdda93242..568677ee389fa 100644 --- a/src/plugins/kibana_react/public/overview_page/overview_page_footer/overview_page_footer.test.tsx +++ b/src/plugins/kibana_react/public/overview_page/overview_page_footer/overview_page_footer.test.tsx @@ -28,7 +28,7 @@ jest.mock('../../app_links', () => ({ jest.mock('../../context', () => ({ useKibana: jest.fn().mockReturnValue({ services: { - application: { capabilities: { advancedSettings: { show: true } } }, + application: { capabilities: { advancedSettings: { show: true, save: true } } }, notifications: { toast: { addSuccess: jest.fn() } }, }, }), diff --git a/src/plugins/kibana_react/public/overview_page/overview_page_footer/overview_page_footer.tsx b/src/plugins/kibana_react/public/overview_page/overview_page_footer/overview_page_footer.tsx index 113992099aee1..576046092d512 100644 --- a/src/plugins/kibana_react/public/overview_page/overview_page_footer/overview_page_footer.tsx +++ b/src/plugins/kibana_react/public/overview_page/overview_page_footer/overview_page_footer.tsx @@ -32,7 +32,7 @@ interface Props { path: string; /** Callback function to invoke when the user wants to set their default route to the current page */ onSetDefaultRoute?: (event: MouseEvent) => void; - /** Callback function to invoke when the user wants to change their default route button is changed */ + /** Callback function to invoke when the user wants to change their default route button is changed */ onChangeDefaultRoute?: (event: MouseEvent) => void; } @@ -51,9 +51,9 @@ export const OverviewPageFooter: FC = ({ } = useKibana(); const { show, save } = application.capabilities.advancedSettings; - const isAdvancedSettingsEnabled = show && save; + if (!show && !save) return <>; - const defaultRoutebutton = defaultRoute.includes(path) ? ( + const defaultRouteButton = defaultRoute.includes(path) ? ( = ({
-
{isAdvancedSettingsEnabled ? defaultRoutebutton : null}
-
- - -
- - - - - -
+
{defaultRouteButton}
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 4e43749c1753d..f128b8cebc9b0 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -1676,11 +1676,6 @@ "home.dataManagementDisclaimerPrivacyLink": "プライバシーポリシーをご覧ください。", "home.dataManagementEnableCollection": " 収集を開始するには、 ", "home.dataManagementEnableCollectionLink": "ここで使用状況データを有効にします。", - "home.directory.directoryTitle": "ディレクトリ", - "home.directory.tabs.administrativeTitle": "管理", - "home.directory.tabs.allTitle": "すべて", - "home.directory.tabs.dataTitle": "データの閲覧と可視化", - "home.directory.tabs.otherTitle": "その他", "home.exploreButtonLabel": "独りで閲覧", "home.exploreYourDataDescription": "すべてのステップを終えたら、データ閲覧準備の完了です。", "home.header.title": "ホーム", @@ -2784,7 +2779,6 @@ "kibana-react.kbnOverviewPageHeader.devToolsButtonLabel": "開発ツール", "kibana-react.kbnOverviewPageHeader.stackManagementButtonLabel": "管理", "kibana-react.mountPointPortal.errorMessage": "ポータルコンテンツのレンダリングエラー", - "kibana-react.pageFooter.appDirectoryButtonLabel": "アプリディレクトリを表示", "kibana-react.pageFooter.changeDefaultRouteSuccessToast": "ランディングページが更新されました", "kibana-react.pageFooter.changeHomeRouteLink": "ログイン時に別のページを表示", "kibana-react.pageFooter.makeDefaultRouteLink": "これをランディングページにする", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 0682acd0152e5..148287fe4810d 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -1677,11 +1677,6 @@ "home.dataManagementDisclaimerPrivacyLink": "隐私声明。", "home.dataManagementEnableCollection": " 要启动收集, ", "home.dataManagementEnableCollectionLink": "请在此处启用使用情况数据。", - "home.directory.directoryTitle": "目录", - "home.directory.tabs.administrativeTitle": "管理", - "home.directory.tabs.allTitle": "全部", - "home.directory.tabs.dataTitle": "数据浏览和可视化", - "home.directory.tabs.otherTitle": "其他", "home.exploreButtonLabel": "自己浏览", "home.exploreYourDataDescription": "完成所有步骤后,您便可以随时浏览自己的数据。", "home.header.title": "主页", @@ -2785,7 +2780,6 @@ "kibana-react.kbnOverviewPageHeader.devToolsButtonLabel": "开发工具", "kibana-react.kbnOverviewPageHeader.stackManagementButtonLabel": "管理", "kibana-react.mountPointPortal.errorMessage": "呈现门户内容时出错", - "kibana-react.pageFooter.appDirectoryButtonLabel": "查看应用目录", "kibana-react.pageFooter.changeDefaultRouteSuccessToast": "登陆页面已更新", "kibana-react.pageFooter.changeHomeRouteLink": "登录时显示不同页面", "kibana-react.pageFooter.makeDefaultRouteLink": "将此设为我的登陆页面", From d909a9617fa26d967c71f7574f74b2689b786bbb Mon Sep 17 00:00:00 2001 From: Tyler Smalley Date: Mon, 14 Dec 2020 10:32:57 -0800 Subject: [PATCH 34/95] Revert "[APM] Alerting: Show preview as chart of threshold (#84080)" This reverts commit 9986aff82ec253b9551eddda80cccda34f9e3831. --- .../action_menu/alerting_popover_flyout.tsx | 2 +- .../index.tsx | 0 .../index.stories.tsx | 0 .../index.tsx | 45 ++----- .../PopoverExpression}/index.tsx | 0 .../index.tsx | 5 +- .../index.stories.tsx | 0 .../index.tsx | 78 ++--------- .../index.tsx | 4 +- .../select_anomaly_severity.test.tsx | 0 .../select_anomaly_severity.tsx | 0 .../index.tsx | 59 ++------- .../alerting/chart_preview/index.tsx | 112 ---------------- .../apm/public/components/alerting/fields.tsx | 2 +- .../apm/public/components/alerting/helper.ts | 17 --- .../alerting/register_apm_alerts.ts | 8 +- .../service_alert_trigger.test.tsx | 33 ----- .../chart_preview/get_transaction_duration.ts | 93 ------------- .../get_transaction_error_count.ts | 63 --------- .../get_transaction_error_rate.ts | 84 ------------ .../apm/server/routes/alerts/chart_preview.ts | 72 ---------- .../apm/server/routes/create_apm_api.ts | 12 +- .../basic/tests/alerts/chart_preview.ts | 124 ------------------ .../apm_api_integration/basic/tests/index.ts | 4 - 24 files changed, 43 insertions(+), 774 deletions(-) rename x-pack/plugins/apm/public/components/alerting/{alerting_flyout => AlertingFlyout}/index.tsx (100%) rename x-pack/plugins/apm/public/components/alerting/{error_count_alert_trigger => ErrorCountAlertTrigger}/index.stories.tsx (100%) rename x-pack/plugins/apm/public/components/alerting/{error_count_alert_trigger => ErrorCountAlertTrigger}/index.tsx (65%) rename x-pack/plugins/apm/public/components/alerting/{service_alert_trigger/popover_expression => ServiceAlertTrigger/PopoverExpression}/index.tsx (100%) rename x-pack/plugins/apm/public/components/alerting/{service_alert_trigger => ServiceAlertTrigger}/index.tsx (92%) rename x-pack/plugins/apm/public/components/alerting/{transaction_duration_alert_trigger => TransactionDurationAlertTrigger}/index.stories.tsx (100%) rename x-pack/plugins/apm/public/components/alerting/{transaction_duration_alert_trigger => TransactionDurationAlertTrigger}/index.tsx (70%) rename x-pack/plugins/apm/public/components/alerting/{transaction_duration_anomaly_alert_trigger => TransactionDurationAnomalyAlertTrigger}/index.tsx (96%) rename x-pack/plugins/apm/public/components/alerting/{transaction_duration_anomaly_alert_trigger => TransactionDurationAnomalyAlertTrigger}/select_anomaly_severity.test.tsx (100%) rename x-pack/plugins/apm/public/components/alerting/{transaction_duration_anomaly_alert_trigger => TransactionDurationAnomalyAlertTrigger}/select_anomaly_severity.tsx (100%) rename x-pack/plugins/apm/public/components/alerting/{transaction_error_rate_alert_trigger => TransactionErrorRateAlertTrigger}/index.tsx (71%) delete mode 100644 x-pack/plugins/apm/public/components/alerting/chart_preview/index.tsx delete mode 100644 x-pack/plugins/apm/public/components/alerting/helper.ts delete mode 100644 x-pack/plugins/apm/public/components/alerting/service_alert_trigger/service_alert_trigger.test.tsx delete mode 100644 x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_duration.ts delete mode 100644 x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_count.ts delete mode 100644 x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_rate.ts delete mode 100644 x-pack/plugins/apm/server/routes/alerts/chart_preview.ts delete mode 100644 x-pack/test/apm_api_integration/basic/tests/alerts/chart_preview.ts diff --git a/x-pack/plugins/apm/public/application/action_menu/alerting_popover_flyout.tsx b/x-pack/plugins/apm/public/application/action_menu/alerting_popover_flyout.tsx index 395233735a9d5..394b4caea3e7b 100644 --- a/x-pack/plugins/apm/public/application/action_menu/alerting_popover_flyout.tsx +++ b/x-pack/plugins/apm/public/application/action_menu/alerting_popover_flyout.tsx @@ -14,7 +14,7 @@ import { i18n } from '@kbn/i18n'; import React, { useState } from 'react'; import { IBasePath } from '../../../../../../src/core/public'; import { AlertType } from '../../../common/alert_types'; -import { AlertingFlyout } from '../../components/alerting/alerting_flyout'; +import { AlertingFlyout } from '../../components/alerting/AlertingFlyout'; const alertLabel = i18n.translate('xpack.apm.home.alertsMenu.alerts', { defaultMessage: 'Alerts', diff --git a/x-pack/plugins/apm/public/components/alerting/alerting_flyout/index.tsx b/x-pack/plugins/apm/public/components/alerting/AlertingFlyout/index.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/alerting/alerting_flyout/index.tsx rename to x-pack/plugins/apm/public/components/alerting/AlertingFlyout/index.tsx diff --git a/x-pack/plugins/apm/public/components/alerting/error_count_alert_trigger/index.stories.tsx b/x-pack/plugins/apm/public/components/alerting/ErrorCountAlertTrigger/index.stories.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/alerting/error_count_alert_trigger/index.stories.tsx rename to x-pack/plugins/apm/public/components/alerting/ErrorCountAlertTrigger/index.stories.tsx diff --git a/x-pack/plugins/apm/public/components/alerting/error_count_alert_trigger/index.tsx b/x-pack/plugins/apm/public/components/alerting/ErrorCountAlertTrigger/index.tsx similarity index 65% rename from x-pack/plugins/apm/public/components/alerting/error_count_alert_trigger/index.tsx rename to x-pack/plugins/apm/public/components/alerting/ErrorCountAlertTrigger/index.tsx index cce973f8587da..efa792ff44273 100644 --- a/x-pack/plugins/apm/public/components/alerting/error_count_alert_trigger/index.tsx +++ b/x-pack/plugins/apm/public/components/alerting/ErrorCountAlertTrigger/index.tsx @@ -8,17 +8,12 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import { useParams } from 'react-router-dom'; import { ForLastExpression } from '../../../../../triggers_actions_ui/public'; -import { AlertType, ALERT_TYPES_CONFIG } from '../../../../common/alert_types'; +import { ALERT_TYPES_CONFIG, AlertType } from '../../../../common/alert_types'; import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values'; -import { asInteger } from '../../../../common/utils/formatters'; -import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { useEnvironmentsFetcher } from '../../../hooks/use_environments_fetcher'; -import { useFetcher } from '../../../hooks/use_fetcher'; -import { callApmApi } from '../../../services/rest/createCallApmApi'; -import { ChartPreview } from '../chart_preview'; -import { EnvironmentField, IsAboveField, ServiceField } from '../fields'; -import { getAbsoluteTimeRange } from '../helper'; -import { ServiceAlertTrigger } from '../service_alert_trigger'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; +import { EnvironmentField, ServiceField, IsAboveField } from '../fields'; +import { ServiceAlertTrigger } from '../ServiceAlertTrigger'; export interface AlertParams { windowSize: number; @@ -45,23 +40,6 @@ export function ErrorCountAlertTrigger(props: Props) { end, }); - const { threshold, windowSize, windowUnit, environment } = alertParams; - - const { data } = useFetcher(() => { - if (windowSize && windowUnit) { - return callApmApi({ - endpoint: 'GET /api/apm/alerts/chart_preview/transaction_error_count', - params: { - query: { - ...getAbsoluteTimeRange(windowSize, windowUnit), - environment, - serviceName, - }, - }, - }); - } - }, [windowSize, windowUnit, environment, serviceName]); - const defaults = { threshold: 25, windowSize: 1, @@ -86,14 +64,14 @@ export function ErrorCountAlertTrigger(props: Props) { unit={i18n.translate('xpack.apm.errorCountAlertTrigger.errors', { defaultMessage: ' errors', })} - onChange={(value) => setAlertParams('threshold', value || 0)} + onChange={(value) => setAlertParams('threshold', value)} />, - setAlertParams('windowSize', timeWindowSize || '') + onChangeWindowSize={(windowSize) => + setAlertParams('windowSize', windowSize || '') } - onChangeWindowUnit={(timeWindowUnit) => - setAlertParams('windowUnit', timeWindowUnit) + onChangeWindowUnit={(windowUnit) => + setAlertParams('windowUnit', windowUnit) } timeWindowSize={params.windowSize} timeWindowUnit={params.windowUnit} @@ -104,10 +82,6 @@ export function ErrorCountAlertTrigger(props: Props) { />, ]; - const chartPreview = ( - - ); - return ( ); } diff --git a/x-pack/plugins/apm/public/components/alerting/service_alert_trigger/popover_expression/index.tsx b/x-pack/plugins/apm/public/components/alerting/ServiceAlertTrigger/PopoverExpression/index.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/alerting/service_alert_trigger/popover_expression/index.tsx rename to x-pack/plugins/apm/public/components/alerting/ServiceAlertTrigger/PopoverExpression/index.tsx diff --git a/x-pack/plugins/apm/public/components/alerting/service_alert_trigger/index.tsx b/x-pack/plugins/apm/public/components/alerting/ServiceAlertTrigger/index.tsx similarity index 92% rename from x-pack/plugins/apm/public/components/alerting/service_alert_trigger/index.tsx rename to x-pack/plugins/apm/public/components/alerting/ServiceAlertTrigger/index.tsx index 0a12f79bf61a9..b4d3e8f3ad241 100644 --- a/x-pack/plugins/apm/public/components/alerting/service_alert_trigger/index.tsx +++ b/x-pack/plugins/apm/public/components/alerting/ServiceAlertTrigger/index.tsx @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiFlexGrid, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import React, { useEffect } from 'react'; +import { EuiSpacer, EuiFlexGrid, EuiFlexItem } from '@elastic/eui'; import { useParams } from 'react-router-dom'; interface Props { @@ -14,7 +14,6 @@ interface Props { setAlertProperty: (key: string, value: any) => void; defaults: Record; fields: React.ReactNode[]; - chartPreview?: React.ReactNode; } export function ServiceAlertTrigger(props: Props) { @@ -26,7 +25,6 @@ export function ServiceAlertTrigger(props: Props) { setAlertProperty, alertTypeName, defaults, - chartPreview, } = props; const params: Record = { @@ -63,7 +61,6 @@ export function ServiceAlertTrigger(props: Props) { ))} - {chartPreview} ); diff --git a/x-pack/plugins/apm/public/components/alerting/transaction_duration_alert_trigger/index.stories.tsx b/x-pack/plugins/apm/public/components/alerting/TransactionDurationAlertTrigger/index.stories.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/alerting/transaction_duration_alert_trigger/index.stories.tsx rename to x-pack/plugins/apm/public/components/alerting/TransactionDurationAlertTrigger/index.stories.tsx diff --git a/x-pack/plugins/apm/public/components/alerting/transaction_duration_alert_trigger/index.tsx b/x-pack/plugins/apm/public/components/alerting/TransactionDurationAlertTrigger/index.tsx similarity index 70% rename from x-pack/plugins/apm/public/components/alerting/transaction_duration_alert_trigger/index.tsx rename to x-pack/plugins/apm/public/components/alerting/TransactionDurationAlertTrigger/index.tsx index f18e407cc58dd..3566850aa24c4 100644 --- a/x-pack/plugins/apm/public/components/alerting/transaction_duration_alert_trigger/index.tsx +++ b/x-pack/plugins/apm/public/components/alerting/TransactionDurationAlertTrigger/index.tsx @@ -4,31 +4,24 @@ * you may not use this file except in compliance with the Elastic License. */ import { EuiSelect } from '@elastic/eui'; +import { useParams } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; import { map } from 'lodash'; import React from 'react'; -import { useParams } from 'react-router-dom'; -import { useFetcher } from '../../../../../observability/public'; import { ForLastExpression } from '../../../../../triggers_actions_ui/public'; import { ALERT_TYPES_CONFIG } from '../../../../common/alert_types'; -import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values'; -import { TimeSeries } from '../../../../typings/timeseries'; -import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; -import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { useEnvironmentsFetcher } from '../../../hooks/use_environments_fetcher'; -import { callApmApi } from '../../../services/rest/createCallApmApi'; -import { getResponseTimeTickFormatter } from '../../shared/charts/transaction_charts/helper'; -import { useFormatter } from '../../shared/charts/transaction_charts/use_formatter'; -import { ChartPreview } from '../chart_preview'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; +import { ServiceAlertTrigger } from '../ServiceAlertTrigger'; +import { PopoverExpression } from '../ServiceAlertTrigger/PopoverExpression'; +import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values'; import { EnvironmentField, - IsAboveField, ServiceField, TransactionTypeField, + IsAboveField, } from '../fields'; -import { getAbsoluteTimeRange } from '../helper'; -import { ServiceAlertTrigger } from '../service_alert_trigger'; -import { PopoverExpression } from '../service_alert_trigger/popover_expression'; +import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; interface AlertParams { windowSize: number; @@ -70,58 +63,14 @@ interface Props { export function TransactionDurationAlertTrigger(props: Props) { const { setAlertParams, alertParams, setAlertProperty } = props; const { urlParams } = useUrlParams(); - const { transactionTypes, transactionType } = useApmServiceContext(); + const { transactionTypes } = useApmServiceContext(); const { serviceName } = useParams<{ serviceName?: string }>(); - const { start, end } = urlParams; + const { start, end, transactionType } = urlParams; const { environmentOptions } = useEnvironmentsFetcher({ serviceName, start, end, }); - const { - aggregationType, - environment, - threshold, - windowSize, - windowUnit, - } = alertParams; - - const { data } = useFetcher(() => { - if (windowSize && windowUnit) { - return callApmApi({ - endpoint: 'GET /api/apm/alerts/chart_preview/transaction_duration', - params: { - query: { - ...getAbsoluteTimeRange(windowSize, windowUnit), - aggregationType, - environment, - serviceName, - transactionType: alertParams.transactionType, - }, - }, - }); - } - }, [ - aggregationType, - environment, - serviceName, - alertParams.transactionType, - windowSize, - windowUnit, - ]); - - const { formatter } = useFormatter([{ data: data ?? [] } as TimeSeries]); - const yTickFormat = getResponseTimeTickFormatter(formatter); - // The threshold from the form is in ms. Convert to µs. - const thresholdMs = threshold * 1000; - - const chartPreview = ( - - ); if (!transactionTypes.length || !serviceName) { return null; @@ -132,7 +81,9 @@ export function TransactionDurationAlertTrigger(props: Props) { aggregationType: 'avg', windowSize: 5, windowUnit: 'm', - transactionType, + + // use the current transaction type or default to the first in the list + transactionType: transactionType || transactionTypes[0], environment: urlParams.environment || ENVIRONMENT_ALL.value, }; @@ -176,7 +127,7 @@ export function TransactionDurationAlertTrigger(props: Props) { unit={i18n.translate('xpack.apm.transactionDurationAlertTrigger.ms', { defaultMessage: 'ms', })} - onChange={(value) => setAlertParams('threshold', value || 0)} + onChange={(value) => setAlertParams('threshold', value)} />, @@ -197,9 +148,8 @@ export function TransactionDurationAlertTrigger(props: Props) { return ( diff --git a/x-pack/plugins/apm/public/components/alerting/transaction_duration_anomaly_alert_trigger/index.tsx b/x-pack/plugins/apm/public/components/alerting/TransactionDurationAnomalyAlertTrigger/index.tsx similarity index 96% rename from x-pack/plugins/apm/public/components/alerting/transaction_duration_anomaly_alert_trigger/index.tsx rename to x-pack/plugins/apm/public/components/alerting/TransactionDurationAnomalyAlertTrigger/index.tsx index 10c4bbff08396..ff5939c601375 100644 --- a/x-pack/plugins/apm/public/components/alerting/transaction_duration_anomaly_alert_trigger/index.tsx +++ b/x-pack/plugins/apm/public/components/alerting/TransactionDurationAnomalyAlertTrigger/index.tsx @@ -11,8 +11,8 @@ import { ANOMALY_SEVERITY } from '../../../../../ml/common'; import { ALERT_TYPES_CONFIG } from '../../../../common/alert_types'; import { useEnvironmentsFetcher } from '../../../hooks/use_environments_fetcher'; import { useUrlParams } from '../../../context/url_params_context/use_url_params'; -import { ServiceAlertTrigger } from '../service_alert_trigger'; -import { PopoverExpression } from '../service_alert_trigger/popover_expression'; +import { ServiceAlertTrigger } from '../ServiceAlertTrigger'; +import { PopoverExpression } from '../ServiceAlertTrigger/PopoverExpression'; import { AnomalySeverity, SelectAnomalySeverity, diff --git a/x-pack/plugins/apm/public/components/alerting/transaction_duration_anomaly_alert_trigger/select_anomaly_severity.test.tsx b/x-pack/plugins/apm/public/components/alerting/TransactionDurationAnomalyAlertTrigger/select_anomaly_severity.test.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/alerting/transaction_duration_anomaly_alert_trigger/select_anomaly_severity.test.tsx rename to x-pack/plugins/apm/public/components/alerting/TransactionDurationAnomalyAlertTrigger/select_anomaly_severity.test.tsx diff --git a/x-pack/plugins/apm/public/components/alerting/transaction_duration_anomaly_alert_trigger/select_anomaly_severity.tsx b/x-pack/plugins/apm/public/components/alerting/TransactionDurationAnomalyAlertTrigger/select_anomaly_severity.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/alerting/transaction_duration_anomaly_alert_trigger/select_anomaly_severity.tsx rename to x-pack/plugins/apm/public/components/alerting/TransactionDurationAnomalyAlertTrigger/select_anomaly_severity.tsx diff --git a/x-pack/plugins/apm/public/components/alerting/transaction_error_rate_alert_trigger/index.tsx b/x-pack/plugins/apm/public/components/alerting/TransactionErrorRateAlertTrigger/index.tsx similarity index 71% rename from x-pack/plugins/apm/public/components/alerting/transaction_error_rate_alert_trigger/index.tsx rename to x-pack/plugins/apm/public/components/alerting/TransactionErrorRateAlertTrigger/index.tsx index 9707df9e86335..f723febde389d 100644 --- a/x-pack/plugins/apm/public/components/alerting/transaction_error_rate_alert_trigger/index.tsx +++ b/x-pack/plugins/apm/public/components/alerting/TransactionErrorRateAlertTrigger/index.tsx @@ -3,26 +3,22 @@ * 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 { useParams } from 'react-router-dom'; +import React from 'react'; import { ForLastExpression } from '../../../../../triggers_actions_ui/public'; -import { AlertType, ALERT_TYPES_CONFIG } from '../../../../common/alert_types'; -import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values'; -import { asPercent } from '../../../../common/utils/formatters'; -import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; -import { useUrlParams } from '../../../context/url_params_context/use_url_params'; +import { ALERT_TYPES_CONFIG, AlertType } from '../../../../common/alert_types'; import { useEnvironmentsFetcher } from '../../../hooks/use_environments_fetcher'; -import { useFetcher } from '../../../hooks/use_fetcher'; -import { callApmApi } from '../../../services/rest/createCallApmApi'; -import { ChartPreview } from '../chart_preview'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; +import { ServiceAlertTrigger } from '../ServiceAlertTrigger'; + +import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values'; import { - EnvironmentField, - IsAboveField, ServiceField, TransactionTypeField, + EnvironmentField, + IsAboveField, } from '../fields'; -import { getAbsoluteTimeRange } from '../helper'; -import { ServiceAlertTrigger } from '../service_alert_trigger'; +import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; interface AlertParams { windowSize: number; @@ -51,32 +47,6 @@ export function TransactionErrorRateAlertTrigger(props: Props) { end, }); - const { threshold, windowSize, windowUnit, environment } = alertParams; - - const thresholdAsPercent = (threshold ?? 0) / 100; - - const { data } = useFetcher(() => { - if (windowSize && windowUnit) { - return callApmApi({ - endpoint: 'GET /api/apm/alerts/chart_preview/transaction_error_rate', - params: { - query: { - ...getAbsoluteTimeRange(windowSize, windowUnit), - environment, - serviceName, - transactionType: alertParams.transactionType, - }, - }, - }); - } - }, [ - alertParams.transactionType, - environment, - serviceName, - windowSize, - windowUnit, - ]); - if (serviceName && !transactionTypes.length) { return null; } @@ -109,7 +79,7 @@ export function TransactionErrorRateAlertTrigger(props: Props) { setAlertParams('threshold', value || 0)} + onChange={(value) => setAlertParams('threshold', value)} />, @@ -127,14 +97,6 @@ export function TransactionErrorRateAlertTrigger(props: Props) { />, ]; - const chartPreview = ( - asPercent(d, 1)} - threshold={thresholdAsPercent} - /> - ); - return ( ); } diff --git a/x-pack/plugins/apm/public/components/alerting/chart_preview/index.tsx b/x-pack/plugins/apm/public/components/alerting/chart_preview/index.tsx deleted file mode 100644 index 1ed5748cd757e..0000000000000 --- a/x-pack/plugins/apm/public/components/alerting/chart_preview/index.tsx +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { - AnnotationDomainTypes, - Axis, - BarSeries, - Chart, - LineAnnotation, - niceTimeFormatter, - Position, - RectAnnotation, - RectAnnotationDatum, - ScaleType, - Settings, - TickFormatter, -} from '@elastic/charts'; -import { EuiSpacer } from '@elastic/eui'; -import React from 'react'; -import { Coordinate } from '../../../../typings/timeseries'; -import { useTheme } from '../../../hooks/use_theme'; - -interface ChartPreviewProps { - yTickFormat?: TickFormatter; - data?: Coordinate[]; - threshold: number; -} - -export function ChartPreview({ - data = [], - yTickFormat, - threshold, -}: ChartPreviewProps) { - const theme = useTheme(); - const thresholdOpacity = 0.3; - const timestamps = data.map((d) => d.x); - const xMin = Math.min(...timestamps); - const xMax = Math.max(...timestamps); - const xFormatter = niceTimeFormatter([xMin, xMax]); - - // Make the maximum Y value either the actual max or 20% more than the threshold - const values = data.map((d) => d.y ?? 0); - const yMax = Math.max(...values, threshold * 1.2); - - const style = { - fill: theme.eui.euiColorVis9, - line: { - strokeWidth: 2, - stroke: theme.eui.euiColorVis9, - opacity: 1, - }, - opacity: thresholdOpacity, - }; - - const rectDataValues: RectAnnotationDatum[] = [ - { - coordinates: { - x0: null, - x1: null, - y0: threshold, - y1: null, - }, - }, - ]; - - return ( - <> - - - - - - - - - - - ); -} diff --git a/x-pack/plugins/apm/public/components/alerting/fields.tsx b/x-pack/plugins/apm/public/components/alerting/fields.tsx index 9e814bb1b58c5..858604d2baa2a 100644 --- a/x-pack/plugins/apm/public/components/alerting/fields.tsx +++ b/x-pack/plugins/apm/public/components/alerting/fields.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { EuiSelectOption } from '@elastic/eui'; import { getEnvironmentLabel } from '../../../common/environment_filter_values'; -import { PopoverExpression } from './service_alert_trigger/popover_expression'; +import { PopoverExpression } from './ServiceAlertTrigger/PopoverExpression'; const ALL_OPTION = i18n.translate('xpack.apm.alerting.fields.all_option', { defaultMessage: 'All', diff --git a/x-pack/plugins/apm/public/components/alerting/helper.ts b/x-pack/plugins/apm/public/components/alerting/helper.ts deleted file mode 100644 index fd3aebc7495a1..0000000000000 --- a/x-pack/plugins/apm/public/components/alerting/helper.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 datemath from '@elastic/datemath'; - -export function getAbsoluteTimeRange(windowSize: number, windowUnit: string) { - const now = new Date().toISOString(); - - return { - start: - datemath.parse(`now-${windowSize}${windowUnit}`)?.toISOString() ?? now, - end: now, - }; -} diff --git a/x-pack/plugins/apm/public/components/alerting/register_apm_alerts.ts b/x-pack/plugins/apm/public/components/alerting/register_apm_alerts.ts index 6dc2cb3163b1f..988e335af5b7c 100644 --- a/x-pack/plugins/apm/public/components/alerting/register_apm_alerts.ts +++ b/x-pack/plugins/apm/public/components/alerting/register_apm_alerts.ts @@ -25,7 +25,7 @@ export function registerApmAlerts( documentationUrl(docLinks) { return `${docLinks.ELASTIC_WEBSITE_URL}guide/en/kibana/${docLinks.DOC_LINK_VERSION}/apm-alerts.html`; }, - alertParamsExpression: lazy(() => import('./error_count_alert_trigger')), + alertParamsExpression: lazy(() => import('./ErrorCountAlertTrigger')), validate: () => ({ errors: [], }), @@ -60,7 +60,7 @@ export function registerApmAlerts( return `${docLinks.ELASTIC_WEBSITE_URL}guide/en/kibana/${docLinks.DOC_LINK_VERSION}/apm-alerts.html`; }, alertParamsExpression: lazy( - () => import('./transaction_duration_alert_trigger') + () => import('./TransactionDurationAlertTrigger') ), validate: () => ({ errors: [], @@ -97,7 +97,7 @@ export function registerApmAlerts( return `${docLinks.ELASTIC_WEBSITE_URL}guide/en/kibana/${docLinks.DOC_LINK_VERSION}/apm-alerts.html`; }, alertParamsExpression: lazy( - () => import('./transaction_error_rate_alert_trigger') + () => import('./TransactionErrorRateAlertTrigger') ), validate: () => ({ errors: [], @@ -134,7 +134,7 @@ export function registerApmAlerts( return `${docLinks.ELASTIC_WEBSITE_URL}guide/en/kibana/${docLinks.DOC_LINK_VERSION}/apm-alerts.html`; }, alertParamsExpression: lazy( - () => import('./transaction_duration_anomaly_alert_trigger') + () => import('./TransactionDurationAnomalyAlertTrigger') ), validate: () => ({ errors: [], diff --git a/x-pack/plugins/apm/public/components/alerting/service_alert_trigger/service_alert_trigger.test.tsx b/x-pack/plugins/apm/public/components/alerting/service_alert_trigger/service_alert_trigger.test.tsx deleted file mode 100644 index 72611043bbed3..0000000000000 --- a/x-pack/plugins/apm/public/components/alerting/service_alert_trigger/service_alert_trigger.test.tsx +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { render } from '@testing-library/react'; -import React, { ReactNode } from 'react'; -import { MemoryRouter } from 'react-router-dom'; -import { ServiceAlertTrigger } from './'; - -function Wrapper({ children }: { children?: ReactNode }) { - return {children}; -} - -describe('ServiceAlertTrigger', () => { - it('renders', () => { - expect(() => - render( - {}} - setAlertProperty={() => {}} - />, - { - wrapper: Wrapper, - } - ) - ).not.toThrowError(); - }); -}); diff --git a/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_duration.ts b/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_duration.ts deleted file mode 100644 index 37e3a2f201fb9..0000000000000 --- a/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_duration.ts +++ /dev/null @@ -1,93 +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 { MetricsAggregationResponsePart } from '../../../../../../typings/elasticsearch/aggregations'; -import { - PROCESSOR_EVENT, - SERVICE_NAME, - TRANSACTION_DURATION, - TRANSACTION_TYPE, -} from '../../../../common/elasticsearch_fieldnames'; -import { ProcessorEvent } from '../../../../common/processor_event'; -import { rangeFilter } from '../../../../common/utils/range_filter'; -import { AlertParams } from '../../../routes/alerts/chart_preview'; -import { getEnvironmentUiFilterES } from '../../helpers/convert_ui_filters/get_environment_ui_filter_es'; -import { getBucketSize } from '../../helpers/get_bucket_size'; -import { Setup, SetupTimeRange } from '../../helpers/setup_request'; - -export async function getTransactionDurationChartPreview({ - alertParams, - setup, -}: { - alertParams: AlertParams; - setup: Setup & SetupTimeRange; -}) { - const { apmEventClient, start, end } = setup; - const { - aggregationType, - environment, - serviceName, - transactionType, - } = alertParams; - - const query = { - bool: { - filter: [ - { range: rangeFilter(start, end) }, - { term: { [PROCESSOR_EVENT]: ProcessorEvent.transaction } }, - ...(serviceName ? [{ term: { [SERVICE_NAME]: serviceName } }] : []), - ...(transactionType - ? [{ term: { [TRANSACTION_TYPE]: transactionType } }] - : []), - ...getEnvironmentUiFilterES(environment), - ], - }, - }; - - const { intervalString } = getBucketSize({ start, end, numBuckets: 20 }); - - const aggs = { - timeseries: { - date_histogram: { - field: '@timestamp', - fixed_interval: intervalString, - }, - aggs: { - agg: - aggregationType === 'avg' - ? { avg: { field: TRANSACTION_DURATION } } - : { - percentiles: { - field: TRANSACTION_DURATION, - percents: [aggregationType === '95th' ? 95 : 99], - }, - }, - }, - }, - }; - const params = { - apm: { events: [ProcessorEvent.transaction] }, - body: { size: 0, query, aggs }, - }; - const resp = await apmEventClient.search(params); - - if (!resp.aggregations) { - return []; - } - - return resp.aggregations.timeseries.buckets.map((bucket) => { - const percentilesKey = aggregationType === '95th' ? '95.0' : '99.0'; - const x = bucket.key; - const y = - aggregationType === 'avg' - ? (bucket.agg as MetricsAggregationResponsePart).value - : (bucket.agg as { values: Record }).values[ - percentilesKey - ]; - - return { x, y }; - }); -} diff --git a/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_count.ts b/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_count.ts deleted file mode 100644 index 28316298aeaad..0000000000000 --- a/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_count.ts +++ /dev/null @@ -1,63 +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 { SERVICE_NAME } from '../../../../common/elasticsearch_fieldnames'; -import { ProcessorEvent } from '../../../../common/processor_event'; -import { rangeFilter } from '../../../../common/utils/range_filter'; -import { AlertParams } from '../../../routes/alerts/chart_preview'; -import { getEnvironmentUiFilterES } from '../../helpers/convert_ui_filters/get_environment_ui_filter_es'; -import { getBucketSize } from '../../helpers/get_bucket_size'; -import { Setup, SetupTimeRange } from '../../helpers/setup_request'; - -export async function getTransactionErrorCountChartPreview({ - setup, - alertParams, -}: { - setup: Setup & SetupTimeRange; - alertParams: AlertParams; -}) { - const { apmEventClient, start, end } = setup; - const { serviceName, environment } = alertParams; - - const query = { - bool: { - filter: [ - { range: rangeFilter(start, end) }, - ...(serviceName ? [{ term: { [SERVICE_NAME]: serviceName } }] : []), - ...getEnvironmentUiFilterES(environment), - ], - }, - }; - - const { intervalString } = getBucketSize({ start, end, numBuckets: 20 }); - - const aggs = { - timeseries: { - date_histogram: { - field: '@timestamp', - fixed_interval: intervalString, - }, - }, - }; - - const params = { - apm: { events: [ProcessorEvent.error] }, - body: { size: 0, query, aggs }, - }; - - const resp = await apmEventClient.search(params); - - if (!resp.aggregations) { - return []; - } - - return resp.aggregations.timeseries.buckets.map((bucket) => { - return { - x: bucket.key, - y: bucket.doc_count, - }; - }); -} diff --git a/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_rate.ts b/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_rate.ts deleted file mode 100644 index fae43ef148cfa..0000000000000 --- a/x-pack/plugins/apm/server/lib/alerts/chart_preview/get_transaction_error_rate.ts +++ /dev/null @@ -1,84 +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 { - PROCESSOR_EVENT, - SERVICE_NAME, - TRANSACTION_TYPE, -} from '../../../../common/elasticsearch_fieldnames'; -import { ProcessorEvent } from '../../../../common/processor_event'; -import { rangeFilter } from '../../../../common/utils/range_filter'; -import { AlertParams } from '../../../routes/alerts/chart_preview'; -import { getEnvironmentUiFilterES } from '../../helpers/convert_ui_filters/get_environment_ui_filter_es'; -import { getBucketSize } from '../../helpers/get_bucket_size'; -import { Setup, SetupTimeRange } from '../../helpers/setup_request'; -import { - calculateTransactionErrorPercentage, - getOutcomeAggregation, -} from '../../helpers/transaction_error_rate'; - -export async function getTransactionErrorRateChartPreview({ - setup, - alertParams, -}: { - setup: Setup & SetupTimeRange; - alertParams: AlertParams; -}) { - const { apmEventClient, start, end } = setup; - const { serviceName, environment, transactionType } = alertParams; - - const query = { - bool: { - filter: [ - { range: rangeFilter(start, end) }, - { term: { [PROCESSOR_EVENT]: ProcessorEvent.transaction } }, - ...(serviceName ? [{ term: { [SERVICE_NAME]: serviceName } }] : []), - ...(transactionType - ? [{ term: { [TRANSACTION_TYPE]: transactionType } }] - : []), - ...getEnvironmentUiFilterES(environment), - ], - }, - }; - - const outcomes = getOutcomeAggregation({ - searchAggregatedTransactions: false, - }); - - const { intervalString } = getBucketSize({ start, end, numBuckets: 20 }); - - const aggs = { - outcomes, - timeseries: { - date_histogram: { - field: '@timestamp', - fixed_interval: intervalString, - }, - aggs: { outcomes }, - }, - }; - - const params = { - apm: { events: [ProcessorEvent.transaction] }, - body: { size: 0, query, aggs }, - }; - - const resp = await apmEventClient.search(params); - - if (!resp.aggregations) { - return []; - } - - return resp.aggregations.timeseries.buckets.map((bucket) => { - const errorPercentage = calculateTransactionErrorPercentage( - bucket.outcomes - ); - return { - x: bucket.key, - y: errorPercentage, - }; - }); -} diff --git a/x-pack/plugins/apm/server/routes/alerts/chart_preview.ts b/x-pack/plugins/apm/server/routes/alerts/chart_preview.ts deleted file mode 100644 index dc8bf45de091b..0000000000000 --- a/x-pack/plugins/apm/server/routes/alerts/chart_preview.ts +++ /dev/null @@ -1,72 +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 * as t from 'io-ts'; -import { getTransactionDurationChartPreview } from '../../lib/alerts/chart_preview/get_transaction_duration'; -import { getTransactionErrorCountChartPreview } from '../../lib/alerts/chart_preview/get_transaction_error_count'; -import { getTransactionErrorRateChartPreview } from '../../lib/alerts/chart_preview/get_transaction_error_rate'; -import { setupRequest } from '../../lib/helpers/setup_request'; -import { createRoute } from '../create_route'; -import { rangeRt } from '../default_api_types'; - -const alertParamsRt = t.intersection([ - t.partial({ - aggregationType: t.union([ - t.literal('avg'), - t.literal('95th'), - t.literal('99th'), - ]), - serviceName: t.string, - environment: t.string, - transactionType: t.string, - }), - rangeRt, -]); - -export type AlertParams = t.TypeOf; - -export const transactionErrorRateChartPreview = createRoute({ - endpoint: 'GET /api/apm/alerts/chart_preview/transaction_error_rate', - params: t.type({ query: alertParamsRt }), - options: { tags: ['access:apm'] }, - handler: async ({ context, request }) => { - const setup = await setupRequest(context, request); - const { _debug, ...alertParams } = context.params.query; - - return getTransactionErrorRateChartPreview({ - setup, - alertParams, - }); - }, -}); - -export const transactionErrorCountChartPreview = createRoute({ - endpoint: 'GET /api/apm/alerts/chart_preview/transaction_error_count', - params: t.type({ query: alertParamsRt }), - options: { tags: ['access:apm'] }, - handler: async ({ context, request }) => { - const setup = await setupRequest(context, request); - const { _debug, ...alertParams } = context.params.query; - return getTransactionErrorCountChartPreview({ - setup, - alertParams, - }); - }, -}); - -export const transactionDurationChartPreview = createRoute({ - endpoint: 'GET /api/apm/alerts/chart_preview/transaction_duration', - params: t.type({ query: alertParamsRt }), - options: { tags: ['access:apm'] }, - handler: async ({ context, request }) => { - const setup = await setupRequest(context, request); - const { _debug, ...alertParams } = context.params.query; - - return getTransactionDurationChartPreview({ - alertParams, - setup, - }); - }, -}); 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 b09175a6841f8..d34e67083b037 100644 --- a/x-pack/plugins/apm/server/routes/create_apm_api.ts +++ b/x-pack/plugins/apm/server/routes/create_apm_api.ts @@ -101,11 +101,6 @@ import { rumVisitorsBreakdownRoute, rumWebCoreVitals, } from './rum_client'; -import { - transactionErrorRateChartPreview, - transactionErrorCountChartPreview, - transactionDurationChartPreview, -} from './alerts/chart_preview'; const createApmApi = () => { const api = createApi() @@ -209,12 +204,7 @@ const createApmApi = () => { .add(rumJSErrors) .add(rumUrlSearch) .add(rumLongTaskMetrics) - .add(rumHasDataRoute) - - // Alerting - .add(transactionErrorCountChartPreview) - .add(transactionDurationChartPreview) - .add(transactionErrorRateChartPreview); + .add(rumHasDataRoute); return api; }; diff --git a/x-pack/test/apm_api_integration/basic/tests/alerts/chart_preview.ts b/x-pack/test/apm_api_integration/basic/tests/alerts/chart_preview.ts deleted file mode 100644 index 3119de47a8635..0000000000000 --- a/x-pack/test/apm_api_integration/basic/tests/alerts/chart_preview.ts +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { format } from 'url'; -import archives from '../../../common/archives_metadata'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; - -export default function ApiTest({ getService }: FtrProviderContext) { - const esArchiver = getService('esArchiver'); - const supertest = getService('supertest'); - const archiveName = 'apm_8.0.0'; - const { end } = archives[archiveName]; - const start = new Date(Date.parse(end) - 600000).toISOString(); - - describe('Alerting chart previews', () => { - describe('GET /api/apm/alerts/chart_preview/transaction_error_rate', () => { - const url = format({ - pathname: '/api/apm/alerts/chart_preview/transaction_error_rate', - query: { - start, - end, - transactionType: 'request', - serviceName: 'opbeans-java', - }, - }); - - describe('when data is not loaded', () => { - it('handles the empty state', async () => { - const response = await supertest.get(url); - - expect(response.status).to.be(200); - expect(response.body).to.eql([]); - }); - }); - - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); - - it('returns the correct data', async () => { - const response = await supertest.get(url); - - expect(response.status).to.be(200); - expect( - response.body.some((item: { x: number; y: number | null }) => item.x && item.y) - ).to.equal(true); - }); - }); - }); - - describe('GET /api/apm/alerts/chart_preview/transaction_error_count', () => { - const url = format({ - pathname: '/api/apm/alerts/chart_preview/transaction_error_count', - query: { - start, - end, - serviceName: 'opbeans-java', - }, - }); - - describe('when data is not loaded', () => { - it('handles the empty state', async () => { - const response = await supertest.get(url); - - expect(response.status).to.be(200); - expect(response.body).to.eql([]); - }); - }); - - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); - - it('returns the correct data', async () => { - const response = await supertest.get(url); - - expect(response.status).to.be(200); - expect( - response.body.some((item: { x: number; y: number | null }) => item.x && item.y) - ).to.equal(true); - }); - }); - }); - - describe('GET /api/apm/alerts/chart_preview/transaction_duration', () => { - const url = format({ - pathname: '/api/apm/alerts/chart_preview/transaction_duration', - query: { - start, - end, - serviceName: 'opbeans-java', - transactionType: 'request', - }, - }); - - describe('when data is not loaded', () => { - it('handles the empty state', async () => { - const response = await supertest.get(url); - - expect(response.status).to.be(200); - expect(response.body).to.eql([]); - }); - }); - - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); - - it('returns the correct data', async () => { - const response = await supertest.get(url); - - expect(response.status).to.be(200); - expect( - response.body.some((item: { x: number; y: number | null }) => item.x && item.y) - ).to.equal(true); - }); - }); - }); - }); -} 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 c0156d92439f0..3e625688e2459 100644 --- a/x-pack/test/apm_api_integration/basic/tests/index.ts +++ b/x-pack/test/apm_api_integration/basic/tests/index.ts @@ -11,10 +11,6 @@ export default function apmApiIntegrationTests({ loadTestFile }: FtrProviderCont loadTestFile(require.resolve('./feature_controls')); - describe('Alerts', function () { - loadTestFile(require.resolve('./alerts/chart_preview')); - }); - describe('Service Maps', function () { loadTestFile(require.resolve('./service_maps/service_maps')); }); From 06993c469b7baf23e81d268ecdcb4ee13131fc36 Mon Sep 17 00:00:00 2001 From: Kevin Logan <56395104+kevinlog@users.noreply.github.com> Date: Mon, 14 Dec 2020 13:45:56 -0500 Subject: [PATCH 35/95] [Fleet] Installation of hidden field (#85703) Co-authored-by: nnamdifrankie Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../plugins/fleet/common/types/models/epm.ts | 3 ++- .../epm/elasticsearch/template/install.ts | 1 + .../elasticsearch/template/template.test.ts | 25 +++++++++++++++++++ .../epm/elasticsearch/template/template.ts | 16 +++++++++--- 4 files changed, 41 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/fleet/common/types/models/epm.ts b/x-pack/plugins/fleet/common/types/models/epm.ts index 96868fa8cfc3b..f518c606d6959 100644 --- a/x-pack/plugins/fleet/common/types/models/epm.ts +++ b/x-pack/plugins/fleet/common/types/models/epm.ts @@ -207,6 +207,7 @@ export type ElasticsearchAssetTypeToParts = Record< export interface RegistryDataStream { type: string; + hidden?: boolean; dataset: string; title: string; release: string; @@ -319,7 +320,7 @@ export interface IndexTemplate { mappings: any; aliases: object; }; - data_stream: object; + data_stream: { hidden?: boolean }; composed_of: string[]; _meta: object; } diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.ts index 199026da30c11..944f742e54546 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.ts @@ -314,6 +314,7 @@ export async function installTemplate({ pipelineName, packageName, composedOfTemplates, + hidden: dataStream.hidden, }); // TODO: Check return values for errors diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.test.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.test.ts index cc1aa79c7491c..bdff7e0fb3bc6 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.test.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.test.ts @@ -60,6 +60,31 @@ test('adds empty composed_of correctly', () => { expect(template.composed_of).toStrictEqual(composedOfTemplates); }); +test('adds hidden field correctly', () => { + const templateWithHiddenName = 'logs-nginx-access-abcd'; + + const templateWithHidden = getTemplate({ + type: 'logs', + templateName: templateWithHiddenName, + packageName: 'nginx', + mappings: { properties: {} }, + composedOfTemplates: [], + hidden: true, + }); + expect(templateWithHidden.data_stream.hidden).toEqual(true); + + const templateWithoutHiddenName = 'logs-nginx-access-efgh'; + + const templateWithoutHidden = getTemplate({ + type: 'logs', + templateName: templateWithoutHiddenName, + packageName: 'nginx', + mappings: { properties: {} }, + composedOfTemplates: [], + }); + expect(templateWithoutHidden.data_stream.hidden).toEqual(undefined); +}); + test('tests loading base.yml', () => { const ymlPath = path.join(__dirname, '../../fields/tests/base.yml'); const fieldsYML = readFileSync(ymlPath, 'utf-8'); diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts index 8d33180d6262d..d80d54d098db7 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts @@ -45,6 +45,7 @@ export function getTemplate({ pipelineName, packageName, composedOfTemplates, + hidden, }: { type: string; templateName: string; @@ -52,8 +53,16 @@ export function getTemplate({ pipelineName?: string | undefined; packageName: string; composedOfTemplates: string[]; + hidden?: boolean; }): IndexTemplate { - const template = getBaseTemplate(type, templateName, mappings, packageName, composedOfTemplates); + const template = getBaseTemplate( + type, + templateName, + mappings, + packageName, + composedOfTemplates, + hidden + ); if (pipelineName) { template.template.settings.index.default_pipeline = pipelineName; } @@ -253,7 +262,8 @@ function getBaseTemplate( templateName: string, mappings: IndexTemplateMappings, packageName: string, - composedOfTemplates: string[] + composedOfTemplates: string[], + hidden?: boolean ): IndexTemplate { // Meta information to identify Ingest Manager's managed templates and indices const _meta = { @@ -324,7 +334,7 @@ function getBaseTemplate( // To be filled with the aliases that we need aliases: {}, }, - data_stream: {}, + data_stream: { hidden }, composed_of: composedOfTemplates, _meta, }; From 5f6ed3dc3ccd034f4e067e4a3d181f1f0c82a5d1 Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Mon, 14 Dec 2020 12:51:14 -0600 Subject: [PATCH 36/95] skip custom detection rules. #83772 --- .../cypress/integration/alerts_detection_rules_custom.spec.ts | 2 +- 1 file changed, 1 insertion(+), 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 b3c82a8d9d6f0..3ce507c791f0a 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 @@ -217,7 +217,7 @@ describe('Custom detection rules creation', () => { }); }); -describe('Custom detection rules deletion and edition', () => { +describe.skip('Custom detection rules deletion and edition', () => { beforeEach(() => { esArchiverLoad('custom_rules'); loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); From ea4e2224a93bef2ee31f203a88baa71e5594ec9b Mon Sep 17 00:00:00 2001 From: Christos Nasikas Date: Mon, 14 Dec 2020 21:11:53 +0200 Subject: [PATCH 37/95] [Security Solution][Case] Sync cases with alerts (#84731) --- x-pack/plugins/case/common/api/cases/case.ts | 8 +- .../case/common/api/cases/user_actions.ts | 1 + x-pack/plugins/case/kibana.json | 2 +- .../server/client/alerts/update_status.ts | 25 +++++ .../case/server/client/cases/create.test.ts | 28 +++++- .../case/server/client/cases/create.ts | 2 +- .../case/server/client/cases/update.test.ts | 38 ++++++-- .../case/server/client/cases/update.ts | 65 ++++++++++++- .../case/server/client/comments/add.test.ts | 9 ++ .../case/server/client/comments/add.ts | 24 ++++- .../plugins/case/server/client/index.test.ts | 26 +++++- x-pack/plugins/case/server/client/index.ts | 18 ++++ x-pack/plugins/case/server/client/mocks.ts | 54 +++++++++-- x-pack/plugins/case/server/client/types.ts | 16 +++- .../case/server/connectors/case/index.test.ts | 92 ++++++++++++++++++- .../case/server/connectors/case/index.ts | 29 +++++- .../case/server/connectors/case/schema.ts | 30 ++++-- .../plugins/case/server/connectors/index.ts | 4 + x-pack/plugins/case/server/plugin.ts | 21 ++++- .../api/__fixtures__/mock_saved_objects.ts | 15 +++ .../routes/api/__fixtures__/route_contexts.ts | 39 +++++--- .../routes/api/__mocks__/request_responses.ts | 3 + .../routes/api/cases/comments/post_comment.ts | 2 +- .../routes/api/cases/patch_cases.test.ts | 9 ++ .../server/routes/api/cases/patch_cases.ts | 2 +- .../server/routes/api/cases/post_case.test.ts | 18 ++++ .../case/server/routes/api/utils.test.ts | 12 +++ .../case/server/saved_object_types/cases.ts | 7 ++ .../server/saved_object_types/migrations.ts | 36 ++++++-- .../case/server/services/alerts/index.ts | 57 ++++++++++++ x-pack/plugins/case/server/services/index.ts | 1 + x-pack/plugins/case/server/services/mocks.ts | 13 ++- .../server/services/user_actions/helpers.ts | 1 + x-pack/plugins/case/server/types.ts | 5 + .../cases/components/all_cases/index.test.tsx | 3 + .../components/case_action_bar/index.tsx | 75 +++++++++++---- .../case_settings/sync_alerts_switch.tsx | 48 ++++++++++ .../cases/components/case_view/index.tsx | 21 ++++- .../cases/components/create/connector.tsx | 4 +- .../cases/components/create/form.test.tsx | 1 + .../public/cases/components/create/form.tsx | 20 +++- .../cases/components/create/form_context.tsx | 12 ++- .../cases/components/create/index.test.tsx | 8 +- .../public/cases/components/create/schema.tsx | 11 ++- .../components/create/sync_alerts_toggle.tsx | 37 ++++++++ .../cases/components/create/translations.ts | 14 +++ .../user_action_alert_comment_event.tsx | 7 +- .../public/cases/containers/api.test.tsx | 3 + .../public/cases/containers/mock.ts | 3 + .../public/cases/containers/types.ts | 2 + .../public/cases/containers/use_get_case.tsx | 3 + .../cases/containers/use_post_case.test.tsx | 3 + .../cases/containers/use_update_case.tsx | 2 +- .../public/cases/translations.ts | 22 +++++ .../plugins/security_solution/server/index.ts | 2 + .../basic/tests/cases/migrations.ts | 13 +++ .../user_actions/get_all_user_actions.ts | 11 ++- .../basic/tests/connectors/case.ts | 64 ++++++++++++- .../case_api_integration/common/lib/mock.ts | 3 + 59 files changed, 996 insertions(+), 108 deletions(-) create mode 100644 x-pack/plugins/case/server/client/alerts/update_status.ts create mode 100644 x-pack/plugins/case/server/services/alerts/index.ts create mode 100644 x-pack/plugins/security_solution/public/cases/components/case_settings/sync_alerts_switch.tsx create mode 100644 x-pack/plugins/security_solution/public/cases/components/create/sync_alerts_toggle.tsx diff --git a/x-pack/plugins/case/common/api/cases/case.ts b/x-pack/plugins/case/common/api/cases/case.ts index 9b99bf0e54cc2..a08e1fbca66ea 100644 --- a/x-pack/plugins/case/common/api/cases/case.ts +++ b/x-pack/plugins/case/common/api/cases/case.ts @@ -29,12 +29,17 @@ const CaseStatusRt = rt.union([ export const caseStatuses = Object.values(CaseStatuses); +const SettingsRt = rt.type({ + syncAlerts: rt.boolean, +}); + const CaseBasicRt = rt.type({ - connector: CaseConnectorRt, description: rt.string, status: CaseStatusRt, tags: rt.array(rt.string), title: rt.string, + connector: CaseConnectorRt, + settings: SettingsRt, }); const CaseExternalServiceBasicRt = rt.type({ @@ -74,6 +79,7 @@ export const CasePostRequestRt = rt.type({ tags: rt.array(rt.string), title: rt.string, connector: CaseConnectorRt, + settings: SettingsRt, }); export const CaseExternalServiceRequestRt = CaseExternalServiceBasicRt; diff --git a/x-pack/plugins/case/common/api/cases/user_actions.ts b/x-pack/plugins/case/common/api/cases/user_actions.ts index 1a3ccfc04eed9..e7aa67db9287e 100644 --- a/x-pack/plugins/case/common/api/cases/user_actions.ts +++ b/x-pack/plugins/case/common/api/cases/user_actions.ts @@ -20,6 +20,7 @@ const UserActionFieldRt = rt.array( rt.literal('tags'), rt.literal('title'), rt.literal('status'), + rt.literal('settings'), ]) ); const UserActionRt = rt.union([ diff --git a/x-pack/plugins/case/kibana.json b/x-pack/plugins/case/kibana.json index 55416ee28c7df..2048ae41fa8ab 100644 --- a/x-pack/plugins/case/kibana.json +++ b/x-pack/plugins/case/kibana.json @@ -2,7 +2,7 @@ "configPath": ["xpack", "case"], "id": "case", "kibanaVersion": "kibana", - "requiredPlugins": ["actions"], + "requiredPlugins": ["actions", "securitySolution"], "optionalPlugins": [ "spaces", "security" diff --git a/x-pack/plugins/case/server/client/alerts/update_status.ts b/x-pack/plugins/case/server/client/alerts/update_status.ts new file mode 100644 index 0000000000000..d90424eb5fb15 --- /dev/null +++ b/x-pack/plugins/case/server/client/alerts/update_status.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Boom from '@hapi/boom'; +import { CaseClientUpdateAlertsStatus, CaseClientFactoryArguments } from '../types'; + +export const updateAlertsStatus = ({ + alertsService, + request, + context, +}: CaseClientFactoryArguments) => async ({ + ids, + status, +}: CaseClientUpdateAlertsStatus): Promise => { + const securitySolutionClient = context?.securitySolution?.getAppClient(); + if (securitySolutionClient == null) { + throw Boom.notFound('securitySolutionClient client have not been found'); + } + + const index = securitySolutionClient.getSignalsIndex(); + await alertsService.updateAlertsStatus({ ids, status, index, request }); +}; diff --git a/x-pack/plugins/case/server/client/cases/create.test.ts b/x-pack/plugins/case/server/client/cases/create.test.ts index e09ce226b3125..90116e3728883 100644 --- a/x-pack/plugins/case/server/client/cases/create.test.ts +++ b/x-pack/plugins/case/server/client/cases/create.test.ts @@ -34,6 +34,9 @@ describe('create', () => { type: ConnectorTypes.jira, fields: { issueType: 'Task', priority: 'High', parent: null }, }, + settings: { + syncAlerts: true, + }, } as CasePostRequest; const savedObjectsClient = createMockSavedObjectsRepository({ @@ -65,6 +68,9 @@ describe('create', () => { updated_at: null, updated_by: null, version: 'WzksMV0=', + settings: { + syncAlerts: true, + }, }); expect( @@ -79,9 +85,9 @@ describe('create', () => { full_name: 'Awesome D00d', username: 'awesome', }, - action_field: ['description', 'status', 'tags', 'title', 'connector'], + action_field: ['description', 'status', 'tags', 'title', 'connector', 'settings'], new_value: - '{"description":"This is a brand new case of a bad meanie defacing data","title":"Super Bad Security Issue","tags":["defacement"],"connector":{"id":"123","name":"Jira","type":".jira","fields":{"issueType":"Task","priority":"High","parent":null}}}', + '{"description":"This is a brand new case of a bad meanie defacing data","title":"Super Bad Security Issue","tags":["defacement"],"connector":{"id":"123","name":"Jira","type":".jira","fields":{"issueType":"Task","priority":"High","parent":null}},"settings":{"syncAlerts":true}}', old_value: null, }, references: [ @@ -106,6 +112,9 @@ describe('create', () => { type: ConnectorTypes.none, fields: null, }, + settings: { + syncAlerts: true, + }, }; const savedObjectsClient = createMockSavedObjectsRepository({ @@ -131,6 +140,9 @@ describe('create', () => { updated_at: null, updated_by: null, version: 'WzksMV0=', + settings: { + syncAlerts: true, + }, }); }); @@ -145,6 +157,9 @@ describe('create', () => { type: ConnectorTypes.none, fields: null, }, + settings: { + syncAlerts: true, + }, }; const savedObjectsClient = createMockSavedObjectsRepository({ @@ -174,6 +189,9 @@ describe('create', () => { updated_at: null, updated_by: null, version: 'WzksMV0=', + settings: { + syncAlerts: true, + }, }); }); }); @@ -323,6 +341,9 @@ describe('create', () => { type: ConnectorTypes.none, fields: null, }, + settings: { + syncAlerts: true, + }, }; const savedObjectsClient = createMockSavedObjectsRepository({ @@ -347,6 +368,9 @@ describe('create', () => { type: ConnectorTypes.none, fields: null, }, + settings: { + syncAlerts: true, + }, }; const savedObjectsClient = createMockSavedObjectsRepository({ caseSavedObject: mockCases, diff --git a/x-pack/plugins/case/server/client/cases/create.ts b/x-pack/plugins/case/server/client/cases/create.ts index 59222be062c75..1dca025036c1e 100644 --- a/x-pack/plugins/case/server/client/cases/create.ts +++ b/x-pack/plugins/case/server/client/cases/create.ts @@ -64,7 +64,7 @@ export const create = ({ actionAt: createdDate, actionBy: { username, full_name, email }, caseId: newCase.id, - fields: ['description', 'status', 'tags', 'title', 'connector'], + fields: ['description', 'status', 'tags', 'title', 'connector', 'settings'], newValue: JSON.stringify(query), }), ], diff --git a/x-pack/plugins/case/server/client/cases/update.test.ts b/x-pack/plugins/case/server/client/cases/update.test.ts index ae701f16b2bcb..1f9e8cc788404 100644 --- a/x-pack/plugins/case/server/client/cases/update.test.ts +++ b/x-pack/plugins/case/server/client/cases/update.test.ts @@ -38,7 +38,10 @@ describe('update', () => { }); const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient); - const res = await caseClient.client.update({ cases: patchCases }); + const res = await caseClient.client.update({ + caseClient: caseClient.client, + cases: patchCases, + }); expect(res).toEqual([ { @@ -63,6 +66,9 @@ describe('update', () => { updated_at: '2019-11-25T21:54:48.952Z', updated_by: { email: 'd00d@awesome.com', full_name: 'Awesome D00d', username: 'awesome' }, version: 'WzE3LDFd', + settings: { + syncAlerts: true, + }, }, ]); @@ -115,7 +121,10 @@ describe('update', () => { }); const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient); - const res = await caseClient.client.update({ cases: patchCases }); + const res = await caseClient.client.update({ + caseClient: caseClient.client, + cases: patchCases, + }); expect(res).toEqual([ { @@ -140,6 +149,9 @@ describe('update', () => { updated_at: '2019-11-25T21:54:48.952Z', updated_by: { email: 'd00d@awesome.com', full_name: 'Awesome D00d', username: 'awesome' }, version: 'WzE3LDFd', + settings: { + syncAlerts: true, + }, }, ]); }); @@ -160,7 +172,10 @@ describe('update', () => { }); const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient); - const res = await caseClient.client.update({ cases: patchCases }); + const res = await caseClient.client.update({ + caseClient: caseClient.client, + cases: patchCases, + }); expect(res).toEqual([ { @@ -185,6 +200,9 @@ describe('update', () => { updated_at: '2019-11-25T21:54:48.952Z', updated_by: { email: 'd00d@awesome.com', full_name: 'Awesome D00d', username: 'awesome' }, version: 'WzE3LDFd', + settings: { + syncAlerts: true, + }, }, ]); }); @@ -210,7 +228,10 @@ describe('update', () => { }); const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient); - const res = await caseClient.client.update({ cases: patchCases }); + const res = await caseClient.client.update({ + caseClient: caseClient.client, + cases: patchCases, + }); expect(res).toEqual([ { @@ -243,6 +264,9 @@ describe('update', () => { username: 'awesome', }, version: 'WzE3LDFd', + settings: { + syncAlerts: true, + }, }, ]); }); @@ -328,7 +352,7 @@ describe('update', () => { }); const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient); - caseClient.client.update({ cases: patchCases }).catch((e) => { + caseClient.client.update({ caseClient: caseClient.client, cases: patchCases }).catch((e) => { expect(e).not.toBeNull(); expect(e.isBoom).toBe(true); expect(e.output.statusCode).toBe(406); @@ -358,7 +382,7 @@ describe('update', () => { }); const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient); - caseClient.client.update({ cases: patchCases }).catch((e) => { + caseClient.client.update({ caseClient: caseClient.client, cases: patchCases }).catch((e) => { expect(e).not.toBeNull(); expect(e.isBoom).toBe(true); expect(e.output.statusCode).toBe(404); @@ -385,7 +409,7 @@ describe('update', () => { }); const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient); - caseClient.client.update({ cases: patchCases }).catch((e) => { + caseClient.client.update({ caseClient: caseClient.client, cases: patchCases }).catch((e) => { expect(e).not.toBeNull(); expect(e.isBoom).toBe(true); expect(e.output.statusCode).toBe(409); diff --git a/x-pack/plugins/case/server/client/cases/update.ts b/x-pack/plugins/case/server/client/cases/update.ts index 406e43a74cccf..e2b6cb8337251 100644 --- a/x-pack/plugins/case/server/client/cases/update.ts +++ b/x-pack/plugins/case/server/client/cases/update.ts @@ -9,6 +9,7 @@ import { pipe } from 'fp-ts/lib/pipeable'; import { fold } from 'fp-ts/lib/Either'; import { identity } from 'fp-ts/lib/function'; +import { SavedObjectsFindResponse } from 'kibana/server'; import { flattenCaseSavedObject } from '../../routes/api/utils'; import { @@ -34,7 +35,10 @@ export const update = ({ caseService, userActionService, request, -}: CaseClientFactoryArguments) => async ({ cases }: CaseClientUpdate): Promise => { +}: CaseClientFactoryArguments) => async ({ + caseClient, + cases, +}: CaseClientUpdate): Promise => { const query = pipe( excess(CasesPatchRequestRt).decode(cases), fold(throwErrors(Boom.badRequest), identity) @@ -126,6 +130,65 @@ export const update = ({ }), }); + // If a status update occurred and the case is synced then we need to update all alerts' status + // attached to the case to the new status. + const casesWithStatusChangedAndSynced = updateFilterCases.filter((caseToUpdate) => { + const currentCase = myCases.saved_objects.find((c) => c.id === caseToUpdate.id); + return ( + currentCase != null && + caseToUpdate.status != null && + currentCase.attributes.status !== caseToUpdate.status && + currentCase.attributes.settings.syncAlerts + ); + }); + + // If syncAlerts setting turned on we need to update all alerts' status + // attached to the case to the current status. + const casesWithSyncSettingChangedToOn = updateFilterCases.filter((caseToUpdate) => { + const currentCase = myCases.saved_objects.find((c) => c.id === caseToUpdate.id); + return ( + currentCase != null && + caseToUpdate.settings?.syncAlerts != null && + currentCase.attributes.settings.syncAlerts !== caseToUpdate.settings.syncAlerts && + caseToUpdate.settings.syncAlerts + ); + }); + + for (const theCase of [ + ...casesWithSyncSettingChangedToOn, + ...casesWithStatusChangedAndSynced, + ]) { + const currentCase = myCases.saved_objects.find((c) => c.id === theCase.id); + const totalComments = await caseService.getAllCaseComments({ + client: savedObjectsClient, + caseId: theCase.id, + options: { + fields: [], + filter: 'cases-comments.attributes.type: alert', + page: 1, + perPage: 1, + }, + }); + + const caseComments = (await caseService.getAllCaseComments({ + client: savedObjectsClient, + caseId: theCase.id, + options: { + fields: [], + filter: 'cases-comments.attributes.type: alert', + page: 1, + perPage: totalComments.total, + }, + // The filter guarantees that the comments will be of type alert + })) as SavedObjectsFindResponse<{ alertId: string }>; + + caseClient.updateAlertsStatus({ + ids: caseComments.saved_objects.map(({ attributes: { alertId } }) => alertId), + // Either there is a status update or the syncAlerts got turned on. + status: theCase.status ?? currentCase?.attributes.status ?? CaseStatuses.open, + }); + } + const returnUpdatedCase = myCases.saved_objects .filter((myCase) => updatedCases.saved_objects.some((updatedCase) => updatedCase.id === myCase.id) diff --git a/x-pack/plugins/case/server/client/comments/add.test.ts b/x-pack/plugins/case/server/client/comments/add.test.ts index d00df5a3246bd..40b87f6ad17f0 100644 --- a/x-pack/plugins/case/server/client/comments/add.test.ts +++ b/x-pack/plugins/case/server/client/comments/add.test.ts @@ -31,6 +31,7 @@ describe('addComment', () => { const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient); const res = await caseClient.client.addComment({ + caseClient: caseClient.client, caseId: 'mock-id-1', comment: { comment: 'Wow, good luck catching that bad meanie!', @@ -66,6 +67,7 @@ describe('addComment', () => { const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient); const res = await caseClient.client.addComment({ + caseClient: caseClient.client, caseId: 'mock-id-1', comment: { type: CommentType.alert, @@ -103,6 +105,7 @@ describe('addComment', () => { const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient); const res = await caseClient.client.addComment({ + caseClient: caseClient.client, caseId: 'mock-id-1', comment: { comment: 'Wow, good luck catching that bad meanie!', @@ -126,6 +129,7 @@ describe('addComment', () => { const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient); await caseClient.client.addComment({ + caseClient: caseClient.client, caseId: 'mock-id-1', comment: { comment: 'Wow, good luck catching that bad meanie!', @@ -173,6 +177,7 @@ describe('addComment', () => { const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient, true); const res = await caseClient.client.addComment({ + caseClient: caseClient.client, caseId: 'mock-id-1', comment: { comment: 'Wow, good luck catching that bad meanie!', @@ -267,6 +272,7 @@ describe('addComment', () => { ['alertId', 'index'].forEach((attribute) => { caseClient.client .addComment({ + caseClient: caseClient.client, caseId: 'mock-id-1', comment: { [attribute]: attribute, @@ -328,6 +334,7 @@ describe('addComment', () => { ['comment'].forEach((attribute) => { caseClient.client .addComment({ + caseClient: caseClient.client, caseId: 'mock-id-1', comment: { [attribute]: attribute, @@ -354,6 +361,7 @@ describe('addComment', () => { const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient); caseClient.client .addComment({ + caseClient: caseClient.client, caseId: 'not-exists', comment: { comment: 'Wow, good luck catching that bad meanie!', @@ -377,6 +385,7 @@ describe('addComment', () => { const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient); caseClient.client .addComment({ + caseClient: caseClient.client, caseId: 'mock-id-1', comment: { comment: 'Throw an error', diff --git a/x-pack/plugins/case/server/client/comments/add.ts b/x-pack/plugins/case/server/client/comments/add.ts index 169157c95d4c1..bb61094cfa3bd 100644 --- a/x-pack/plugins/case/server/client/comments/add.ts +++ b/x-pack/plugins/case/server/client/comments/add.ts @@ -11,7 +11,14 @@ import { identity } from 'fp-ts/lib/function'; import { decodeComment, flattenCaseSavedObject, transformNewComment } from '../../routes/api/utils'; -import { throwErrors, CaseResponseRt, CommentRequestRt, CaseResponse } from '../../../common/api'; +import { + throwErrors, + CaseResponseRt, + CommentRequestRt, + CaseResponse, + CommentType, + CaseStatuses, +} from '../../../common/api'; import { buildCommentUserActionItem } from '../../services/user_actions/helpers'; import { CaseClientAddComment, CaseClientFactoryArguments } from '../types'; @@ -23,11 +30,11 @@ export const addComment = ({ userActionService, request, }: CaseClientFactoryArguments) => async ({ + caseClient, caseId, comment, }: CaseClientAddComment): Promise => { const query = pipe( - // TODO: Excess CommentRequestRt when the excess() function supports union types CommentRequestRt.decode(comment), fold(throwErrors(Boom.badRequest), identity) ); @@ -39,6 +46,11 @@ export const addComment = ({ caseId, }); + // An alert cannot be attach to a closed case. + if (query.type === CommentType.alert && myCase.attributes.status === CaseStatuses.closed) { + throw Boom.badRequest('Alert cannot be attached to a closed case'); + } + // eslint-disable-next-line @typescript-eslint/naming-convention const { username, full_name, email } = await caseService.getUser({ request }); const createdDate = new Date().toISOString(); @@ -72,6 +84,14 @@ export const addComment = ({ }), ]); + // If the case is synced with alerts the newly attached alert must match the status of the case. + if (newComment.attributes.type === CommentType.alert && myCase.attributes.settings.syncAlerts) { + caseClient.updateAlertsStatus({ + ids: [newComment.attributes.alertId], + status: myCase.attributes.status, + }); + } + const totalCommentsFindByCases = await caseService.getAllCaseComments({ client: savedObjectsClient, caseId, diff --git a/x-pack/plugins/case/server/client/index.test.ts b/x-pack/plugins/case/server/client/index.test.ts index 1ecdc8ea96dea..ef4491204d9f5 100644 --- a/x-pack/plugins/case/server/client/index.test.ts +++ b/x-pack/plugins/case/server/client/index.test.ts @@ -4,32 +4,38 @@ * you may not use this file except in compliance with the Elastic License. */ -import { KibanaRequest } from 'kibana/server'; +import { KibanaRequest, RequestHandlerContext } from 'kibana/server'; import { savedObjectsClientMock } from '../../../../../src/core/server/mocks'; import { createCaseClient } from '.'; import { createCaseServiceMock, createConfigureServiceMock, createUserActionServiceMock, + createAlertServiceMock, } from '../services/mocks'; import { create } from './cases/create'; import { update } from './cases/update'; import { addComment } from './comments/add'; +import { updateAlertsStatus } from './alerts/update_status'; jest.mock('./cases/create'); jest.mock('./cases/update'); jest.mock('./comments/add'); +jest.mock('./alerts/update_status'); const caseService = createCaseServiceMock(); const caseConfigureService = createConfigureServiceMock(); const userActionService = createUserActionServiceMock(); +const alertsService = createAlertServiceMock(); const savedObjectsClient = savedObjectsClientMock.create(); const request = {} as KibanaRequest; +const context = {} as RequestHandlerContext; const createMock = create as jest.Mock; const updateMock = update as jest.Mock; const addCommentMock = addComment as jest.Mock; +const updateAlertsStatusMock = updateAlertsStatus as jest.Mock; describe('createCaseClient()', () => { test('it creates the client correctly', async () => { @@ -39,6 +45,8 @@ describe('createCaseClient()', () => { caseConfigureService, caseService, userActionService, + alertsService, + context, }); expect(createMock).toHaveBeenCalledWith({ @@ -47,6 +55,8 @@ describe('createCaseClient()', () => { caseConfigureService, caseService, userActionService, + alertsService, + context, }); expect(updateMock).toHaveBeenCalledWith({ @@ -55,6 +65,8 @@ describe('createCaseClient()', () => { caseConfigureService, caseService, userActionService, + alertsService, + context, }); expect(addCommentMock).toHaveBeenCalledWith({ @@ -63,6 +75,18 @@ describe('createCaseClient()', () => { caseConfigureService, caseService, userActionService, + alertsService, + context, + }); + + expect(updateAlertsStatusMock).toHaveBeenCalledWith({ + savedObjectsClient, + request, + caseConfigureService, + caseService, + userActionService, + alertsService, + context, }); }); }); diff --git a/x-pack/plugins/case/server/client/index.ts b/x-pack/plugins/case/server/client/index.ts index 75e9e3c4cfebc..bf43921b46466 100644 --- a/x-pack/plugins/case/server/client/index.ts +++ b/x-pack/plugins/case/server/client/index.ts @@ -8,6 +8,7 @@ import { CaseClientFactoryArguments, CaseClient } from './types'; import { create } from './cases/create'; import { update } from './cases/update'; import { addComment } from './comments/add'; +import { updateAlertsStatus } from './alerts/update_status'; export { CaseClient } from './types'; @@ -17,6 +18,8 @@ export const createCaseClient = ({ caseConfigureService, caseService, userActionService, + alertsService, + context, }: CaseClientFactoryArguments): CaseClient => { return { create: create({ @@ -25,6 +28,8 @@ export const createCaseClient = ({ caseConfigureService, caseService, userActionService, + alertsService, + context, }), update: update({ savedObjectsClient, @@ -32,6 +37,8 @@ export const createCaseClient = ({ caseConfigureService, caseService, userActionService, + alertsService, + context, }), addComment: addComment({ savedObjectsClient, @@ -39,6 +46,17 @@ export const createCaseClient = ({ caseConfigureService, caseService, userActionService, + alertsService, + context, + }), + updateAlertsStatus: updateAlertsStatus({ + savedObjectsClient, + request, + caseConfigureService, + caseService, + userActionService, + alertsService, + context, }), }; }; diff --git a/x-pack/plugins/case/server/client/mocks.ts b/x-pack/plugins/case/server/client/mocks.ts index 243dd884f9ef6..dd4e8b52b4dc6 100644 --- a/x-pack/plugins/case/server/client/mocks.ts +++ b/x-pack/plugins/case/server/client/mocks.ts @@ -4,18 +4,26 @@ * you may not use this file except in compliance with the Elastic License. */ -import { KibanaRequest } from 'kibana/server'; -import { loggingSystemMock } from '../../../../../src/core/server/mocks'; -import { CaseService, CaseConfigureService, CaseUserActionServiceSetup } from '../services'; +import { KibanaRequest, RequestHandlerContext } from 'kibana/server'; +import { loggingSystemMock, elasticsearchServiceMock } from '../../../../../src/core/server/mocks'; +import { actionsClientMock } from '../../../actions/server/mocks'; +import { + CaseService, + CaseConfigureService, + CaseUserActionServiceSetup, + AlertService, +} from '../services'; import { CaseClient } from './types'; import { authenticationMock } from '../routes/api/__fixtures__'; import { createCaseClient } from '.'; +import { getActions } from '../routes/api/__mocks__/request_responses'; export type CaseClientMock = jest.Mocked; export const createCaseClientMock = (): CaseClientMock => ({ create: jest.fn(), update: jest.fn(), addComment: jest.fn(), + updateAlertsStatus: jest.fn(), }); export const createCaseClientWithMockSavedObjectsClient = async ( @@ -25,7 +33,10 @@ export const createCaseClientWithMockSavedObjectsClient = async ( client: CaseClient; services: { userActionService: jest.Mocked }; }> => { + const actionsMock = actionsClientMock.create(); + actionsMock.getAll.mockImplementation(() => Promise.resolve(getActions())); const log = loggingSystemMock.create().get('case'); + const esClientMock = elasticsearchServiceMock.createClusterClient(); const request = {} as KibanaRequest; const caseServicePlugin = new CaseService(log); @@ -39,15 +50,38 @@ export const createCaseClientWithMockSavedObjectsClient = async ( postUserActions: jest.fn(), getUserActions: jest.fn(), }; + const alertsService = new AlertService(); + alertsService.initialize(esClientMock); + + const context = ({ + core: { + savedObjects: { + client: savedObjectsClient, + }, + }, + actions: { getActionsClient: () => actionsMock }, + case: { + getCaseClient: () => caseClient, + }, + securitySolution: { + getAppClient: () => ({ + getSignalsIndex: () => '.siem-signals', + }), + }, + } as unknown) as RequestHandlerContext; + + const caseClient = createCaseClient({ + savedObjectsClient, + request, + caseService, + caseConfigureService, + userActionService, + alertsService, + context, + }); return { - client: createCaseClient({ - savedObjectsClient, - request, - caseService, - caseConfigureService, - userActionService, - }), + client: caseClient, services: { userActionService }, }; }; diff --git a/x-pack/plugins/case/server/client/types.ts b/x-pack/plugins/case/server/client/types.ts index 8db7d8a5747d7..a9e8494c43dbc 100644 --- a/x-pack/plugins/case/server/client/types.ts +++ b/x-pack/plugins/case/server/client/types.ts @@ -4,18 +4,20 @@ * you may not use this file except in compliance with the Elastic License. */ -import { KibanaRequest, SavedObjectsClientContract } from '../../../../../src/core/server'; +import { KibanaRequest, SavedObjectsClientContract, RequestHandlerContext } from 'kibana/server'; import { CasePostRequest, CasesPatchRequest, CommentRequest, CaseResponse, CasesResponse, + CaseStatuses, } from '../../common/api'; import { CaseConfigureServiceSetup, CaseServiceSetup, CaseUserActionServiceSetup, + AlertServiceContract, } from '../services'; export interface CaseClientCreate { @@ -23,24 +25,36 @@ export interface CaseClientCreate { } export interface CaseClientUpdate { + caseClient: CaseClient; cases: CasesPatchRequest; } export interface CaseClientAddComment { + caseClient: CaseClient; caseId: string; comment: CommentRequest; } +export interface CaseClientUpdateAlertsStatus { + ids: string[]; + status: CaseStatuses; +} + +type PartialExceptFor = Partial & Pick; + export interface CaseClientFactoryArguments { savedObjectsClient: SavedObjectsClientContract; request: KibanaRequest; caseConfigureService: CaseConfigureServiceSetup; caseService: CaseServiceSetup; userActionService: CaseUserActionServiceSetup; + alertsService: AlertServiceContract; + context?: PartialExceptFor; } export interface CaseClient { create: (args: CaseClientCreate) => Promise; update: (args: CaseClientUpdate) => Promise; addComment: (args: CaseClientAddComment) => Promise; + updateAlertsStatus: (args: CaseClientUpdateAlertsStatus) => Promise; } diff --git a/x-pack/plugins/case/server/connectors/case/index.test.ts b/x-pack/plugins/case/server/connectors/case/index.test.ts index adf94661216cb..9f5b186c0c687 100644 --- a/x-pack/plugins/case/server/connectors/case/index.test.ts +++ b/x-pack/plugins/case/server/connectors/case/index.test.ts @@ -14,6 +14,7 @@ import { createCaseServiceMock, createConfigureServiceMock, createUserActionServiceMock, + createAlertServiceMock, } from '../../services/mocks'; import { CaseActionType, CaseActionTypeExecutorOptions, CaseExecutorParams } from './types'; import { getActionType } from '.'; @@ -35,11 +36,13 @@ describe('case connector', () => { const caseService = createCaseServiceMock(); const caseConfigureService = createConfigureServiceMock(); const userActionService = createUserActionServiceMock(); + const alertsService = createAlertServiceMock(); caseActionType = getActionType({ logger, caseService, caseConfigureService, userActionService, + alertsService, }); }); @@ -62,6 +65,9 @@ describe('case connector', () => { parent: null, }, }, + settings: { + syncAlerts: true, + }, }, }; @@ -98,6 +104,9 @@ describe('case connector', () => { parent: null, }, }, + settings: { + syncAlerts: true, + }, }, }, }, @@ -118,6 +127,9 @@ describe('case connector', () => { severityCode: '3', }, }, + settings: { + syncAlerts: true, + }, }, }, }, @@ -139,6 +151,9 @@ describe('case connector', () => { urgency: 'Medium', }, }, + settings: { + syncAlerts: true, + }, }, }, }, @@ -156,6 +171,9 @@ describe('case connector', () => { type: '.none', fields: null, }, + settings: { + syncAlerts: true, + }, }, }, }, @@ -180,6 +198,9 @@ describe('case connector', () => { type: '.servicenow', fields: {}, }, + settings: { + syncAlerts: true, + }, }, }; @@ -195,6 +216,9 @@ describe('case connector', () => { type: '.servicenow', fields: { impact: null, severity: null, urgency: null }, }, + settings: { + syncAlerts: true, + }, }, }); }); @@ -212,6 +236,9 @@ describe('case connector', () => { type: '.none', fields: null, }, + settings: { + syncAlerts: true, + }, }, }; @@ -234,6 +261,9 @@ describe('case connector', () => { parent: null, }, }, + settings: { + syncAlerts: true, + }, }, }; @@ -262,6 +292,9 @@ describe('case connector', () => { excess: null, }, }, + settings: { + syncAlerts: true, + }, }, }; @@ -289,6 +322,9 @@ describe('case connector', () => { parent: null, }, }, + settings: { + syncAlerts: true, + }, }, }; @@ -312,6 +348,9 @@ describe('case connector', () => { type: '.none', fields: {}, }, + settings: { + syncAlerts: true, + }, }, }; @@ -343,6 +382,7 @@ describe('case connector', () => { title: null, status: null, connector: null, + settings: null, ...(params.subActionParams as Record), }, }); @@ -375,6 +415,7 @@ describe('case connector', () => { tags: null, title: null, status: null, + settings: null, ...(params.subActionParams as Record), }, }); @@ -405,6 +446,7 @@ describe('case connector', () => { tags: null, title: null, status: null, + settings: null, ...(params.subActionParams as Record), }, }); @@ -436,6 +478,7 @@ describe('case connector', () => { tags: null, title: null, status: null, + settings: null, ...(params.subActionParams as Record), }, }); @@ -465,6 +508,7 @@ describe('case connector', () => { tags: null, title: null, status: null, + settings: null, connector: { id: 'servicenow', name: 'Servicenow', @@ -497,6 +541,7 @@ describe('case connector', () => { tags: null, title: null, status: null, + settings: null, ...(params.subActionParams as Record), }, }); @@ -630,7 +675,9 @@ describe('case connector', () => { expect(validateParams(caseActionType, params)).toEqual(params); }); - it('succeeds when type is an alert', () => { + // TODO: Enable when the creation of comments of type alert is supported + // https://github.com/elastic/kibana/issues/85750 + it.skip('succeeds when type is an alert', () => { const params: Record = { subAction: 'addComment', subActionParams: { @@ -656,6 +703,26 @@ describe('case connector', () => { }).toThrow(); }); + // TODO: Remove it when the creation of comments of type alert is supported + // https://github.com/elastic/kibana/issues/85750 + it('fails when type is an alert', () => { + const params: Record = { + subAction: 'addComment', + subActionParams: { + caseId: 'case-id', + comment: { + type: CommentType.alert, + alertId: 'test-id', + index: 'test-index', + }, + }, + }; + + expect(() => { + validateParams(caseActionType, params); + }).toThrow(); + }); + it('fails when missing attributes: type user', () => { const allParams = { type: CommentType.user, @@ -678,7 +745,9 @@ describe('case connector', () => { }); }); - it('fails when missing attributes: type alert', () => { + // TODO: Enable when the creation of comments of type alert is supported + // https://github.com/elastic/kibana/issues/85750 + it.skip('fails when missing attributes: type alert', () => { const allParams = { type: CommentType.alert, comment: 'a comment', @@ -720,7 +789,9 @@ describe('case connector', () => { }); }); - it('fails when excess attributes are provided: type alert', () => { + // TODO: Enable when the creation of comments of type alert is supported + // https://github.com/elastic/kibana/issues/85750 + it.skip('fails when excess attributes are provided: type alert', () => { ['comment'].forEach((attribute) => { const params: Record = { subAction: 'addComment', @@ -789,6 +860,9 @@ describe('case connector', () => { updated_at: null, updated_by: null, version: 'WzksMV0=', + settings: { + syncAlerts: true, + }, }; mockCaseClient.create.mockReturnValue(Promise.resolve(createReturn)); @@ -810,6 +884,9 @@ describe('case connector', () => { parent: null, }, }, + settings: { + syncAlerts: true, + }, }, }; @@ -879,6 +956,9 @@ describe('case connector', () => { username: 'awesome', }, version: 'WzE3LDFd', + settings: { + syncAlerts: true, + }, }, ]; @@ -895,6 +975,7 @@ describe('case connector', () => { tags: null, status: null, connector: null, + settings: null, }, }; @@ -910,6 +991,7 @@ describe('case connector', () => { expect(result).toEqual({ actionId, status: 'ok', data: updateReturn }); expect(mockCaseClient.update).toHaveBeenCalledWith({ + caseClient: mockCaseClient, // Null values have been striped out. cases: { cases: [ @@ -960,6 +1042,9 @@ describe('case connector', () => { version: 'WzksMV0=', }, ], + settings: { + syncAlerts: true, + }, }; mockCaseClient.addComment.mockReturnValue(Promise.resolve(commentReturn)); @@ -988,6 +1073,7 @@ describe('case connector', () => { expect(result).toEqual({ actionId, status: 'ok', data: commentReturn }); expect(mockCaseClient.addComment).toHaveBeenCalledWith({ + caseClient: mockCaseClient, caseId: 'case-id', comment: { comment: 'a comment', diff --git a/x-pack/plugins/case/server/connectors/case/index.ts b/x-pack/plugins/case/server/connectors/case/index.ts index dc647d288ec65..48124b8ae32eb 100644 --- a/x-pack/plugins/case/server/connectors/case/index.ts +++ b/x-pack/plugins/case/server/connectors/case/index.ts @@ -6,7 +6,7 @@ import { curry } from 'lodash'; -import { KibanaRequest } from 'kibana/server'; +import { KibanaRequest, RequestHandlerContext } from 'kibana/server'; import { ActionTypeExecutorResult } from '../../../../actions/common'; import { CasePatchRequest, CasePostRequest } from '../../../common/api'; import { createCaseClient } from '../../client'; @@ -30,6 +30,7 @@ export function getActionType({ caseService, caseConfigureService, userActionService, + alertsService, }: GetActionTypeParams): CaseActionType { return { id: CASE_ACTION_TYPE_ID, @@ -39,13 +40,25 @@ export function getActionType({ config: CaseConfigurationSchema, params: CaseExecutorParamsSchema, }, - executor: curry(executor)({ logger, caseService, caseConfigureService, userActionService }), + executor: curry(executor)({ + logger, + caseService, + caseConfigureService, + userActionService, + alertsService, + }), }; } // action executor async function executor( - { logger, caseService, caseConfigureService, userActionService }: GetActionTypeParams, + { + logger, + caseService, + caseConfigureService, + userActionService, + alertsService, + }: GetActionTypeParams, execOptions: CaseActionTypeExecutorOptions ): Promise> { const { actionId, params, services } = execOptions; @@ -59,6 +72,9 @@ async function executor( caseService, caseConfigureService, userActionService, + alertsService, + // TODO: When case connector is enabled we should figure out how to pass the context. + context: {} as RequestHandlerContext, }); if (!supportedSubActions.includes(subAction)) { @@ -80,12 +96,15 @@ async function executor( {} as CasePatchRequest ); - data = await caseClient.update({ cases: { cases: [updateParamsWithoutNullValues] } }); + data = await caseClient.update({ + caseClient, + cases: { cases: [updateParamsWithoutNullValues] }, + }); } if (subAction === 'addComment') { const { caseId, comment } = subActionParams as ExecutorSubActionAddCommentParams; - data = await caseClient.addComment({ caseId, comment }); + data = await caseClient.addComment({ caseClient, caseId, comment }); } return { status: 'ok', data: data ?? {}, actionId }; diff --git a/x-pack/plugins/case/server/connectors/case/schema.ts b/x-pack/plugins/case/server/connectors/case/schema.ts index 039c0e2e7e67f..d17c9ce6eb1cc 100644 --- a/x-pack/plugins/case/server/connectors/case/schema.ts +++ b/x-pack/plugins/case/server/connectors/case/schema.ts @@ -14,13 +14,27 @@ const ContextTypeUserSchema = schema.object({ comment: schema.string(), }); -const ContextTypeAlertSchema = schema.object({ - type: schema.literal('alert'), - alertId: schema.string(), - index: schema.string(), -}); - -export const CommentSchema = schema.oneOf([ContextTypeUserSchema, ContextTypeAlertSchema]); +/** + * ContextTypeAlertSchema has been deleted. + * Comments of type alert need the siem signal index. + * Case connector is not being passed the context which contains the + * security solution app client which in turn provides the siem signal index. + * For that reason, we disable comments of type alert for the case connector until + * we figure out how to pass the security solution app client to the connector. + * See: x-pack/plugins/case/server/connectors/case/index.ts L76. + * + * The schema: + * + * const ContextTypeAlertSchema = schema.object({ + * type: schema.literal('alert'), + * alertId: schema.string(), + * index: schema.string(), + * }); + * + * Issue: https://github.com/elastic/kibana/issues/85750 + * */ + +export const CommentSchema = schema.oneOf([ContextTypeUserSchema]); const JiraFieldsSchema = schema.object({ issueType: schema.string(), @@ -80,6 +94,7 @@ const CaseBasicProps = { title: schema.string(), tags: schema.arrayOf(schema.string()), connector: schema.object(ConnectorProps, { validate: validateConnector }), + settings: schema.object({ syncAlerts: schema.boolean() }), }; const CaseUpdateRequestProps = { @@ -89,6 +104,7 @@ const CaseUpdateRequestProps = { title: schema.nullable(CaseBasicProps.title), tags: schema.nullable(CaseBasicProps.tags), connector: schema.nullable(CaseBasicProps.connector), + settings: schema.nullable(CaseBasicProps.settings), status: schema.nullable(schema.string()), }; diff --git a/x-pack/plugins/case/server/connectors/index.ts b/x-pack/plugins/case/server/connectors/index.ts index bee7b1e475457..f373445719164 100644 --- a/x-pack/plugins/case/server/connectors/index.ts +++ b/x-pack/plugins/case/server/connectors/index.ts @@ -16,6 +16,7 @@ import { CaseServiceSetup, CaseConfigureServiceSetup, CaseUserActionServiceSetup, + AlertServiceContract, } from '../services'; import { getActionType as getCaseConnector } from './case'; @@ -26,6 +27,7 @@ export interface GetActionTypeParams { caseService: CaseServiceSetup; caseConfigureService: CaseConfigureServiceSetup; userActionService: CaseUserActionServiceSetup; + alertsService: AlertServiceContract; } export interface RegisterConnectorsArgs extends GetActionTypeParams { @@ -45,6 +47,7 @@ export const registerConnectors = ({ caseService, caseConfigureService, userActionService, + alertsService, }: RegisterConnectorsArgs) => { actionsRegisterType( getCaseConnector({ @@ -52,6 +55,7 @@ export const registerConnectors = ({ caseService, caseConfigureService, userActionService, + alertsService, }) ); }; diff --git a/x-pack/plugins/case/server/plugin.ts b/x-pack/plugins/case/server/plugin.ts index 64c4b422d1cf7..8d508ce0b76b1 100644 --- a/x-pack/plugins/case/server/plugin.ts +++ b/x-pack/plugins/case/server/plugin.ts @@ -11,6 +11,7 @@ import { Logger, PluginInitializerContext, RequestHandler, + RequestHandlerContext, } from 'kibana/server'; import { CoreSetup, CoreStart } from 'src/core/server'; @@ -33,6 +34,8 @@ import { CaseServiceSetup, CaseUserActionService, CaseUserActionServiceSetup, + AlertService, + AlertServiceContract, } from './services'; import { createCaseClient } from './client'; import { registerConnectors } from './connectors'; @@ -51,6 +54,7 @@ export class CasePlugin { private caseService?: CaseServiceSetup; private caseConfigureService?: CaseConfigureServiceSetup; private userActionService?: CaseUserActionServiceSetup; + private alertsService?: AlertService; constructor(private readonly initializerContext: PluginInitializerContext) { this.log = this.initializerContext.logger.get(); @@ -79,6 +83,7 @@ export class CasePlugin { }); this.caseConfigureService = await new CaseConfigureService(this.log).setup(); this.userActionService = await new CaseUserActionService(this.log).setup(); + this.alertsService = new AlertService(); core.http.registerRouteHandlerContext( APP_ID, @@ -87,6 +92,7 @@ export class CasePlugin { caseService: this.caseService, caseConfigureService: this.caseConfigureService, userActionService: this.userActionService, + alertsService: this.alertsService, }) ); @@ -104,24 +110,31 @@ export class CasePlugin { caseService: this.caseService, caseConfigureService: this.caseConfigureService, userActionService: this.userActionService, + alertsService: this.alertsService, }); } public async start(core: CoreStart) { this.log.debug(`Starting Case Workflow`); + this.alertsService!.initialize(core.elasticsearch.client); - const getCaseClientWithRequest = async (request: KibanaRequest) => { + const getCaseClientWithRequestAndContext = async ( + context: RequestHandlerContext, + request: KibanaRequest + ) => { return createCaseClient({ savedObjectsClient: core.savedObjects.getScopedClient(request), request, caseService: this.caseService!, caseConfigureService: this.caseConfigureService!, userActionService: this.userActionService!, + alertsService: this.alertsService!, + context, }); }; return { - getCaseClientWithRequest, + getCaseClientWithRequestAndContext, }; } @@ -134,11 +147,13 @@ export class CasePlugin { caseService, caseConfigureService, userActionService, + alertsService, }: { core: CoreSetup; caseService: CaseServiceSetup; caseConfigureService: CaseConfigureServiceSetup; userActionService: CaseUserActionServiceSetup; + alertsService: AlertServiceContract; }): IContextProvider, typeof APP_ID> => { return async (context, request) => { const [{ savedObjects }] = await core.getStartServices(); @@ -149,7 +164,9 @@ export class CasePlugin { caseService, caseConfigureService, userActionService, + alertsService, request, + context, }); }, }; diff --git a/x-pack/plugins/case/server/routes/api/__fixtures__/mock_saved_objects.ts b/x-pack/plugins/case/server/routes/api/__fixtures__/mock_saved_objects.ts index 95856dd75d0ae..645673fdee756 100644 --- a/x-pack/plugins/case/server/routes/api/__fixtures__/mock_saved_objects.ts +++ b/x-pack/plugins/case/server/routes/api/__fixtures__/mock_saved_objects.ts @@ -44,6 +44,9 @@ export const mockCases: Array> = [ email: 'testemail@elastic.co', username: 'elastic', }, + settings: { + syncAlerts: true, + }, }, references: [], updated_at: '2019-11-25T21:54:48.952Z', @@ -78,6 +81,9 @@ export const mockCases: Array> = [ email: 'testemail@elastic.co', username: 'elastic', }, + settings: { + syncAlerts: true, + }, }, references: [], updated_at: '2019-11-25T22:32:00.900Z', @@ -116,6 +122,9 @@ export const mockCases: Array> = [ email: 'testemail@elastic.co', username: 'elastic', }, + settings: { + syncAlerts: true, + }, }, references: [], updated_at: '2019-11-25T22:32:17.947Z', @@ -158,6 +167,9 @@ export const mockCases: Array> = [ email: 'testemail@elastic.co', username: 'elastic', }, + settings: { + syncAlerts: true, + }, }, references: [], updated_at: '2019-11-25T22:32:17.947Z', @@ -188,6 +200,9 @@ export const mockCaseNoConnectorId: SavedObject> = { email: 'testemail@elastic.co', username: 'elastic', }, + settings: { + syncAlerts: true, + }, }, references: [], updated_at: '2019-11-25T21:54:48.952Z', diff --git a/x-pack/plugins/case/server/routes/api/__fixtures__/route_contexts.ts b/x-pack/plugins/case/server/routes/api/__fixtures__/route_contexts.ts index 67890599fa417..dcae1c6083eb6 100644 --- a/x-pack/plugins/case/server/routes/api/__fixtures__/route_contexts.ts +++ b/x-pack/plugins/case/server/routes/api/__fixtures__/route_contexts.ts @@ -5,10 +5,10 @@ */ import { RequestHandlerContext, KibanaRequest } from 'src/core/server'; -import { loggingSystemMock } from 'src/core/server/mocks'; +import { loggingSystemMock, elasticsearchServiceMock } from 'src/core/server/mocks'; import { actionsClientMock } from '../../../../../actions/server/mocks'; import { createCaseClient } from '../../../client'; -import { CaseService, CaseConfigureService } from '../../../services'; +import { CaseService, CaseConfigureService, AlertService } from '../../../services'; import { getActions } from '../__mocks__/request_responses'; import { authenticationMock } from '../__fixtures__'; @@ -16,6 +16,7 @@ export const createRouteContext = async (client: any, badAuth = false) => { const actionsMock = actionsClientMock.create(); actionsMock.getAll.mockImplementation(() => Promise.resolve(getActions())); const log = loggingSystemMock.create().get('case'); + const esClientMock = elasticsearchServiceMock.createClusterClient(); const caseServicePlugin = new CaseService(log); const caseConfigureServicePlugin = new CaseConfigureService(log); @@ -24,18 +25,10 @@ export const createRouteContext = async (client: any, badAuth = false) => { authentication: badAuth ? authenticationMock.createInvalid() : authenticationMock.create(), }); const caseConfigureService = await caseConfigureServicePlugin.setup(); - const caseClient = createCaseClient({ - savedObjectsClient: client, - request: {} as KibanaRequest, - caseService, - caseConfigureService, - userActionService: { - postUserActions: jest.fn(), - getUserActions: jest.fn(), - }, - }); + const alertsService = new AlertService(); + alertsService.initialize(esClientMock); - return ({ + const context = ({ core: { savedObjects: { client, @@ -45,5 +38,25 @@ export const createRouteContext = async (client: any, badAuth = false) => { case: { getCaseClient: () => caseClient, }, + securitySolution: { + getAppClient: () => ({ + getSignalsIndex: () => '.siem-signals', + }), + }, } as unknown) as RequestHandlerContext; + + const caseClient = createCaseClient({ + savedObjectsClient: client, + request: {} as KibanaRequest, + caseService, + caseConfigureService, + userActionService: { + postUserActions: jest.fn(), + getUserActions: jest.fn(), + }, + alertsService, + context, + }); + + return context; }; diff --git a/x-pack/plugins/case/server/routes/api/__mocks__/request_responses.ts b/x-pack/plugins/case/server/routes/api/__mocks__/request_responses.ts index ce35b99750419..209fa11116c56 100644 --- a/x-pack/plugins/case/server/routes/api/__mocks__/request_responses.ts +++ b/x-pack/plugins/case/server/routes/api/__mocks__/request_responses.ts @@ -17,6 +17,9 @@ export const newCase: CasePostRequest = { type: ConnectorTypes.none, fields: null, }, + settings: { + syncAlerts: true, + }, }; export const getActions = (): FindActionResult[] => [ diff --git a/x-pack/plugins/case/server/routes/api/cases/comments/post_comment.ts b/x-pack/plugins/case/server/routes/api/cases/comments/post_comment.ts index 08d442bccf2cb..139fb7c5f27a4 100644 --- a/x-pack/plugins/case/server/routes/api/cases/comments/post_comment.ts +++ b/x-pack/plugins/case/server/routes/api/cases/comments/post_comment.ts @@ -32,7 +32,7 @@ export function initPostCommentApi({ router }: RouteDeps) { try { return response.ok({ - body: await caseClient.addComment({ caseId, comment }), + body: await caseClient.addComment({ caseClient, caseId, comment }), }); } catch (error) { return response.customError(wrapError(error)); diff --git a/x-pack/plugins/case/server/routes/api/cases/patch_cases.test.ts b/x-pack/plugins/case/server/routes/api/cases/patch_cases.test.ts index 053f9ec18ab0f..6a6f5653375b8 100644 --- a/x-pack/plugins/case/server/routes/api/cases/patch_cases.test.ts +++ b/x-pack/plugins/case/server/routes/api/cases/patch_cases.test.ts @@ -74,6 +74,9 @@ describe('PATCH cases', () => { updated_at: '2019-11-25T21:54:48.952Z', updated_by: { email: 'd00d@awesome.com', full_name: 'Awesome D00d', username: 'awesome' }, version: 'WzE3LDFd', + settings: { + syncAlerts: true, + }, }, ]); }); @@ -125,6 +128,9 @@ describe('PATCH cases', () => { updated_at: '2019-11-25T21:54:48.952Z', updated_by: { email: 'd00d@awesome.com', full_name: 'Awesome D00d', username: 'awesome' }, version: 'WzE3LDFd', + settings: { + syncAlerts: true, + }, }, ]); }); @@ -175,6 +181,9 @@ describe('PATCH cases', () => { updated_at: '2019-11-25T21:54:48.952Z', updated_by: { email: 'd00d@awesome.com', full_name: 'Awesome D00d', username: 'awesome' }, version: 'WzE3LDFd', + settings: { + syncAlerts: true, + }, }, ]); }); diff --git a/x-pack/plugins/case/server/routes/api/cases/patch_cases.ts b/x-pack/plugins/case/server/routes/api/cases/patch_cases.ts index 873671a909801..178e40520d9d2 100644 --- a/x-pack/plugins/case/server/routes/api/cases/patch_cases.ts +++ b/x-pack/plugins/case/server/routes/api/cases/patch_cases.ts @@ -27,7 +27,7 @@ export function initPatchCasesApi({ router }: RouteDeps) { try { return response.ok({ - body: await caseClient.update({ cases }), + body: await caseClient.update({ caseClient, cases }), }); } catch (error) { return response.customError(wrapError(error)); diff --git a/x-pack/plugins/case/server/routes/api/cases/post_case.test.ts b/x-pack/plugins/case/server/routes/api/cases/post_case.test.ts index 508684b422891..ea59959b0e849 100644 --- a/x-pack/plugins/case/server/routes/api/cases/post_case.test.ts +++ b/x-pack/plugins/case/server/routes/api/cases/post_case.test.ts @@ -42,6 +42,9 @@ describe('POST cases', () => { type: ConnectorTypes.none, fields: null, }, + settings: { + syncAlerts: true, + }, }, }); @@ -78,6 +81,9 @@ describe('POST cases', () => { type: '.jira', fields: { issueType: 'Task', priority: 'High', parent: null }, }, + settings: { + syncAlerts: true, + }, }, }); @@ -108,6 +114,9 @@ describe('POST cases', () => { status: CaseStatuses.open, tags: ['defacement'], connector: null, + settings: { + syncAlerts: true, + }, }, }); @@ -130,6 +139,9 @@ describe('POST cases', () => { title: 'Super Bad Security Issue', tags: ['error'], connector: null, + settings: { + syncAlerts: true, + }, }, }); @@ -160,6 +172,9 @@ describe('POST cases', () => { type: ConnectorTypes.none, fields: null, }, + settings: { + syncAlerts: true, + }, }, }); @@ -199,6 +214,9 @@ describe('POST cases', () => { updated_at: null, updated_by: null, version: 'WzksMV0=', + settings: { + syncAlerts: true, + }, }); }); }); diff --git a/x-pack/plugins/case/server/routes/api/utils.test.ts b/x-pack/plugins/case/server/routes/api/utils.test.ts index 7654ae5ff0d1a..405da0df17542 100644 --- a/x-pack/plugins/case/server/routes/api/utils.test.ts +++ b/x-pack/plugins/case/server/routes/api/utils.test.ts @@ -302,6 +302,9 @@ describe('Utils', () => { comments: [], totalComment: 2, version: 'WzAsMV0=', + settings: { + syncAlerts: true, + }, }, ]); }); @@ -341,6 +344,9 @@ describe('Utils', () => { comments: [], totalComment: 0, version: 'WzAsMV0=', + settings: { + syncAlerts: true, + }, }, ]); }); @@ -387,6 +393,9 @@ describe('Utils', () => { comments: [], totalComment: 0, version: 'WzAsMV0=', + settings: { + syncAlerts: true, + }, }, ]); }); @@ -497,6 +506,9 @@ describe('Utils', () => { comments: [], totalComment: 2, version: 'WzAsMV0=', + settings: { + syncAlerts: true, + }, }); }); }); diff --git a/x-pack/plugins/case/server/saved_object_types/cases.ts b/x-pack/plugins/case/server/saved_object_types/cases.ts index d8ee2f90f3d93..6468d4b3aa61d 100644 --- a/x-pack/plugins/case/server/saved_object_types/cases.ts +++ b/x-pack/plugins/case/server/saved_object_types/cases.ts @@ -134,6 +134,13 @@ export const caseSavedObjectType: SavedObjectsType = { }, }, }, + settings: { + properties: { + syncAlerts: { + type: 'boolean', + }, + }, + }, }, }, migrations: caseMigrations, diff --git a/x-pack/plugins/case/server/saved_object_types/migrations.ts b/x-pack/plugins/case/server/saved_object_types/migrations.ts index 27c363a40af37..9124314ac3f5e 100644 --- a/x-pack/plugins/case/server/saved_object_types/migrations.ts +++ b/x-pack/plugins/case/server/saved_object_types/migrations.ts @@ -9,16 +9,16 @@ import { SavedObjectUnsanitizedDoc, SavedObjectSanitizedDoc } from '../../../../../src/core/server'; import { ConnectorTypes, CommentType } from '../../common/api'; -interface UnsanitizedCase { +interface UnsanitizedCaseConnector { connector_id: string; } -interface UnsanitizedConfigure { +interface UnsanitizedConfigureConnector { connector_id: string; connector_name: string; } -interface SanitizedCase { +interface SanitizedCaseConnector { connector: { id: string; name: string | null; @@ -27,7 +27,7 @@ interface SanitizedCase { }; } -interface SanitizedConfigure { +interface SanitizedConfigureConnector { connector: { id: string; name: string | null; @@ -42,10 +42,16 @@ interface UserActions { old_value: string; } +interface SanitizedCaseSettings { + settings: { + syncAlerts: boolean; + }; +} + export const caseMigrations = { '7.10.0': ( - doc: SavedObjectUnsanitizedDoc - ): SavedObjectSanitizedDoc => { + doc: SavedObjectUnsanitizedDoc + ): SavedObjectSanitizedDoc => { const { connector_id, ...attributesWithoutConnectorId } = doc.attributes; return { @@ -62,12 +68,26 @@ export const caseMigrations = { references: doc.references || [], }; }, + '7.11.0': ( + doc: SavedObjectUnsanitizedDoc> + ): SavedObjectSanitizedDoc => { + return { + ...doc, + attributes: { + ...doc.attributes, + settings: { + syncAlerts: true, + }, + }, + references: doc.references || [], + }; + }, }; export const configureMigrations = { '7.10.0': ( - doc: SavedObjectUnsanitizedDoc - ): SavedObjectSanitizedDoc => { + doc: SavedObjectUnsanitizedDoc + ): SavedObjectSanitizedDoc => { const { connector_id, connector_name, ...restAttributes } = doc.attributes; return { diff --git a/x-pack/plugins/case/server/services/alerts/index.ts b/x-pack/plugins/case/server/services/alerts/index.ts new file mode 100644 index 0000000000000..4fb98278b8afa --- /dev/null +++ b/x-pack/plugins/case/server/services/alerts/index.ts @@ -0,0 +1,57 @@ +/* + * 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 type { PublicMethodsOf } from '@kbn/utility-types'; + +import { IClusterClient, KibanaRequest } from 'kibana/server'; +import { CaseStatuses } from '../../../common/api'; + +export type AlertServiceContract = PublicMethodsOf; + +interface UpdateAlertsStatusArgs { + request: KibanaRequest; + ids: string[]; + status: CaseStatuses; + index: string; +} + +export class AlertService { + private isInitialized = false; + private esClient?: IClusterClient; + + constructor() {} + + public initialize(esClient: IClusterClient) { + if (this.isInitialized) { + throw new Error('AlertService already initialized'); + } + + this.isInitialized = true; + this.esClient = esClient; + } + + public async updateAlertsStatus({ request, ids, status, index }: UpdateAlertsStatusArgs) { + if (!this.isInitialized) { + throw new Error('AlertService not initialized'); + } + + // The above check makes sure that esClient is defined. + const result = await this.esClient!.asScoped(request).asCurrentUser.updateByQuery({ + index, + conflicts: 'abort', + body: { + script: { + source: `ctx._source.signal.status = '${status}'`, + lang: 'painless', + }, + query: { ids: { values: ids } }, + }, + ignore_unavailable: true, + }); + + return result; + } +} diff --git a/x-pack/plugins/case/server/services/index.ts b/x-pack/plugins/case/server/services/index.ts index 0ce2b196af471..95bcf87361e07 100644 --- a/x-pack/plugins/case/server/services/index.ts +++ b/x-pack/plugins/case/server/services/index.ts @@ -31,6 +31,7 @@ import { readTags } from './tags/read_tags'; export { CaseConfigureService, CaseConfigureServiceSetup } from './configure'; export { CaseUserActionService, CaseUserActionServiceSetup } from './user_actions'; +export { AlertService, AlertServiceContract } from './alerts'; export interface ClientArgs { client: SavedObjectsClientContract; diff --git a/x-pack/plugins/case/server/services/mocks.ts b/x-pack/plugins/case/server/services/mocks.ts index 287f80a60ab07..01a8cb09ac2d5 100644 --- a/x-pack/plugins/case/server/services/mocks.ts +++ b/x-pack/plugins/case/server/services/mocks.ts @@ -4,11 +4,17 @@ * you may not use this file except in compliance with the Elastic License. */ -import { CaseConfigureServiceSetup, CaseServiceSetup, CaseUserActionServiceSetup } from '.'; +import { + CaseConfigureServiceSetup, + CaseServiceSetup, + CaseUserActionServiceSetup, + AlertServiceContract, +} from '.'; export type CaseServiceMock = jest.Mocked; export type CaseConfigureServiceMock = jest.Mocked; export type CaseUserActionServiceMock = jest.Mocked; +export type AlertServiceMock = jest.Mocked; export const createCaseServiceMock = (): CaseServiceMock => ({ deleteCase: jest.fn(), @@ -41,3 +47,8 @@ export const createUserActionServiceMock = (): CaseUserActionServiceMock => ({ getUserActions: jest.fn(), postUserActions: jest.fn(), }); + +export const createAlertServiceMock = (): AlertServiceMock => ({ + initialize: jest.fn(), + updateAlertsStatus: jest.fn(), +}); diff --git a/x-pack/plugins/case/server/services/user_actions/helpers.ts b/x-pack/plugins/case/server/services/user_actions/helpers.ts index c9339862b8f24..c7bdc8b10b5a3 100644 --- a/x-pack/plugins/case/server/services/user_actions/helpers.ts +++ b/x-pack/plugins/case/server/services/user_actions/helpers.ts @@ -129,6 +129,7 @@ const userActionFieldsAllowed: UserActionField = [ 'tags', 'title', 'status', + 'settings', ]; export const buildCaseUserActions = ({ diff --git a/x-pack/plugins/case/server/types.ts b/x-pack/plugins/case/server/types.ts index b95060ef30452..d0dfc26aa7b8c 100644 --- a/x-pack/plugins/case/server/types.ts +++ b/x-pack/plugins/case/server/types.ts @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { AppRequestContext } from '../../security_solution/server/types'; import { CaseClient } from './client'; export interface CaseRequestContext { @@ -13,5 +15,8 @@ export interface CaseRequestContext { declare module 'src/core/server' { interface RequestHandlerContext { case?: CaseRequestContext; + // TODO: Remove when triggers_ui do not import case's types. + // PR https://github.com/elastic/kibana/pull/84587. + securitySolution?: AppRequestContext; } } diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/index.test.tsx b/x-pack/plugins/security_solution/public/cases/components/all_cases/index.test.tsx index 755dde9341dca..78bb3a8d2f2f3 100644 --- a/x-pack/plugins/security_solution/public/cases/components/all_cases/index.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/all_cases/index.test.tsx @@ -474,6 +474,9 @@ describe('AllCases', () => { username: 'lknope', }, version: 'WzQ3LDFd', + settings: { + syncAlerts: true, + }, }); }); }); diff --git a/x-pack/plugins/security_solution/public/cases/components/case_action_bar/index.tsx b/x-pack/plugins/security_solution/public/cases/components/case_action_bar/index.tsx index 945458e92bc8a..62ce0cc2cc2f5 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_action_bar/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/case_action_bar/index.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useMemo } from 'react'; +import React, { useMemo, useCallback } from 'react'; import styled, { css } from 'styled-components'; import { EuiButtonEmpty, @@ -13,6 +13,7 @@ import { EuiDescriptionListTitle, EuiFlexGroup, EuiFlexItem, + EuiIconTip, } from '@elastic/eui'; import { CaseStatuses } from '../../../../../case/common/api'; import * as i18n from '../case_view/translations'; @@ -22,6 +23,8 @@ import { Case } from '../../containers/types'; import { CaseService } from '../../containers/use_get_case_user_actions'; import { StatusContextMenu } from './status_context_menu'; import { getStatusDate, getStatusTitle } from './helpers'; +import { SyncAlertsSwitch } from '../case_settings/sync_alerts_switch'; +import { OnUpdateFields } from '../case_view'; const MyDescriptionList = styled(EuiDescriptionList)` ${({ theme }) => css` @@ -38,7 +41,7 @@ interface CaseActionBarProps { disabled?: boolean; isLoading: boolean; onRefresh: () => void; - onStatusChanged: (status: CaseStatuses) => void; + onUpdateField: (args: OnUpdateFields) => void; } const CaseActionBarComponent: React.FC = ({ caseData, @@ -46,10 +49,27 @@ const CaseActionBarComponent: React.FC = ({ disabled = false, isLoading, onRefresh, - onStatusChanged, + onUpdateField, }) => { const date = useMemo(() => getStatusDate(caseData), [caseData]); const title = useMemo(() => getStatusTitle(caseData.status), [caseData.status]); + const onStatusChanged = useCallback( + (status: CaseStatuses) => + onUpdateField({ + key: 'status', + value: status, + }), + [onUpdateField] + ); + + const onSyncAlertsChanged = useCallback( + (syncAlerts: boolean) => + onUpdateField({ + key: 'settings', + value: { ...caseData.settings, syncAlerts }, + }), + [caseData.settings, onUpdateField] + ); return ( @@ -78,20 +98,41 @@ const CaseActionBarComponent: React.FC = ({ - - - - {i18n.CASE_REFRESH} - - - - - - + + + + + + + + {i18n.STATUS} + + + + + + + + + + + + {i18n.CASE_REFRESH} + + + + + + + ); diff --git a/x-pack/plugins/security_solution/public/cases/components/case_settings/sync_alerts_switch.tsx b/x-pack/plugins/security_solution/public/cases/components/case_settings/sync_alerts_switch.tsx new file mode 100644 index 0000000000000..ab91f2ae8cdf3 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/case_settings/sync_alerts_switch.tsx @@ -0,0 +1,48 @@ +/* + * 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, { memo, useCallback, useState } from 'react'; +import { EuiSwitch } from '@elastic/eui'; + +import * as i18n from '../../translations'; + +interface Props { + disabled: boolean; + isSynced?: boolean; + showLabel?: boolean; + onSwitchChange?: (isSynced: boolean) => void; +} + +const SyncAlertsSwitchComponent: React.FC = ({ + disabled, + isSynced = true, + showLabel = false, + onSwitchChange, +}) => { + const [isOn, setIsOn] = useState(isSynced); + + const onChange = useCallback(() => { + if (onSwitchChange) { + onSwitchChange(!isOn); + } + + setIsOn(!isOn); + }, [isOn, onSwitchChange]); + + return ( + + ); +}; + +SyncAlertsSwitchComponent.displayName = 'SyncAlertsSwitchComponent'; + +export const SyncAlertsSwitch = memo(SyncAlertsSwitchComponent); diff --git a/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx b/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx index 0e6226f69fce7..6007038b33ab7 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx @@ -16,7 +16,7 @@ import { EuiHorizontalRule, } from '@elastic/eui'; -import { CaseStatuses } from '../../../../../case/common/api'; +import { CaseStatuses, CaseAttributes } from '../../../../../case/common/api'; import { Case, CaseConnector } from '../../containers/types'; import { getCaseDetailsUrl, getCaseUrl, useFormatUrl } from '../../../common/components/link_to'; import { gutterTimeline } from '../../../common/lib/helpers'; @@ -234,6 +234,21 @@ export const CaseComponent = React.memo( onError, }); } + break; + case 'settings': + const settingsUpdate = getTypedPayload(value); + if (caseData.settings !== value) { + updateCaseProperty({ + fetchCaseUserActions, + updateKey: 'settings', + updateValue: settingsUpdate, + updateCase: handleUpdateNewCase, + version: caseData.version, + onSuccess, + onError, + }); + } + break; default: return null; } @@ -397,9 +412,9 @@ export const CaseComponent = React.memo( currentExternalIncident={currentExternalIncident} caseData={caseData} disabled={!userCanCrud} - isLoading={isLoading && updateKey === 'status'} + isLoading={isLoading && (updateKey === 'status' || updateKey === 'settings')} onRefresh={handleRefresh} - onStatusChanged={changeStatus} + onUpdateField={onUpdateField} /> diff --git a/x-pack/plugins/security_solution/public/cases/components/create/connector.tsx b/x-pack/plugins/security_solution/public/cases/components/create/connector.tsx index b2a0f3c351552..67c536f652ec1 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/connector.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/create/connector.tsx @@ -7,13 +7,13 @@ import React, { memo, useEffect } from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { ConnectorTypeFields } from '../../../../../case/common/api/connectors'; import { UseField, useFormData, FieldHook } from '../../../shared_imports'; import { useConnectors } from '../../containers/configure/use_connectors'; import { ConnectorSelector } from '../connector_selector/form'; import { SettingFieldsForm } from '../settings/fields_form'; import { ActionConnector } from '../../containers/types'; import { getConnectorById } from '../configure_cases/utils'; +import { FormProps } from './schema'; interface Props { isLoading: boolean; @@ -21,7 +21,7 @@ interface Props { interface SettingsFieldProps { connectors: ActionConnector[]; - field: FieldHook; + field: FieldHook; isEdit: boolean; } diff --git a/x-pack/plugins/security_solution/public/cases/components/create/form.test.tsx b/x-pack/plugins/security_solution/public/cases/components/create/form.test.tsx index e64b2b3a05080..3091e6b33d333 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/form.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/create/form.test.tsx @@ -25,6 +25,7 @@ const initialCaseValue: FormProps = { title: '', connectorId: 'none', fields: null, + syncAlerts: true, }; describe('CreateCaseForm', () => { diff --git a/x-pack/plugins/security_solution/public/cases/components/create/form.tsx b/x-pack/plugins/security_solution/public/cases/components/create/form.tsx index 40db4d792c1c8..308dc63916934 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/form.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/create/form.tsx @@ -15,6 +15,7 @@ import { Description } from './description'; import { Tags } from './tags'; import { Connector } from './connector'; import * as i18n from './translations'; +import { SyncAlertsToggle } from './sync_alerts_toggle'; interface ContainerProps { big?: boolean; @@ -61,6 +62,18 @@ export const CreateCaseForm: React.FC = React.memo(({ withSteps = true }) const secondStep = useMemo( () => ({ title: i18n.STEP_TWO_TITLE, + children: ( + + + + ), + }), + [isSubmitting] + ); + + const thirdStep = useMemo( + () => ({ + title: i18n.STEP_THREE_TITLE, children: ( @@ -70,7 +83,11 @@ export const CreateCaseForm: React.FC = React.memo(({ withSteps = true }) [isSubmitting] ); - const allSteps = useMemo(() => [firstStep, secondStep], [firstStep, secondStep]); + const allSteps = useMemo(() => [firstStep, secondStep, thirdStep], [ + firstStep, + secondStep, + thirdStep, + ]); return ( <> @@ -85,6 +102,7 @@ export const CreateCaseForm: React.FC = React.memo(({ withSteps = true }) <> {firstStep.children} {secondStep.children} + {thirdStep.children} )} diff --git a/x-pack/plugins/security_solution/public/cases/components/create/form_context.tsx b/x-pack/plugins/security_solution/public/cases/components/create/form_context.tsx index e11e508b60ebf..4575059a5a6c0 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/form_context.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/create/form_context.tsx @@ -23,6 +23,7 @@ const initialCaseValue: FormProps = { title: '', connectorId: 'none', fields: null, + syncAlerts: true, }; interface Props { @@ -34,14 +35,21 @@ export const FormContext: React.FC = ({ children, onSuccess }) => { const { caseData, postCase } = usePostCase(); const submitCase = useCallback( - async ({ connectorId: dataConnectorId, fields, ...dataWithoutConnectorId }, isValid) => { + async ( + { connectorId: dataConnectorId, fields, syncAlerts, ...dataWithoutConnectorId }, + isValid + ) => { if (isValid) { const caseConnector = getConnectorById(dataConnectorId, connectors); const connectorToUpdate = caseConnector ? normalizeActionConnector(caseConnector, fields) : getNoneConnector(); - await postCase({ ...dataWithoutConnectorId, connector: connectorToUpdate }); + await postCase({ + ...dataWithoutConnectorId, + connector: connectorToUpdate, + settings: { syncAlerts }, + }); } }, [postCase, connectors] diff --git a/x-pack/plugins/security_solution/public/cases/components/create/index.test.tsx b/x-pack/plugins/security_solution/public/cases/components/create/index.test.tsx index 29073e7774158..fe5b3bea6445c 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/index.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/create/index.test.tsx @@ -8,8 +8,9 @@ import React from 'react'; import { mount, ReactWrapper } from 'enzyme'; import { act, waitFor } from '@testing-library/react'; import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui'; -import { TestProviders } from '../../../common/mock'; +import { CasePostRequest } from '../../../../../case/common/api'; +import { TestProviders } from '../../../common/mock'; import { usePostCase } from '../../containers/use_post_case'; import { useGetTags } from '../../containers/use_get_tags'; import { useConnectors } from '../../containers/configure/use_connectors'; @@ -41,7 +42,7 @@ const useGetFieldsByIssueTypeMock = useGetFieldsByIssueType as jest.Mock; const postCase = jest.fn(); const sampleTags = ['coke', 'pepsi']; -const sampleData = { +const sampleData: CasePostRequest = { description: 'what a great description', tags: sampleTags, title: 'what a cool title', @@ -51,6 +52,9 @@ const sampleData = { name: 'none', type: ConnectorTypes.none, }, + settings: { + syncAlerts: true, + }, }; const defaultPostCase = { diff --git a/x-pack/plugins/security_solution/public/cases/components/create/schema.tsx b/x-pack/plugins/security_solution/public/cases/components/create/schema.tsx index a336860121c94..34f0bdd051483 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/schema.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/create/schema.tsx @@ -6,7 +6,7 @@ import { CasePostRequest, ConnectorTypeFields } from '../../../../../case/common/api'; import { FIELD_TYPES, fieldValidators, FormSchema } from '../../../shared_imports'; -import * as i18n from '../../translations'; +import * as i18n from './translations'; import { OptionalFieldLabel } from './optional_field_label'; const { emptyField } = fieldValidators; @@ -18,9 +18,10 @@ export const schemaTags = { labelAppend: OptionalFieldLabel, }; -export type FormProps = Omit & { +export type FormProps = Omit & { connectorId: string; fields: ConnectorTypeFields['fields']; + syncAlerts: boolean; }; export const schema: FormSchema = { @@ -47,4 +48,10 @@ export const schema: FormSchema = { label: i18n.CONNECTORS, defaultValue: 'none', }, + fields: {}, + syncAlerts: { + helpText: i18n.SYNC_ALERTS_HELP, + type: FIELD_TYPES.TOGGLE, + defaultValue: true, + }, }; diff --git a/x-pack/plugins/security_solution/public/cases/components/create/sync_alerts_toggle.tsx b/x-pack/plugins/security_solution/public/cases/components/create/sync_alerts_toggle.tsx new file mode 100644 index 0000000000000..0abb2974dd2cb --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/create/sync_alerts_toggle.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, { memo } from 'react'; +import { Field, getUseField, useFormData } from '../../../shared_imports'; +import * as i18n from './translations'; + +const CommonUseField = getUseField({ component: Field }); + +interface Props { + isLoading: boolean; +} + +const SyncAlertsToggleComponent: React.FC = ({ isLoading }) => { + const [{ syncAlerts }] = useFormData({ watch: ['syncAlerts'] }); + return ( + + ); +}; + +SyncAlertsToggleComponent.displayName = 'SyncAlertsToggleComponent'; + +export const SyncAlertsToggle = memo(SyncAlertsToggleComponent); diff --git a/x-pack/plugins/security_solution/public/cases/components/create/translations.ts b/x-pack/plugins/security_solution/public/cases/components/create/translations.ts index 38916dbddc7d7..f892e080af782 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/translations.ts +++ b/x-pack/plugins/security_solution/public/cases/components/create/translations.ts @@ -17,7 +17,21 @@ export const STEP_ONE_TITLE = i18n.translate( export const STEP_TWO_TITLE = i18n.translate( 'xpack.securitySolution.components.create.stepTwoTitle', + { + defaultMessage: 'Case settings', + } +); + +export const STEP_THREE_TITLE = i18n.translate( + 'xpack.securitySolution.components.create.stepThreeTitle', { defaultMessage: 'External Connector Fields', } ); + +export const SYNC_ALERTS_LABEL = i18n.translate( + 'xpack.securitySolution.components.create.syncAlertsLabel', + { + defaultMessage: 'Sync alert status with case status', + } +); diff --git a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_alert_comment_event.tsx b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_alert_comment_event.tsx index 148ad275b756e..be437073e693c 100644 --- a/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_alert_comment_event.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/user_action_tree/user_action_alert_comment_event.tsx @@ -9,7 +9,7 @@ import { EuiLink } from '@elastic/eui'; import { APP_ID } from '../../../../common/constants'; import { useKibana } from '../../../common/lib/kibana'; -import { getRuleDetailsUrl, useFormatUrl } from '../../../common/components/link_to'; +import { getRuleDetailsUrl } from '../../../common/components/link_to'; import { SecurityPageName } from '../../../app/types'; import { Alert } from '../case_view'; @@ -23,16 +23,15 @@ const AlertCommentEventComponent: React.FC = ({ alert }) => { const ruleName = alert?.rule?.name ?? null; const ruleId = alert?.rule?.id ?? null; const { navigateToApp } = useKibana().services.application; - const { formatUrl } = useFormatUrl(SecurityPageName.detections); const onLinkClick = useCallback( (ev: { preventDefault: () => void }) => { ev.preventDefault(); navigateToApp(`${APP_ID}:${SecurityPageName.detections}`, { - path: formatUrl(getRuleDetailsUrl(ruleId ?? '')), + path: getRuleDetailsUrl(ruleId ?? ''), }); }, - [ruleId, formatUrl, navigateToApp] + [ruleId, navigateToApp] ); return ruleId != null && ruleName != null ? ( diff --git a/x-pack/plugins/security_solution/public/cases/containers/api.test.tsx b/x-pack/plugins/security_solution/public/cases/containers/api.test.tsx index f60993fc9aa02..bec1ab3dd4292 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/api.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/containers/api.test.tsx @@ -384,6 +384,9 @@ describe('Case Configuration API', () => { type: ConnectorTypes.none, fields: null, }, + settings: { + syncAlerts: true, + }, }; test('check url, method, signal', async () => { diff --git a/x-pack/plugins/security_solution/public/cases/containers/mock.ts b/x-pack/plugins/security_solution/public/cases/containers/mock.ts index 40312a8713783..f94fb189c90ce 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/mock.ts +++ b/x-pack/plugins/security_solution/public/cases/containers/mock.ts @@ -76,6 +76,9 @@ export const basicCase: Case = { updatedAt: basicUpdatedAt, updatedBy: elasticUser, version: 'WzQ3LDFd', + settings: { + syncAlerts: true, + }, }; export const basicCasePost: Case = { diff --git a/x-pack/plugins/security_solution/public/cases/containers/types.ts b/x-pack/plugins/security_solution/public/cases/containers/types.ts index ec1eaa939fe31..a5c9c65dab62a 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/types.ts +++ b/x-pack/plugins/security_solution/public/cases/containers/types.ts @@ -11,6 +11,7 @@ import { CaseConnector, CommentRequest, CaseStatuses, + CaseAttributes, } from '../../../../case/common/api'; export { CaseConnector, ActionConnector } from '../../../../case/common/api'; @@ -63,6 +64,7 @@ export interface Case { updatedAt: string | null; updatedBy: ElasticUser | null; version: string; + settings: CaseAttributes['settings']; } export interface QueryParams { diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_get_case.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_get_case.tsx index 44166a14ad292..060ed787c7f4e 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_get_case.tsx +++ b/x-pack/plugins/security_solution/public/cases/containers/use_get_case.tsx @@ -74,6 +74,9 @@ export const initialData: Case = { updatedAt: null, updatedBy: null, version: '', + settings: { + syncAlerts: true, + }, }; export interface UseGetCase extends CaseState { diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_post_case.test.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_post_case.test.tsx index c4363236a0977..8e8432d0d190c 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_post_case.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/containers/use_post_case.test.tsx @@ -24,6 +24,9 @@ describe('usePostCase', () => { type: ConnectorTypes.none, fields: null, }, + settings: { + syncAlerts: true, + }, }; beforeEach(() => { jest.clearAllMocks(); diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_update_case.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_update_case.tsx index c305399ee02d0..08333416d3c46 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/use_update_case.tsx +++ b/x-pack/plugins/security_solution/public/cases/containers/use_update_case.tsx @@ -19,7 +19,7 @@ import { Case } from './types'; export type UpdateKey = keyof Pick< CasePatchRequest, - 'connector' | 'description' | 'status' | 'tags' | 'title' + 'connector' | 'description' | 'status' | 'tags' | 'title' | 'settings' >; interface NewCaseState { diff --git a/x-pack/plugins/security_solution/public/cases/translations.ts b/x-pack/plugins/security_solution/public/cases/translations.ts index a79f7a3af18bf..fd217457f9e7d 100644 --- a/x-pack/plugins/security_solution/public/cases/translations.ts +++ b/x-pack/plugins/security_solution/public/cases/translations.ts @@ -256,3 +256,25 @@ export const IN_PROGRESS_CASES = i18n.translate( defaultMessage: 'In progress cases', } ); + +export const SYNC_ALERTS_SWITCH_LABEL_ON = i18n.translate( + 'xpack.securitySolution.case.settings.syncAlertsSwitchLabelOn', + { + defaultMessage: 'On', + } +); + +export const SYNC_ALERTS_SWITCH_LABEL_OFF = i18n.translate( + 'xpack.securitySolution.case.settings.syncAlertsSwitchLabelOff', + { + defaultMessage: 'Off', + } +); + +export const SYNC_ALERTS_HELP = i18n.translate( + 'xpack.securitySolution.components.create.syncAlertHelpText', + { + defaultMessage: + 'Enabling this option will sync the status of alerts in this case with the case status.', + } +); diff --git a/x-pack/plugins/security_solution/server/index.ts b/x-pack/plugins/security_solution/server/index.ts index 7b84c531dd376..94764fd159360 100644 --- a/x-pack/plugins/security_solution/server/index.ts +++ b/x-pack/plugins/security_solution/server/index.ts @@ -8,6 +8,7 @@ import { PluginInitializerContext, PluginConfigDescriptor } from '../../../../sr import { Plugin, PluginSetup, PluginStart } from './plugin'; import { configSchema, ConfigType } from './config'; import { SIGNALS_INDEX_KEY } from '../common/constants'; +import { AppClient } from './types'; export const plugin = (context: PluginInitializerContext) => { return new Plugin(context); @@ -41,6 +42,7 @@ export const config: PluginConfigDescriptor = { }; export { ConfigType, Plugin, PluginSetup, PluginStart }; +export { AppClient }; // Exports to be shared with plugins such as x-pack/lists plugin export { deleteTemplate } from './lib/detection_engine/index/delete_template'; diff --git a/x-pack/test/case_api_integration/basic/tests/cases/migrations.ts b/x-pack/test/case_api_integration/basic/tests/cases/migrations.ts index 36f07ef92b5f1..df200b34dc429 100644 --- a/x-pack/test/case_api_integration/basic/tests/cases/migrations.ts +++ b/x-pack/test/case_api_integration/basic/tests/cases/migrations.ts @@ -38,5 +38,18 @@ export default function createGetTests({ getService }: FtrProviderContext) { fields: null, }); }); + + it('7.11.0 migrates cases settings', async () => { + const { body } = await supertest + .get(`${CASES_URL}/e1900ac0-017f-11eb-93f8-d161651bf509`) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + expect(body).key('settings'); + expect(body.settings).to.eql({ + syncAlerts: true, + }); + }); }); } diff --git a/x-pack/test/case_api_integration/basic/tests/cases/user_actions/get_all_user_actions.ts b/x-pack/test/case_api_integration/basic/tests/cases/user_actions/get_all_user_actions.ts index 6949052df4703..ec79c8a1ca494 100644 --- a/x-pack/test/case_api_integration/basic/tests/cases/user_actions/get_all_user_actions.ts +++ b/x-pack/test/case_api_integration/basic/tests/cases/user_actions/get_all_user_actions.ts @@ -36,7 +36,7 @@ export default ({ getService }: FtrProviderContext): void => { await actionsRemover.removeAll(); }); - it(`on new case, user action: 'create' should be called with actionFields: ['description', 'status', 'tags', 'title', 'connector']`, async () => { + it(`on new case, user action: 'create' should be called with actionFields: ['description', 'status', 'tags', 'title', 'connector', 'settings]`, async () => { const { body: postedCase } = await supertest .post(CASES_URL) .set('kbn-xsrf', 'true') @@ -51,7 +51,14 @@ export default ({ getService }: FtrProviderContext): void => { expect(body.length).to.eql(1); - expect(body[0].action_field).to.eql(['description', 'status', 'tags', 'title', 'connector']); + expect(body[0].action_field).to.eql([ + 'description', + 'status', + 'tags', + 'title', + 'connector', + 'settings', + ]); expect(body[0].action).to.eql('create'); expect(body[0].old_value).to.eql(null); expect(body[0].new_value).to.eql(JSON.stringify(postCaseReq)); diff --git a/x-pack/test/case_api_integration/basic/tests/connectors/case.ts b/x-pack/test/case_api_integration/basic/tests/connectors/case.ts index 9a45dd541bb56..e0812d01d0fb8 100644 --- a/x-pack/test/case_api_integration/basic/tests/connectors/case.ts +++ b/x-pack/test/case_api_integration/basic/tests/connectors/case.ts @@ -391,6 +391,9 @@ export default ({ getService }: FtrProviderContext): void => { parent: null, }, }, + settings: { + syncAlerts: true, + }, }, }; @@ -442,6 +445,9 @@ export default ({ getService }: FtrProviderContext): void => { type: '.servicenow', fields: {}, }, + settings: { + syncAlerts: true, + }, }, }; @@ -673,7 +679,53 @@ export default ({ getService }: FtrProviderContext): void => { }); }); - it('should respond with a 400 Bad Request when missing attributes of type alert', async () => { + // TODO: Remove it when the creation of comments of type alert is supported + // https://github.com/elastic/kibana/issues/85750 + it('should fail adding a comment of type alert', async () => { + const { body: createdAction } = await supertest + .post('/api/actions/action') + .set('kbn-xsrf', 'foo') + .send({ + name: 'A case connector', + actionTypeId: '.case', + config: {}, + }) + .expect(200); + + createdActionId = createdAction.id; + + const caseRes = await supertest + .post(CASES_URL) + .set('kbn-xsrf', 'true') + .send(postCaseReq) + .expect(200); + + const params = { + subAction: 'addComment', + subActionParams: { + caseId: caseRes.body.id, + comment: { alertId: 'test-id', index: 'test-index', type: CommentType.alert }, + }, + }; + + const caseConnector = await supertest + .post(`/api/actions/action/${createdActionId}/_execute`) + .set('kbn-xsrf', 'foo') + .send({ params }) + .expect(200); + + expect(caseConnector.body).to.eql({ + status: 'error', + actionId: createdActionId, + message: + 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [create]\n- [1.subAction]: expected value to equal [update]\n- [2.subActionParams.comment]: types that failed validation:\n - [subActionParams.comment.0.type]: expected value to equal [user]', + retry: false, + }); + }); + + // TODO: Enable when the creation of comments of type alert is supported + // https://github.com/elastic/kibana/issues/85750 + it.skip('should respond with a 400 Bad Request when missing attributes of type alert', async () => { const { body: createdAction } = await supertest .post('/api/actions/action') .set('kbn-xsrf', 'foo') @@ -754,13 +806,15 @@ export default ({ getService }: FtrProviderContext): void => { expect(caseConnector.body).to.eql({ status: 'error', actionId: createdActionId, - message: `error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [create]\n- [1.subAction]: expected value to equal [update]\n- [2.subActionParams.comment]: types that failed validation:\n - [subActionParams.comment.0.${attribute}]: definition for this key is missing\n - [subActionParams.comment.1.type]: expected value to equal [alert]`, + message: `error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [create]\n- [1.subAction]: expected value to equal [update]\n- [2.subActionParams.comment]: types that failed validation:\n - [subActionParams.comment.0.${attribute}]: definition for this key is missing`, retry: false, }); } }); - it('should respond with a 400 Bad Request when adding excess attributes for type alert', async () => { + // TODO: Enable when the creation of comments of type alert is supported + // https://github.com/elastic/kibana/issues/85750 + it.skip('should respond with a 400 Bad Request when adding excess attributes for type alert', async () => { const { body: createdAction } = await supertest .post('/api/actions/action') .set('kbn-xsrf', 'foo') @@ -892,7 +946,9 @@ export default ({ getService }: FtrProviderContext): void => { }); }); - it('should add a comment of type alert', async () => { + // TODO: Enable when the creation of comments of type alert is supported + // https://github.com/elastic/kibana/issues/85750 + it.skip('should add a comment of type alert', async () => { const { body: createdAction } = await supertest .post('/api/actions/action') .set('kbn-xsrf', 'foo') diff --git a/x-pack/test/case_api_integration/common/lib/mock.ts b/x-pack/test/case_api_integration/common/lib/mock.ts index dac6b2005a9c3..012af6b37f842 100644 --- a/x-pack/test/case_api_integration/common/lib/mock.ts +++ b/x-pack/test/case_api_integration/common/lib/mock.ts @@ -26,6 +26,9 @@ export const postCaseReq: CasePostRequest = { type: '.none' as ConnectorTypes, fields: null, }, + settings: { + syncAlerts: true, + }, }; export const postCommentUserReq: CommentRequestUserType = { From 80ca5a5836e537c2695231b38249a4499d80be5b Mon Sep 17 00:00:00 2001 From: Candace Park <56409205+parkiino@users.noreply.github.com> Date: Mon, 14 Dec 2020 14:26:51 -0500 Subject: [PATCH 38/95] [Security Solution][Endpoint][Admin] Custom malware user notification message allows spaces now (#85207) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../policy/store/policy_details/selectors.ts | 38 ++++++++++++++++++- .../view/policy_forms/protections/malware.tsx | 2 +- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/selectors.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/selectors.ts index 7088f094ddcb4..77e975a46d37b 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/selectors.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/selectors.ts @@ -31,7 +31,43 @@ export const getPolicyDataForUpdate = ( ): NewPolicyData | Immutable => { // eslint-disable-next-line @typescript-eslint/naming-convention const { id, revision, created_by, created_at, updated_by, updated_at, ...newPolicy } = policy; - return newPolicy; + + // trim custom malware notification string + return { + ...newPolicy, + inputs: (newPolicy as Immutable).inputs.map((input) => ({ + ...input, + config: input.config && { + ...input.config, + policy: { + ...input.config.policy, + value: { + ...input.config.policy.value, + windows: { + ...input.config.policy.value.windows, + popup: { + ...input.config.policy.value.windows.popup, + malware: { + ...input.config.policy.value.windows.popup.malware, + message: input.config.policy.value.windows.popup.malware.message.trim(), + }, + }, + }, + mac: { + ...input.config.policy.value.mac, + popup: { + ...input.config.policy.value.mac.popup, + malware: { + ...input.config.policy.value.mac.popup.malware, + message: input.config.policy.value.mac.popup.malware.message.trim(), + }, + }, + }, + }, + }, + }, + })), + }; }; /** diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/protections/malware.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/protections/malware.tsx index c78455aa8d990..330a0ba407453 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/protections/malware.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/protections/malware.tsx @@ -193,7 +193,7 @@ export const MalwareProtections = React.memo(() => { if (policyDetailsConfig) { const newPayload = cloneDeep(policyDetailsConfig); for (const os of OSes) { - newPayload[os].popup[protection].message = event.target.value.trim(); + newPayload[os].popup[protection].message = event.target.value; } dispatch({ type: 'userChangedPolicyConfig', From 8279c2d1a2979d13cf2a9f5455327287d1b1a0b7 Mon Sep 17 00:00:00 2001 From: Candace Park <56409205+parkiino@users.noreply.github.com> Date: Mon, 14 Dec 2020 14:27:58 -0500 Subject: [PATCH 39/95] [Security Solution][Endpoint][Admin] Adds instructional tooltip for malware custom user notification (#85651) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../pages/policy/view/policy_details.test.tsx | 8 +++- .../view/policy_forms/protections/malware.tsx | 40 +++++++++++++++---- 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.test.tsx index bfa592b1f9c8e..e9c13b23834b1 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.test.tsx @@ -293,7 +293,7 @@ describe('Policy Details', () => { policyView = render(); }); - it('malware popup and message customization options are shown', () => { + it('malware popup, message customization options and tooltip are shown', () => { // use query for finding stuff, if it doesn't find it, just returns null const userNotificationCheckbox = policyView.find( 'EuiCheckbox[data-test-subj="malwareUserNotificationCheckbox"]' @@ -301,8 +301,10 @@ describe('Policy Details', () => { const userNotificationCustomMessageTextArea = policyView.find( 'EuiTextArea[data-test-subj="malwareUserNotificationCustomMessage"]' ); + const tooltip = policyView.find('EuiIconTip'); expect(userNotificationCheckbox).toHaveLength(1); expect(userNotificationCustomMessageTextArea).toHaveLength(1); + expect(tooltip).toHaveLength(1); }); }); describe('when the subscription tier is gold or lower', () => { @@ -311,15 +313,17 @@ describe('Policy Details', () => { policyView = render(); }); - it('malware popup and message customization options are hidden', () => { + it('malware popup, message customization options, and tooltip are hidden', () => { const userNotificationCheckbox = policyView.find( 'EuiCheckbox[data-test-subj="malwareUserNotificationCheckbox"]' ); const userNotificationCustomMessageTextArea = policyView.find( 'EuiTextArea[data-test-subj="malwareUserNotificationCustomMessage"]' ); + const tooltip = policyView.find('EuiIconTip'); expect(userNotificationCheckbox).toHaveLength(0); expect(userNotificationCustomMessageTextArea).toHaveLength(0); + expect(tooltip).toHaveLength(0); }); }); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/protections/malware.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/protections/malware.tsx index 330a0ba407453..d611c4102e8f8 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/protections/malware.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/protections/malware.tsx @@ -18,6 +18,9 @@ import { EuiText, EuiTextArea, htmlIdGenerator, + EuiFlexGroup, + EuiFlexItem, + EuiIconTip, } from '@elastic/eui'; import { cloneDeep } from 'lodash'; import { APP_ID } from '../../../../../../../common/constants'; @@ -252,14 +255,37 @@ export const MalwareProtections = React.memo(() => { {isPlatinumPlus && userNotificationSelected && ( <> - -

- + + +

+ +

+
+
+ + + + + + + } /> -

-
+ + Date: Mon, 14 Dec 2020 13:38:38 -0600 Subject: [PATCH 40/95] [Security Solution] [Sourcerer] Cypress tests (#80410) --- .../cypress/integration/sourcerer.spec.ts | 105 ++++++++++++++ .../cypress/screens/sourcerer.ts | 30 ++++ .../cypress/tasks/sourcerer.ts | 136 ++++++++++++++++++ .../timeline/search_or_filter/pick_events.tsx | 17 ++- 4 files changed, 281 insertions(+), 7 deletions(-) create mode 100644 x-pack/plugins/security_solution/cypress/integration/sourcerer.spec.ts create mode 100644 x-pack/plugins/security_solution/cypress/screens/sourcerer.ts create mode 100644 x-pack/plugins/security_solution/cypress/tasks/sourcerer.ts diff --git a/x-pack/plugins/security_solution/cypress/integration/sourcerer.spec.ts b/x-pack/plugins/security_solution/cypress/integration/sourcerer.spec.ts new file mode 100644 index 0000000000000..4126bcfdbf0b4 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/integration/sourcerer.spec.ts @@ -0,0 +1,105 @@ +/* + * 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 { loginAndWaitForPage } from '../tasks/login'; + +import { HOSTS_URL } from '../urls/navigation'; +import { waitForAllHostsToBeLoaded } from '../tasks/hosts/all_hosts'; +import { + clickOutOfSourcererTimeline, + clickTimelineRadio, + deselectSourcererOptions, + isCustomRadio, + isHostsStatValue, + isNotCustomRadio, + isNotSourcererSelection, + isSourcererOptions, + isSourcererSelection, + openSourcerer, + resetSourcerer, + setSourcererOption, + unsetSourcererOption, +} from '../tasks/sourcerer'; +import { openTimelineUsingToggle } from '../tasks/security_main'; +import { populateTimeline } from '../tasks/timeline'; +import { SERVER_SIDE_EVENT_COUNT } from '../screens/timeline'; + +describe('Sourcerer', () => { + beforeEach(() => { + loginAndWaitForPage(HOSTS_URL); + }); + describe('Default scope', () => { + it('has SIEM index patterns selected on initial load', () => { + openSourcerer(); + isSourcererSelection(`auditbeat-*`); + }); + + it('has Kibana index patterns in the options', () => { + openSourcerer(); + isSourcererOptions([`metrics-*`, `logs-*`]); + }); + it('selected KIP gets added to sourcerer', () => { + setSourcererOption(`metrics-*`); + openSourcerer(); + isSourcererSelection(`metrics-*`); + }); + + it('does not return data without correct pattern selected', () => { + waitForAllHostsToBeLoaded(); + isHostsStatValue('4 '); + setSourcererOption(`metrics-*`); + unsetSourcererOption(`auditbeat-*`); + isHostsStatValue('0 '); + }); + + it('reset button restores to original state', () => { + setSourcererOption(`metrics-*`); + openSourcerer(); + isSourcererSelection(`metrics-*`); + resetSourcerer(); + openSourcerer(); + isNotSourcererSelection(`metrics-*`); + }); + }); + describe('Timeline scope', () => { + const alertPatterns = ['.siem-signals-default']; + const rawPatterns = ['auditbeat-*']; + const allPatterns = [...alertPatterns, ...rawPatterns]; + it('Radio buttons select correct sourcerer patterns', () => { + openTimelineUsingToggle(); + openSourcerer('timeline'); + allPatterns.forEach((ss) => isSourcererSelection(ss, 'timeline')); + clickTimelineRadio('raw'); + rawPatterns.forEach((ss) => isSourcererSelection(ss, 'timeline')); + alertPatterns.forEach((ss) => isNotSourcererSelection(ss, 'timeline')); + clickTimelineRadio('alert'); + alertPatterns.forEach((ss) => isSourcererSelection(ss, 'timeline')); + rawPatterns.forEach((ss) => isNotSourcererSelection(ss, 'timeline')); + }); + it('Adding an option results in the custom radio becoming active', () => { + openTimelineUsingToggle(); + openSourcerer('timeline'); + isNotCustomRadio(); + clickOutOfSourcererTimeline(); + const luckyOption = 'logs-*'; + setSourcererOption(luckyOption, 'timeline'); + openSourcerer('timeline'); + isCustomRadio(); + }); + it('Selected index patterns are properly queried', () => { + openTimelineUsingToggle(); + populateTimeline(); + openSourcerer('timeline'); + deselectSourcererOptions(rawPatterns, 'timeline'); + cy.get(SERVER_SIDE_EVENT_COUNT) + .invoke('text') + .then((strCount) => { + const intCount = +strCount; + cy.wrap(intCount).should('eq', 0); + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/cypress/screens/sourcerer.ts b/x-pack/plugins/security_solution/cypress/screens/sourcerer.ts new file mode 100644 index 0000000000000..3f461c425c54d --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/screens/sourcerer.ts @@ -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. + */ + +export const SOURCERER_TRIGGER = '[data-test-subj="sourcerer-trigger"]'; +export const SOURCERER_INPUT = + '[data-test-subj="indexPattern-switcher"] [data-test-subj="comboBoxInput"]'; +export const SOURCERER_OPTIONS = + '[data-test-subj="comboBoxOptionsList indexPattern-switcher-optionsList"]'; +export const SOURCERER_SAVE_BUTTON = 'button[data-test-subj="add-index"]'; +export const SOURCERER_RESET_BUTTON = 'button[data-test-subj="sourcerer-reset"]'; +export const SOURCERER_POPOVER_TITLE = '.euiPopoverTitle'; +export const HOSTS_STAT = '[data-test-subj="stat-hosts"] [data-test-subj="stat-title"]'; + +export const SOURCERER_TIMELINE = { + trigger: '[data-test-subj="sourcerer-timeline-trigger"]', + advancedSettings: '[data-test-subj="advanced-settings"]', + sourcerer: '[data-test-subj="timeline-sourcerer"]', + sourcererInput: '[data-test-subj="timeline-sourcerer"] [data-test-subj="comboBoxInput"]', + sourcererOptions: '[data-test-subj="comboBoxOptionsList timeline-sourcerer-optionsList"]', + radioRaw: '[data-test-subj="timeline-sourcerer-radio"] label.euiRadio__label[for="raw"]', + radioAlert: '[data-test-subj="timeline-sourcerer-radio"] label.euiRadio__label[for="alert"]', + radioAll: '[data-test-subj="timeline-sourcerer-radio"] label.euiRadio__label[for="all"]', + radioCustom: '[data-test-subj="timeline-sourcerer-radio"] input.euiRadio__input[id="custom"]', + radioCustomLabel: + '[data-test-subj="timeline-sourcerer-radio"] label.euiRadio__label[for="custom"]', +}; +export const SOURCERER_TIMELINE_ADVANCED = '[data-test-subj="advanced-settings"]'; diff --git a/x-pack/plugins/security_solution/cypress/tasks/sourcerer.ts b/x-pack/plugins/security_solution/cypress/tasks/sourcerer.ts new file mode 100644 index 0000000000000..b224f81ab8f2f --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/tasks/sourcerer.ts @@ -0,0 +1,136 @@ +/* + * 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 { + HOSTS_STAT, + SOURCERER_INPUT, + SOURCERER_OPTIONS, + SOURCERER_POPOVER_TITLE, + SOURCERER_RESET_BUTTON, + SOURCERER_SAVE_BUTTON, + SOURCERER_TIMELINE, + SOURCERER_TRIGGER, +} from '../screens/sourcerer'; +import { TIMELINE_TITLE } from '../screens/timeline'; + +export const openSourcerer = (sourcererScope?: string) => { + if (sourcererScope != null && sourcererScope === 'timeline') { + return openTimelineSourcerer(); + } + cy.get(SOURCERER_TRIGGER).should('be.enabled'); + cy.get(SOURCERER_TRIGGER).should('be.visible'); + cy.get(SOURCERER_TRIGGER).click(); +}; +export const openTimelineSourcerer = () => { + cy.get(SOURCERER_TIMELINE.trigger).should('be.enabled'); + cy.get(SOURCERER_TIMELINE.trigger).should('be.visible'); + cy.get(SOURCERER_TIMELINE.trigger).click(); + cy.get(SOURCERER_TIMELINE.advancedSettings).should(($div) => { + if ($div.text() === 'Show Advanced') { + $div.click(); + } + expect(true).to.eq(true); + }); +}; +export const openAdvancedSettings = () => {}; + +export const clickOutOfSelector = () => { + return cy.get(SOURCERER_POPOVER_TITLE).first().click(); +}; + +const getScopedSelectors = (sourcererScope?: string): { input: string; options: string } => + sourcererScope != null && sourcererScope === 'timeline' + ? { input: SOURCERER_TIMELINE.sourcererInput, options: SOURCERER_TIMELINE.sourcererOptions } + : { input: SOURCERER_INPUT, options: SOURCERER_OPTIONS }; + +export const isSourcererSelection = (patternName: string, sourcererScope?: string) => { + const { input } = getScopedSelectors(sourcererScope); + return cy.get(input).find(`span[title="${patternName}"]`).should('exist'); +}; + +export const isHostsStatValue = (value: string) => { + return cy.get(HOSTS_STAT).first().should('have.text', value); +}; + +export const isNotSourcererSelection = (patternName: string, sourcererScope?: string) => { + const { input } = getScopedSelectors(sourcererScope); + return cy.get(input).find(`span[title="${patternName}"]`).should('not.exist'); +}; + +export const isSourcererOptions = (patternNames: string[], sourcererScope?: string) => { + const { input, options } = getScopedSelectors(sourcererScope); + cy.get(input).click(); + return patternNames.every((patternName) => { + return cy + .get(options) + .find(`button.euiFilterSelectItem[title="${patternName}"]`) + .its('length') + .should('eq', 1); + }); +}; + +export const selectSourcererOption = (patternName: string, sourcererScope?: string) => { + const { input, options } = getScopedSelectors(sourcererScope); + cy.get(input).click(); + cy.get(options).find(`button.euiFilterSelectItem[title="${patternName}"]`).click(); + clickOutOfSelector(); + return cy.get(SOURCERER_SAVE_BUTTON).click({ force: true }); +}; + +export const deselectSourcererOption = (patternName: string, sourcererScope?: string) => { + const { input } = getScopedSelectors(sourcererScope); + cy.get(input).find(`span[title="${patternName}"] button`).click(); + clickOutOfSelector(); + return cy.get(SOURCERER_SAVE_BUTTON).click({ force: true }); +}; + +export const deselectSourcererOptions = (patternNames: string[], sourcererScope?: string) => { + const { input } = getScopedSelectors(sourcererScope); + patternNames.forEach((patternName) => + cy.get(input).find(`span[title="${patternName}"] button`).click() + ); + clickOutOfSelector(); + return cy.get(SOURCERER_SAVE_BUTTON).click({ force: true }); +}; + +export const resetSourcerer = () => { + cy.get(SOURCERER_RESET_BUTTON).click(); + clickOutOfSelector(); + return cy.get(SOURCERER_SAVE_BUTTON).click({ force: true }); +}; + +export const setSourcererOption = (patternName: string, sourcererScope?: string) => { + openSourcerer(sourcererScope); + isNotSourcererSelection(patternName, sourcererScope); + selectSourcererOption(patternName, sourcererScope); +}; + +export const unsetSourcererOption = (patternName: string, sourcererScope?: string) => { + openSourcerer(sourcererScope); + isSourcererSelection(patternName, sourcererScope); + deselectSourcererOption(patternName, sourcererScope); +}; + +export const clickTimelineRadio = (radioName: string) => { + let theRadio = SOURCERER_TIMELINE.radioAll; + if (radioName === 'alert') { + theRadio = SOURCERER_TIMELINE.radioAlert; + } + if (radioName === 'raw') { + theRadio = SOURCERER_TIMELINE.radioRaw; + } + return cy.get(theRadio).first().click(); +}; + +export const isCustomRadio = () => { + return cy.get(SOURCERER_TIMELINE.radioCustom).should('be.enabled'); +}; + +export const isNotCustomRadio = () => { + return cy.get(SOURCERER_TIMELINE.radioCustom).should('be.disabled'); +}; + +export const clickOutOfSourcererTimeline = () => cy.get(TIMELINE_TITLE).first().click(); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/pick_events.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/pick_events.tsx index d7d8d810f6972..3bc0eeeef70a9 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/pick_events.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/pick_events.tsx @@ -251,12 +251,13 @@ const PickEventTypeComponents: React.FC = ({ const comboBox = useMemo( () => ( ), [onChangeCombo, indexesPatternOptions, renderOption, selectedOptions] @@ -269,6 +270,7 @@ const PickEventTypeComponents: React.FC = ({ const filter = useMemo( () => ( = ({ const options = getEventTypeOptions(); return ( = ({ const ButtonContent = useMemo( () => ( - + {showAdvanceSettings ? i18n.HIDE_INDEX_PATTERNS_ADVANCED_SETTINGS : i18n.SHOW_INDEX_PATTERNS_ADVANCED_SETTINGS} @@ -330,11 +333,11 @@ const PickEventTypeComponents: React.FC = ({ From ac3e02aeadeda91ac81a0bf313acc4b3bed2fb1e Mon Sep 17 00:00:00 2001 From: Jane Miller <57721870+jmiller263@users.noreply.github.com> Date: Mon, 14 Dec 2020 15:00:22 -0500 Subject: [PATCH 41/95] [SECURITY_SOLUTION] Advanced policy docs (#85203) * started docs, removed kernel harden option * Advanced policy field documentation * consistent formatting * consistent formatting * drop unused fields * grammar * i18n first key * i18n, change versions to 7.9, add some new fields, remove some that we don't want to expose * Update x-pack/plugins/security_solution/public/management/pages/policy/models/advanced_policy_schema.ts Co-authored-by: Daniel Ferullo <56368752+ferullo@users.noreply.github.com> * Update x-pack/plugins/security_solution/public/management/pages/policy/models/advanced_policy_schema.ts Co-authored-by: Daniel Ferullo <56368752+ferullo@users.noreply.github.com> * Update x-pack/plugins/security_solution/public/management/pages/policy/models/advanced_policy_schema.ts Co-authored-by: Daniel Ferullo <56368752+ferullo@users.noreply.github.com> Co-authored-by: Daniel Ferullo <56368752+ferullo@users.noreply.github.com> --- .../policy/models/advanced_policy_schema.ts | 578 +++++++++++++----- 1 file changed, 419 insertions(+), 159 deletions(-) diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/models/advanced_policy_schema.ts b/x-pack/plugins/security_solution/public/management/pages/policy/models/advanced_policy_schema.ts index d25588dabedc6..176f64c8bdcb0 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/models/advanced_policy_schema.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/models/advanced_policy_schema.ts @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +import { i18n } from '@kbn/i18n'; + interface AdvancedPolicySchemaType { key: string; first_supported_version: string; @@ -14,302 +16,560 @@ interface AdvancedPolicySchemaType { export const AdvancedPolicySchema: AdvancedPolicySchemaType[] = [ { key: 'linux.advanced.agent.connection_delay', - first_supported_version: '7.11', - documentation: '', + first_supported_version: '7.9', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.linux.advanced.agent.connection_delay', + { + defaultMessage: + 'How long to wait for agent connectivity before sending first policy reply, in seconds. Default: 60.', + } + ), }, { key: 'linux.advanced.artifacts.global.base_url', - first_supported_version: '7.11', - documentation: '', + first_supported_version: '7.9', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.linux.advanced.artifacts.global.base_url', + { + defaultMessage: + 'Base URL from which to download global artifact manifests. Default: https://artifacts.security.elastic.co.', + } + ), }, { key: 'linux.advanced.artifacts.global.manifest_relative_url', - first_supported_version: '7.11', - documentation: '', - }, - { - key: 'linux.advanced.artifacts.global.ca_cert', - first_supported_version: '7.11', - documentation: '', + first_supported_version: '7.9', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.linux.advanced.artifacts.global.manifest_relative_url', + { + defaultMessage: + 'Relative URL from which to download global artifact manifests. Default: /downloads/endpoint/manifest/artifacts-.zip.', + } + ), }, { key: 'linux.advanced.artifacts.global.public_key', - first_supported_version: '7.11', - documentation: '', + first_supported_version: '7.9', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.linux.advanced.artifacts.global.public_key', + { + defaultMessage: + 'PEM-encoded public key used to verify the global artifact manifest signature.', + } + ), }, { key: 'linux.advanced.artifacts.global.interval', - first_supported_version: '7.11', - documentation: '', - }, - { - key: 'linux.advanced.artifacts.user.base_url', - first_supported_version: '7.11', - documentation: '', - }, - { - key: 'linux.advanced.artifacts.user.ca_cert', - first_supported_version: '7.11', - documentation: '', + first_supported_version: '7.9', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.linux.advanced.artifacts.global.interval', + { + defaultMessage: + 'Interval between global artifact manifest download attempts, in seconds. Default: 3600.', + } + ), }, { key: 'linux.advanced.artifacts.user.public_key', - first_supported_version: '7.11', - documentation: '', - }, - { - key: 'linux.advanced.artifacts.user.interval', - first_supported_version: '7.11', - documentation: '', + first_supported_version: '7.9', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.linux.advanced.artifacts.user.public_key', + { + defaultMessage: + 'PEM-encoded public key used to verify the user artifact manifest signature.', + } + ), }, { key: 'linux.advanced.elasticsearch.delay', - first_supported_version: '7.11', - documentation: '', + first_supported_version: '7.9', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.linux.advanced.elasticsearch.delay', + { + defaultMessage: 'Delay for sending events to Elasticsearch, in seconds. Default: 120.', + } + ), }, { key: 'linux.advanced.elasticsearch.tls.verify_peer', - first_supported_version: '7.11', - documentation: 'default is true', + first_supported_version: '7.9', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.linux.advanced.elasticsearch.tls.verify_peer', + { + defaultMessage: 'Whether to verify the certificates presented by the peer. Default: true.', + } + ), }, { key: 'linux.advanced.elasticsearch.tls.verify_hostname', - first_supported_version: '7.11', - documentation: 'default is true', + first_supported_version: '7.9', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.linux.advanced.elasticsearch.tls.verify_hostname', + { + defaultMessage: + "Whether to verify the hostname of the peer is what's in the certificate. Default: true.", + } + ), }, { key: 'linux.advanced.elasticsearch.tls.ca_cert', - first_supported_version: '7.11', - documentation: '', - }, - { - key: 'mac.advanced.agent.connection_delay', - first_supported_version: '7.11', - documentation: '', + first_supported_version: '7.9', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.linux.advanced.elasticsearch.tls.ca_cert', + { + defaultMessage: 'PEM-encoded certificate for Elasticsearch certificate authority.', + } + ), }, { - key: 'mac.advanced.artifacts.global.base_url', + key: 'linux.advanced.logging.file', first_supported_version: '7.11', - documentation: '', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.linux.advanced.logging.file', + { + defaultMessage: + 'A supplied value will override the log level configured for logs that are saved to disk and streamed to Elasticsearch. It is recommended Fleet be used to change this logging in most circumstances. Allowed values are error, warning, info, debug, and trace.', + } + ), }, { - key: 'mac.advanced.artifacts.global.manifest_relative_url', + key: 'linux.advanced.logging.syslog', first_supported_version: '7.11', - documentation: '', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.linux.advanced.logging.syslog', + { + defaultMessage: + 'A supplied value will configure logging to syslog. Allowed values are error, warning, info, debug, and trace.', + } + ), }, { - key: 'mac.advanced.artifacts.global.ca_cert', - first_supported_version: '7.11', - documentation: '', + key: 'mac.advanced.agent.connection_delay', + first_supported_version: '7.9', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.mac.advanced.agent.connection_delay', + { + defaultMessage: + 'How long to wait for agent connectivity before sending first policy reply, in seconds. Default: 60.', + } + ), }, { - key: 'mac.advanced.artifacts.global.public_key', - first_supported_version: '7.11', - documentation: '', + key: 'mac.advanced.artifacts.global.base_url', + first_supported_version: '7.9', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.mac.advanced.artifacts.global.base_url', + { + defaultMessage: 'URL from which to download global artifact manifests.', + } + ), }, { - key: 'mac.advanced.artifacts.global.interval', - first_supported_version: '7.11', - documentation: '', + key: 'mac.advanced.artifacts.global.manifest_relative_url', + first_supported_version: '7.9', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.mac.advanced.artifacts.global.manifest_relative_url', + { + defaultMessage: + 'Relative URL from which to download global artifact manifests. Default: /downloads/endpoint/manifest/artifacts-.zip.', + } + ), }, { - key: 'mac.advanced.artifacts.user.base_url', - first_supported_version: '7.11', - documentation: '', + key: 'mac.advanced.artifacts.global.public_key', + first_supported_version: '7.9', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.mac.advanced.artifacts.global.public_key', + { + defaultMessage: + 'PEM-encoded public key used to verify the global artifact manifest signature.', + } + ), }, { - key: 'mac.advanced.artifacts.user.ca_cert', - first_supported_version: '7.11', - documentation: '', + key: 'mac.advanced.artifacts.global.interval', + first_supported_version: '7.9', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.mac.advanced.artifacts.global.interval', + { + defaultMessage: + 'Interval between global artifact manifest download attempts, in seconds. Default: 3600.', + } + ), }, { key: 'mac.advanced.artifacts.user.public_key', - first_supported_version: '7.11', - documentation: '', - }, - { - key: 'mac.advanced.artifacts.user.interval', - first_supported_version: '7.11', - documentation: '', + first_supported_version: '7.9', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.mac.advanced.artifacts.user.public_key', + { + defaultMessage: + 'PEM-encoded public key used to verify the user artifact manifest signature.', + } + ), }, { key: 'mac.advanced.elasticsearch.delay', - first_supported_version: '7.11', - documentation: '', + first_supported_version: '7.9', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.mac.advanced.elasticsearch.delay', + { + defaultMessage: 'Delay for sending events to Elasticsearch, in seconds. Default: 120.', + } + ), }, { key: 'mac.advanced.elasticsearch.tls.verify_peer', - first_supported_version: '7.11', - documentation: 'default is true', + first_supported_version: '7.9', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.mac.advanced.elasticsearch.tls.verify_peer', + { + defaultMessage: 'Whether to verify the certificates presented by the peer. Default: true.', + } + ), }, { key: 'mac.advanced.elasticsearch.tls.verify_hostname', - first_supported_version: '7.11', - documentation: 'default is true', + first_supported_version: '7.9', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.mac.advanced.elasticsearch.tls.verify_hostname', + { + defaultMessage: + "Whether to verify the hostname of the peer is what's in the certificate. Default: true.", + } + ), }, { key: 'mac.advanced.elasticsearch.tls.ca_cert', - first_supported_version: '7.11', - documentation: '', + first_supported_version: '7.9', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.mac.advanced.elasticsearch.tls.ca_cert', + { + defaultMessage: 'PEM-encoded certificate for Elasticsearch certificate authority.', + } + ), }, { - key: 'mac.advanced.malware.quarantine', + key: 'mac.advanced.logging.file', first_supported_version: '7.11', - documentation: '', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.mac.advanced.logging.file', + { + defaultMessage: + 'A supplied value will override the log level configured for logs that are saved to disk and streamed to Elasticsearch. It is recommended Fleet be used to change this logging in most circumstances. Allowed values are error, warning, info, debug, and trace.', + } + ), }, { - key: 'mac.advanced.kernel.connect', + key: 'mac.advanced.logging.syslog', first_supported_version: '7.11', - documentation: '', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.mac.advanced.logging.syslog', + { + defaultMessage: + 'A supplied value will configure logging to syslog. Allowed values are error, warning, info, debug, and trace.', + } + ), }, { - key: 'mac.advanced.kernel.harden', - first_supported_version: '7.11', - documentation: '', + key: 'mac.advanced.malware.quarantine', + first_supported_version: '7.9', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.mac.advanced.malware.quarantine', + { + defaultMessage: + 'Whether quarantine should be enabled when malware prevention is enabled. Default: true.', + } + ), + }, + { + key: 'mac.advanced.kernel.connect', + first_supported_version: '7.9', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.mac.advanced.kernel.connect', + { + defaultMessage: 'Whether to connect to the kernel driver. Default: true.', + } + ), }, { key: 'mac.advanced.kernel.process', - first_supported_version: '7.11', - documentation: '', + first_supported_version: '7.9', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.mac.advanced.kernel.process', + { + defaultMessage: + "A value of 'false' overrides other config settings that would enable kernel process events. Default: true.", + } + ), }, { key: 'mac.advanced.kernel.filewrite', - first_supported_version: '7.11', - documentation: '', + first_supported_version: '7.9', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.mac.advanced.kernel.filewrite', + { + defaultMessage: + "A value of 'false' overrides other config settings that would enable kernel file write events. Default: true.", + } + ), }, { key: 'mac.advanced.kernel.network', + first_supported_version: '7.9', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.mac.advanced.kernel.network', + { + defaultMessage: + "A value of 'false' overrides other config settings that would enable kernel network events. Default: true.", + } + ), + }, + { + key: 'mac.advanced.harden.self_protect', first_supported_version: '7.11', - documentation: '', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.mac.advanced.harden.self_protect', + { + defaultMessage: 'Enables self-protection on macOS. Default: true.', + } + ), }, { key: 'windows.advanced.agent.connection_delay', - first_supported_version: '7.11', - documentation: '', + first_supported_version: '7.9', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.windows.advanced.agent.connection_delay', + { + defaultMessage: + 'How long to wait for agent connectivity before sending first policy reply, in seconds. Default: 60.', + } + ), }, { key: 'windows.advanced.artifacts.global.base_url', - first_supported_version: '7.11', - documentation: '', + first_supported_version: '7.9', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.windows.advanced.artifacts.global.base_url', + { + defaultMessage: 'URL from which to download global artifact manifests.', + } + ), }, { key: 'windows.advanced.artifacts.global.manifest_relative_url', - first_supported_version: '7.11', - documentation: '', - }, - { - key: 'windows.advanced.artifacts.global.ca_cert', - first_supported_version: '7.11', - documentation: '', + first_supported_version: '7.9', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.windows.advanced.artifacts.global.manifest_relative_url', + { + defaultMessage: + 'Relative URL from which to download global artifact manifests. Default: /downloads/endpoint/manifest/artifacts-.zip.', + } + ), }, { key: 'windows.advanced.artifacts.global.public_key', - first_supported_version: '7.11', - documentation: '', + first_supported_version: '7.9', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.windows.advanced.artifacts.global.public_key', + { + defaultMessage: + 'PEM-encoded public key used to verify the global artifact manifest signature.', + } + ), }, { key: 'windows.advanced.artifacts.global.interval', - first_supported_version: '7.11', - documentation: '', - }, - { - key: 'windows.advanced.artifacts.user.base_url', - first_supported_version: '7.11', - documentation: '', - }, - { - key: 'windows.advanced.artifacts.user.ca_cert', - first_supported_version: '7.11', - documentation: '', + first_supported_version: '7.9', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.windows.advanced.artifacts.global.interval', + { + defaultMessage: + 'Interval between global artifact manifest download attempts, in seconds. Default: 3600.', + } + ), }, { key: 'windows.advanced.artifacts.user.public_key', - first_supported_version: '7.11', - documentation: '', - }, - { - key: 'windows.advanced.artifacts.user.interval', - first_supported_version: '7.11', - documentation: '', + first_supported_version: '7.9', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.windows.advanced.artifacts.user.public_key', + { + defaultMessage: + 'PEM-encoded public key used to verify the user artifact manifest signature.', + } + ), }, { key: 'windows.advanced.elasticsearch.delay', - first_supported_version: '7.11', - documentation: '', + first_supported_version: '7.9', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.windows.advanced.elasticsearch.delay', + { + defaultMessage: 'Delay for sending events to Elasticsearch, in seconds. Default: 120.', + } + ), }, { key: 'windows.advanced.elasticsearch.tls.verify_peer', - first_supported_version: '7.11', - documentation: 'default is true', + first_supported_version: '7.9', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.windows.advanced.elasticsearch.tls.verify_peer', + { + defaultMessage: 'Whether to verify the certificates presented by the peer. Default: true.', + } + ), }, { key: 'windows.advanced.elasticsearch.tls.verify_hostname', - first_supported_version: '7.11', - documentation: 'default is true', + first_supported_version: '7.9', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.windows.advanced.elasticsearch.tls.verify_hostname', + { + defaultMessage: + "Whether to verify the hostname of the peer is what's in the certificate. Default: true.", + } + ), }, { key: 'windows.advanced.elasticsearch.tls.ca_cert', - first_supported_version: '7.11', - documentation: '', + first_supported_version: '7.9', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.windows.advanced.elasticsearch.tls.ca_cert', + { + defaultMessage: 'PEM-encoded certificate for Elasticsearch certificate authority.', + } + ), }, { - key: 'windows.advanced.malware.quarantine', + key: 'windows.advanced.logging.file', first_supported_version: '7.11', - documentation: '', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.windows.advanced.logging.file', + { + defaultMessage: + 'A supplied value will override the log level configured for logs that are saved to disk and streamed to Elasticsearch. It is recommended Fleet be used to change this logging in most circumstances. Allowed values are error, warning, info, debug, and trace.', + } + ), }, { - key: 'windows.advanced.ransomware.mbr', + key: 'windows.advanced.logging.debugview', first_supported_version: '7.11', - documentation: '', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.windows.advanced.logging.debugview', + { + defaultMessage: + 'A supplied value will configure logging to Debugview (a Sysinternals tool). Allowed values are error, warning, info, debug, and trace.', + } + ), }, { - key: 'windows.advanced.ransomware.canary', - first_supported_version: '7.11', - documentation: '', + key: 'windows.advanced.malware.quarantine', + first_supported_version: '7.9', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.windows.advanced.malware.quarantine', + { + defaultMessage: + 'Whether quarantine should be enabled when malware prevention is enabled. Default: true.', + } + ), }, { key: 'windows.advanced.kernel.connect', - first_supported_version: '7.11', - documentation: '', - }, - { - key: 'windows.advanced.kernel.harden', - first_supported_version: '7.11', - documentation: '', + first_supported_version: '7.9', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.windows.advanced.kernel.connect', + { + defaultMessage: 'Whether to connect to the kernel driver. Default: true.', + } + ), }, { key: 'windows.advanced.kernel.process', - first_supported_version: '7.11', - documentation: '', + first_supported_version: '7.9', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.windows.advanced.kernel.process', + { + defaultMessage: + "A value of 'false' overrides other config settings that would enable kernel process events. Default: true.", + } + ), }, { key: 'windows.advanced.kernel.filewrite', - first_supported_version: '7.11', - documentation: '', + first_supported_version: '7.9', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.windows.advanced.kernel.filewrite', + { + defaultMessage: + "A value of 'false' overrides other config settings that would enable kernel file write events. Default: true.", + } + ), }, { key: 'windows.advanced.kernel.network', - first_supported_version: '7.11', - documentation: '', + first_supported_version: '7.9', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.windows.advanced.kernel.network', + { + defaultMessage: + "A value of 'false' overrides other config settings that would enable kernel network events. Default: true.", + } + ), }, { key: 'windows.advanced.kernel.fileopen', - first_supported_version: '7.11', - documentation: '', + first_supported_version: '7.9', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.windows.advanced.kernel.fileopen', + { + defaultMessage: + "A value of 'false' overrides other config settings that would enable kernel file open events. Default: true.", + } + ), }, { key: 'windows.advanced.kernel.asyncimageload', - first_supported_version: '7.11', - documentation: '', + first_supported_version: '7.9', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.windows.advanced.kernel.asyncimageload', + { + defaultMessage: + "A value of 'false' overrides other config settings that would enable kernel async image load events. Default: true.", + } + ), }, { key: 'windows.advanced.kernel.syncimageload', - first_supported_version: '7.11', - documentation: '', + first_supported_version: '7.9', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.windows.advanced.kernel.syncimageload', + { + defaultMessage: + "A value of 'false' overrides other config settings that would enable kernel sync image load events. Default: true.", + } + ), }, { key: 'windows.advanced.kernel.registry', - first_supported_version: '7.11', - documentation: '', + first_supported_version: '7.9', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.windows.advanced.kernel.registry', + { + defaultMessage: + "A value of 'false' overrides other config settings that would enable kernel registry events. Default: true.", + } + ), + }, + { + key: 'windows.advanced.diagnostic.enabled', + first_supported_version: '7.11', + documentation: i18n.translate( + 'xpack.securitySolution.endpoint.policy.advanced.windows.advanced.diagnostic.enabled', + { + defaultMessage: + "A value of 'false' disables running diagnostic features on Endpoint. Default: true.", + } + ), }, ]; From b8ab3fdafc7562b1cfc85b354971a93c7a218f9e Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Mon, 14 Dec 2020 21:10:00 +0100 Subject: [PATCH 42/95] [ILM] Rollover field redesign (#85579) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * implement form-level support for using default rollover action * slight update to copy * added use default rollover switch and tooltips for detailed copy * fix legacy integration tests and do not unmount rollover field!! * remove unused import * fix client integration tests * updated form to use new isUsingRollover check * fix serialization of rollover * Update x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/hot_phase/hot_phase.tsx Co-authored-by: Adam Locke * Update x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/hot_phase/hot_phase.tsx Co-authored-by: Adam Locke * Update x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/hot_phase/hot_phase.tsx Co-authored-by: Adam Locke Co-authored-by: Yulia Čech <6585477+yuliacech@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Adam Locke --- .../edit_policy/edit_policy.helpers.tsx | 3 + .../edit_policy/edit_policy.test.ts | 20 +- .../__jest__/components/edit_policy.test.tsx | 12 + .../common/types/policies.ts | 12 +- .../public/application/constants/policy.ts | 17 +- .../public/application/lib/index.ts | 2 + .../public/application/lib/rollover.ts | 18 + .../described_form_row/described_form_row.tsx | 4 +- .../components/phases/hot_phase/hot_phase.tsx | 325 ++++++++++-------- .../components/phases/shared_fields/index.ts | 2 - .../min_age_input_field.tsx | 13 +- .../searchable_snapshot_field.tsx | 9 +- .../phases/warm_phase/warm_phase.tsx | 10 +- .../sections/edit_policy/constants.ts | 2 + .../form/configuration_issues_context.tsx | 17 +- .../sections/edit_policy/form/deserializer.ts | 3 +- .../sections/edit_policy/form/schema.ts | 6 + .../edit_policy/form/serializer/serializer.ts | 8 +- .../application/sections/edit_policy/types.ts | 1 + 19 files changed, 300 insertions(+), 184 deletions(-) create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/lib/rollover.ts diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.helpers.tsx b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.helpers.tsx index abb33d109742c..7206fbfd547d4 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.helpers.tsx +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.helpers.tsx @@ -111,6 +111,8 @@ export const setup = async (arg?: { appServicesContext: Partial { @@ -239,6 +241,7 @@ export const setup = async (arg?: { appServicesContext: Partial', () => { test('setting all values', async () => { const { actions } = testBed; + await actions.hot.toggleDefaultRollover(false); await actions.hot.setMaxSize('123', 'mb'); await actions.hot.setMaxDocs('123'); await actions.hot.setMaxAge('123', 'h'); @@ -177,7 +178,8 @@ describe('', () => { test('disabling rollover', async () => { const { actions } = testBed; - await actions.hot.toggleRollover(true); + await actions.hot.toggleDefaultRollover(false); + await actions.hot.toggleRollover(false); await actions.savePolicy(); const latestRequest = server.requests[server.requests.length - 1]; const policy = JSON.parse(JSON.parse(latestRequest.requestBody).body); @@ -212,6 +214,17 @@ describe('', () => { expect(actions.cold.searchableSnapshotsExists()).toBeTruthy(); expect(actions.cold.freezeExists()).toBeFalsy(); }); + + test('disabling rollover toggle, but enabling default rollover', async () => { + const { actions } = testBed; + await actions.hot.toggleDefaultRollover(false); + await actions.hot.toggleRollover(false); + await actions.hot.toggleDefaultRollover(true); + + expect(actions.hot.forceMergeFieldExists()).toBeTruthy(); + expect(actions.hot.shrinkExists()).toBeTruthy(); + expect(actions.hot.searchableSnapshotsExists()).toBeTruthy(); + }); }); }); @@ -766,7 +779,7 @@ describe('', () => { await act(async () => { testBed = await setup({ appServicesContext: { - license: licensingMock.createLicense({ license: { type: 'basic' } }), + license: licensingMock.createLicense({ license: { type: 'enterprise' } }), }, }); }); @@ -776,11 +789,12 @@ describe('', () => { }); test('hiding and disabling searchable snapshot field', async () => { const { actions } = testBed; + await actions.hot.toggleDefaultRollover(false); await actions.hot.toggleRollover(false); await actions.cold.enable(true); expect(actions.hot.searchableSnapshotsExists()).toBeFalsy(); - expect(actions.cold.searchableSnapshotDisabledDueToLicense()).toBeTruthy(); + expect(actions.cold.searchableSnapshotDisabledDueToRollover()).toBeTruthy(); }); }); }); diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/components/edit_policy.test.tsx b/x-pack/plugins/index_lifecycle_management/__jest__/components/edit_policy.test.tsx index d7d38e3b92516..c54ccb9f85edf 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/components/edit_policy.test.tsx +++ b/x-pack/plugins/index_lifecycle_management/__jest__/components/edit_policy.test.tsx @@ -113,7 +113,14 @@ const expectedErrorMessages = (rendered: ReactWrapper, expectedMessages: string[ expect(foundErrorMessage).toBe(true); }); }; +const noDefaultRollover = async (rendered: ReactWrapper) => { + await act(async () => { + findTestSubject(rendered, 'useDefaultRolloverSwitch').simulate('click'); + }); + rendered.update(); +}; const noRollover = async (rendered: ReactWrapper) => { + await noDefaultRollover(rendered); await act(async () => { findTestSubject(rendered, 'rolloverSwitch').simulate('click'); }); @@ -326,6 +333,7 @@ describe('edit policy', () => { describe('hot phase', () => { test('should show errors when trying to save with no max size, no max age and no max docs', async () => { const rendered = mountWithIntl(component); + await noDefaultRollover(rendered); expect(findTestSubject(rendered, 'rolloverSettingsRequired').exists()).toBeFalsy(); await setPolicyName(rendered, 'mypolicy'); const maxSizeInput = findTestSubject(rendered, 'hot-selectedMaxSizeStored'); @@ -349,6 +357,7 @@ describe('edit policy', () => { test('should show number above 0 required error when trying to save with -1 for max size', async () => { const rendered = mountWithIntl(component); await setPolicyName(rendered, 'mypolicy'); + await noDefaultRollover(rendered); const maxSizeInput = findTestSubject(rendered, 'hot-selectedMaxSizeStored'); await act(async () => { maxSizeInput.simulate('change', { target: { value: '-1' } }); @@ -360,6 +369,7 @@ describe('edit policy', () => { test('should show number above 0 required error when trying to save with 0 for max size', async () => { const rendered = mountWithIntl(component); await setPolicyName(rendered, 'mypolicy'); + await noDefaultRollover(rendered); const maxSizeInput = findTestSubject(rendered, 'hot-selectedMaxSizeStored'); await act(async () => { maxSizeInput.simulate('change', { target: { value: '-1' } }); @@ -370,6 +380,7 @@ describe('edit policy', () => { test('should show number above 0 required error when trying to save with -1 for max age', async () => { const rendered = mountWithIntl(component); await setPolicyName(rendered, 'mypolicy'); + await noDefaultRollover(rendered); const maxAgeInput = findTestSubject(rendered, 'hot-selectedMaxAge'); await act(async () => { maxAgeInput.simulate('change', { target: { value: '-1' } }); @@ -380,6 +391,7 @@ describe('edit policy', () => { test('should show number above 0 required error when trying to save with 0 for max age', async () => { const rendered = mountWithIntl(component); await setPolicyName(rendered, 'mypolicy'); + await noDefaultRollover(rendered); const maxAgeInput = findTestSubject(rendered, 'hot-selectedMaxAge'); await act(async () => { maxAgeInput.simulate('change', { target: { value: '0' } }); diff --git a/x-pack/plugins/index_lifecycle_management/common/types/policies.ts b/x-pack/plugins/index_lifecycle_management/common/types/policies.ts index 4f7782a51b278..58468f06e3b2d 100644 --- a/x-pack/plugins/index_lifecycle_management/common/types/policies.ts +++ b/x-pack/plugins/index_lifecycle_management/common/types/policies.ts @@ -57,13 +57,15 @@ export interface SearchableSnapshotAction { force_merge_index?: boolean; } +export interface RolloverAction { + max_size?: string; + max_age?: string; + max_docs?: number; +} + export interface SerializedHotPhase extends SerializedPhase { actions: { - rollover?: { - max_size?: string; - max_age?: string; - max_docs?: number; - }; + rollover?: RolloverAction; forcemerge?: ForcemergeAction; readonly?: {}; shrink?: ShrinkAction; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/constants/policy.ts b/x-pack/plugins/index_lifecycle_management/public/application/constants/policy.ts index 23d7387aa7076..a892a7a031a87 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/constants/policy.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/constants/policy.ts @@ -4,21 +4,28 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SerializedPhase, DeletePhase, SerializedPolicy } from '../../../common/types'; +import { + SerializedPhase, + DeletePhase, + SerializedPolicy, + RolloverAction, +} from '../../../common/types'; export const defaultSetPriority: string = '100'; export const defaultPhaseIndexPriority: string = '50'; +export const defaultRolloverAction: RolloverAction = { + max_age: '30d', + max_size: '50gb', +}; + export const defaultPolicy: SerializedPolicy = { name: '', phases: { hot: { actions: { - rollover: { - max_age: '30d', - max_size: '50gb', - }, + rollover: defaultRolloverAction, }, }, }, diff --git a/x-pack/plugins/index_lifecycle_management/public/application/lib/index.ts b/x-pack/plugins/index_lifecycle_management/public/application/lib/index.ts index 1dabae1a0f0c4..274905342f815 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/lib/index.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/lib/index.ts @@ -5,3 +5,5 @@ */ export * from './data_tiers'; + +export * from './rollover'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/lib/rollover.ts b/x-pack/plugins/index_lifecycle_management/public/application/lib/rollover.ts new file mode 100644 index 0000000000000..1b85303c4bce0 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/lib/rollover.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { SerializedPolicy } from '../../../common/types'; +import { defaultRolloverAction } from '../constants'; + +export const isUsingDefaultRollover = (policy: SerializedPolicy): boolean => { + const rollover = policy?.phases?.hot?.actions?.rollover; + return Boolean( + rollover && + rollover.max_age === defaultRolloverAction.max_age && + rollover.max_docs === defaultRolloverAction.max_docs && + rollover.max_size === defaultRolloverAction.max_size + ); +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/described_form_row/described_form_row.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/described_form_row/described_form_row.tsx index 98c63437659fd..161729ae48057 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/described_form_row/described_form_row.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/described_form_row/described_form_row.tsx @@ -55,7 +55,9 @@ export const DescribedFormRow: FunctionComponent = ({ const [uncontrolledIsContentVisible, setUncontrolledIsContentVisible] = useState( () => switchProps?.initialValue ?? false ); - const isContentVisible = Boolean(switchProps?.checked ?? uncontrolledIsContentVisible); + const isContentVisible = Boolean( + switchProps === undefined || (switchProps?.checked ?? uncontrolledIsContentVisible) + ); const renderToggle = () => { if (!switchProps) { diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/hot_phase/hot_phase.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/hot_phase/hot_phase.tsx index 77f36a237c0c4..ae8fecd1a1958 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/hot_phase/hot_phase.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/hot_phase/hot_phase.tsx @@ -16,6 +16,8 @@ import { EuiCallOut, EuiAccordion, EuiTextColor, + EuiSwitch, + EuiIconTip, } from '@elastic/eui'; import { Phases } from '../../../../../../../common/types'; @@ -24,19 +26,18 @@ import { useFormData, UseField, SelectField, NumericField } from '../../../../.. import { i18nTexts } from '../../../i18n_texts'; -import { ROLLOVER_EMPTY_VALIDATION } from '../../../form'; +import { ROLLOVER_EMPTY_VALIDATION, useConfigurationIssues } from '../../../form'; import { useEditPolicyContext } from '../../../edit_policy_context'; -import { ROLLOVER_FORM_PATHS } from '../../../constants'; +import { ROLLOVER_FORM_PATHS, isUsingDefaultRolloverPath } from '../../../constants'; -import { LearnMoreLink, ActiveBadge, ToggleFieldWithDescribedFormRow } from '../../'; +import { LearnMoreLink, ActiveBadge, DescribedFormRow } from '../../'; import { ForcemergeField, SetPriorityInputField, SearchableSnapshotField, - useRolloverPath, ReadonlyField, ShrinkField, } from '../shared_fields'; @@ -48,9 +49,10 @@ const hotProperty: keyof Phases = 'hot'; export const HotPhase: FunctionComponent = () => { const { license } = useEditPolicyContext(); const [formData] = useFormData({ - watch: useRolloverPath, + watch: isUsingDefaultRolloverPath, }); - const isRolloverEnabled = get(formData, useRolloverPath); + const { isUsingRollover } = useConfigurationIssues(); + const isUsingDefaultRollover = get(formData, isUsingDefaultRolloverPath); const [showEmptyRolloverFieldsError, setShowEmptyRolloverFieldsError] = useState(false); return ( @@ -89,7 +91,7 @@ export const HotPhase: FunctionComponent = () => { })} paddingSize="m" > - {i18n.translate('xpack.indexLifecycleMgmt.hotPhase.rolloverFieldTitle', { @@ -98,143 +100,192 @@ export const HotPhase: FunctionComponent = () => { } description={ - -

- {' '} - + +

+ {' '} + + } + docPath="indices-rollover-index.html" + /> +

+
+ + path={isUsingDefaultRolloverPath}> + {(field) => ( + <> + field.setValue(e.target.checked)} + data-test-subj="useDefaultRolloverSwitch" + /> +   + + } /> - } - docPath="indices-rollover-index.html" - /> -

- + + )} + + } - switchProps={{ - path: '_meta.hot.useRollover', - 'data-test-subj': 'rolloverSwitch', - }} fullWidth > - {isRolloverEnabled && ( - <> - - {showEmptyRolloverFieldsError && ( +
+ path="_meta.hot.useRollover"> + {(field) => ( <> - -
{i18nTexts.editPolicy.errors.rollOverConfigurationCallout.body}
-
- - - )} - - - - {(field) => { - const showErrorCallout = field.errors.some( - (e) => e.code === ROLLOVER_EMPTY_VALIDATION - ); - if (showErrorCallout !== showEmptyRolloverFieldsError) { - setShowEmptyRolloverFieldsError(showErrorCallout); - } - return ( - - ); - }} - - - - field.setValue(e.target.checked)} + data-test-subj="rolloverSwitch" /> - - - - - - - - - - - - + } /> - - - - - - - )} - - {isRolloverEnabled && ( + + )} + + {isUsingRollover && ( + <> + + {showEmptyRolloverFieldsError && ( + <> + +
{i18nTexts.editPolicy.errors.rollOverConfigurationCallout.body}
+
+ + + )} + + + + {(field) => { + const showErrorCallout = field.errors.some( + (e) => e.code === ROLLOVER_EMPTY_VALIDATION + ); + if (showErrorCallout !== showEmptyRolloverFieldsError) { + setShowEmptyRolloverFieldsError(showErrorCallout); + } + return ( + + ); + }} + + + + + + + + + + + + + + + + + + + + + + + )} +
+ + {isUsingRollover && ( <> {} diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/index.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/index.ts index e56b0b21491f3..15167672265fd 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/index.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/index.ts @@ -4,8 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -export { useRolloverPath } from '../../../constants'; - export { DataTierAllocationField } from './data_tier_allocation_field'; export { ForcemergeField } from './forcemerge_field'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/min_age_input_field/min_age_input_field.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/min_age_input_field/min_age_input_field.tsx index f37c387354418..59086ce572252 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/min_age_input_field/min_age_input_field.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/min_age_input_field/min_age_input_field.tsx @@ -4,21 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ import React, { FunctionComponent } from 'react'; -import { get } from 'lodash'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { - useFormData, - UseField, - NumericField, - SelectField, -} from '../../../../../../../shared_imports'; +import { UseField, NumericField, SelectField } from '../../../../../../../shared_imports'; import { LearnMoreLink } from '../../../learn_more_link'; -import { useRolloverPath } from '../../../../constants'; +import { useConfigurationIssues } from '../../../../form'; import { getUnitsAriaLabelForPhase, getTimingLabelForPhase } from './util'; @@ -29,8 +23,7 @@ interface Props { } export const MinAgeInputField: FunctionComponent = ({ phase }): React.ReactElement => { - const [formData] = useFormData({ watch: useRolloverPath }); - const rolloverEnabled = get(formData, useRolloverPath); + const { isUsingRollover: rolloverEnabled } = useConfigurationIssues(); let daysOptionLabel; let hoursOptionLabel; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/searchable_snapshot_field/searchable_snapshot_field.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/searchable_snapshot_field/searchable_snapshot_field.tsx index 2a55cee0794c5..3157c0a51accf 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/searchable_snapshot_field/searchable_snapshot_field.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/searchable_snapshot_field/searchable_snapshot_field.tsx @@ -29,8 +29,6 @@ import { useConfigurationIssues } from '../../../../form'; import { i18nTexts } from '../../../../i18n_texts'; -import { useRolloverPath } from '../../../../constants'; - import { FieldLoadingError, DescribedFormRow, LearnMoreLink } from '../../../'; import { SearchableSnapshotDataProvider } from './searchable_snapshot_data_provider'; @@ -54,17 +52,16 @@ export const SearchableSnapshotField: FunctionComponent = ({ phase }) => services: { cloud }, } = useKibana(); const { getUrlForApp, policy, license } = useEditPolicyContext(); - const { isUsingSearchableSnapshotInHotPhase } = useConfigurationIssues(); + const { isUsingSearchableSnapshotInHotPhase, isUsingRollover } = useConfigurationIssues(); const searchableSnapshotPath = `phases.${phase}.actions.searchable_snapshot.snapshot_repository`; - const [formData] = useFormData({ watch: [searchableSnapshotPath, useRolloverPath] }); - const isRolloverEnabled = get(formData, useRolloverPath); + const [formData] = useFormData({ watch: searchableSnapshotPath }); const searchableSnapshotRepo = get(formData, searchableSnapshotPath); const isDisabledDueToLicense = !license.canUseSearchableSnapshot(); const isDisabledInColdDueToHotPhase = phase === 'cold' && isUsingSearchableSnapshotInHotPhase; - const isDisabledInColdDueToRollover = phase === 'cold' && !isRolloverEnabled; + const isDisabledInColdDueToRollover = phase === 'cold' && !isUsingRollover; const isDisabled = isDisabledDueToLicense || isDisabledInColdDueToHotPhase || isDisabledInColdDueToRollover; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/warm_phase/warm_phase.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/warm_phase/warm_phase.tsx index 36a39eb7c110f..77078e94d7e98 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/warm_phase/warm_phase.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/warm_phase/warm_phase.tsx @@ -21,7 +21,6 @@ import { useConfigurationIssues } from '../../../form'; import { ActiveBadge, DescribedFormRow } from '../../'; import { - useRolloverPath, MinAgeInputField, ForcemergeField, SetPriorityInputField, @@ -47,13 +46,12 @@ const formFieldPaths = { export const WarmPhase: FunctionComponent = () => { const { policy } = useEditPolicyContext(); - const { isUsingSearchableSnapshotInHotPhase } = useConfigurationIssues(); + const { isUsingSearchableSnapshotInHotPhase, isUsingRollover } = useConfigurationIssues(); const [formData] = useFormData({ - watch: [useRolloverPath, formFieldPaths.enabled, formFieldPaths.warmPhaseOnRollover], + watch: [formFieldPaths.enabled, formFieldPaths.warmPhaseOnRollover], }); const enabled = get(formData, formFieldPaths.enabled); - const hotPhaseRolloverEnabled = get(formData, useRolloverPath); const warmPhaseOnRollover = get(formData, formFieldPaths.warmPhaseOnRollover); return ( @@ -99,7 +97,7 @@ export const WarmPhase: FunctionComponent = () => { <> {enabled && ( <> - {hotPhaseRolloverEnabled && ( + {isUsingRollover && ( { }} /> )} - {(!warmPhaseOnRollover || !hotPhaseRolloverEnabled) && ( + {(!warmPhaseOnRollover || !isUsingRollover) && ( <> diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/constants.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/constants.ts index a5d5f1c62847c..48ed38fc8a0d7 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/constants.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/constants.ts @@ -6,6 +6,8 @@ export const useRolloverPath = '_meta.hot.useRollover'; +export const isUsingDefaultRolloverPath = '_meta.hot.isUsingDefaultRollover'; + /** * These strings describe the path to their respective values in the serialized * ILM form. diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/configuration_issues_context.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/configuration_issues_context.tsx index c31eb5bdaa329..3a66abebccc1a 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/configuration_issues_context.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/configuration_issues_context.tsx @@ -6,10 +6,17 @@ import { get } from 'lodash'; import React, { FunctionComponent, createContext, useContext } from 'react'; + import { useFormData } from '../../../../shared_imports'; +import { isUsingDefaultRolloverPath, useRolloverPath } from '../constants'; + export interface ConfigurationIssues { - isUsingForceMergeInHotPhase: boolean; + /** + * Whether the serialized policy will use rollover. This blocks certain actions in + * the form such as hot phase (forcemerge, shrink) and cold phase (searchable snapshot). + */ + isUsingRollover: boolean; /** * If this value is true, phases after hot cannot set shrink, forcemerge, freeze, or * searchable_snapshot actions. @@ -24,18 +31,18 @@ const ConfigurationIssuesContext = createContext(null as an const pathToHotPhaseSearchableSnapshot = 'phases.hot.actions.searchable_snapshot.snapshot_repository'; -const pathToHotForceMerge = 'phases.hot.actions.forcemerge.max_num_segments'; - export const ConfigurationIssuesProvider: FunctionComponent = ({ children }) => { const [formData] = useFormData({ - watch: [pathToHotPhaseSearchableSnapshot, pathToHotForceMerge], + watch: [pathToHotPhaseSearchableSnapshot, useRolloverPath, isUsingDefaultRolloverPath], }); + const isUsingDefaultRollover = get(formData, isUsingDefaultRolloverPath); + const rolloverSwitchEnabled = get(formData, useRolloverPath); return ( {children} diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/deserializer.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/deserializer.ts index 0a85a376a9ab4..160c3987f8898 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/deserializer.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/deserializer.ts @@ -10,7 +10,7 @@ import { SerializedPolicy } from '../../../../../common/types'; import { splitSizeAndUnits } from '../../../lib/policies'; -import { determineDataTierAllocationType } from '../../../lib'; +import { determineDataTierAllocationType, isUsingDefaultRollover } from '../../../lib'; import { FormInternal } from '../types'; @@ -22,6 +22,7 @@ export const deserializer = (policy: SerializedPolicy): FormInternal => { const _meta: FormInternal['_meta'] = { hot: { useRollover: Boolean(hot?.actions?.rollover), + isUsingDefaultRollover: isUsingDefaultRollover(policy), bestCompression: hot?.actions?.forcemerge?.index_codec === 'best_compression', readonlyEnabled: Boolean(hot?.actions?.readonly), }, diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/schema.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/schema.ts index a292a888e78c4..ae2432971059c 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/schema.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/schema.ts @@ -38,6 +38,12 @@ export const schema: FormSchema = { defaultMessage: 'Enable rollover', }), }, + isUsingDefaultRollover: { + defaultValue: true, + label: i18n.translate('xpack.indexLifecycleMgmt.hotPhase.isUsingDefaultRollover', { + defaultMessage: 'Use recommended defaults', + }), + }, maxStorageSizeUnit: { defaultValue: 'gb', }, diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/serializer/serializer.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/serializer/serializer.ts index 75935f149534e..2a7689b42554e 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/serializer/serializer.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/serializer/serializer.ts @@ -6,11 +6,11 @@ import { produce } from 'immer'; -import { merge } from 'lodash'; +import { merge, cloneDeep } from 'lodash'; import { SerializedPolicy } from '../../../../../../common/types'; -import { defaultPolicy } from '../../../../constants'; +import { defaultPolicy, defaultRolloverAction } from '../../../../constants'; import { FormInternal } from '../../types'; @@ -42,7 +42,9 @@ export const createSerializer = (originalPolicy?: SerializedPolicy) => ( if (draft.phases.hot?.actions) { const hotPhaseActions = draft.phases.hot.actions; - if (hotPhaseActions.rollover && _meta.hot.useRollover) { + if (_meta.hot.isUsingDefaultRollover) { + hotPhaseActions.rollover = cloneDeep(defaultRolloverAction); + } else if (hotPhaseActions.rollover && _meta.hot.useRollover) { if (updatedPolicy.phases.hot!.actions.rollover?.max_age) { hotPhaseActions.rollover.max_age = `${hotPhaseActions.rollover.max_age}${_meta.hot.maxAgeUnit}`; } else { diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/types.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/types.ts index f04acea0bbf0a..4dfd7503b9973 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/types.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/types.ts @@ -23,6 +23,7 @@ export interface ForcemergeFields { interface HotPhaseMetaFields extends ForcemergeFields { useRollover: boolean; + isUsingDefaultRollover: boolean; maxStorageSizeUnit?: string; maxAgeUnit?: string; readonlyEnabled: boolean; From dd7bbb817b168e7033f419c6a67c1a980175a152 Mon Sep 17 00:00:00 2001 From: Kerry Gallagher Date: Mon, 14 Dec 2020 20:59:12 +0000 Subject: [PATCH 43/95] [Synthetics] Waterfall view (#84821) * Add a new synthetics step detail page for displaying waterfall data --- x-pack/plugins/uptime/common/constants/ui.ts | 2 + .../uptime/common/runtime_types/index.ts | 1 + .../common/runtime_types/network_events.ts | 48 ++ .../uptime/common/runtime_types/ping/ping.ts | 30 +- .../components/common/header/page_header.tsx | 10 +- .../components/common/step_detail_link.tsx | 32 + .../__tests__/executed_journey.test.tsx | 2 + .../__tests__/executed_step.test.tsx | 352 ++++++++- .../monitor/synthetics/executed_journey.tsx | 2 +- .../monitor/synthetics/executed_step.tsx | 160 ++-- .../synthetics/step_detail/step_detail.tsx | 142 ++++ .../step_detail/step_detail_container.tsx | 114 +++ .../waterfall/data_formatting.test.ts | 27 + .../step_detail/waterfall/data_formatting.ts | 209 ++++++ .../waterfall}/types.ts | 19 +- .../waterfall/waterfall_chart_container.tsx | 66 ++ .../waterfall}/waterfall_chart_wrapper.tsx | 13 +- .../waterfall/components/constants.ts | 3 + .../waterfall/components/sidebar.tsx | 6 +- .../synthetics/waterfall/components/styles.ts | 5 +- .../waterfall/components/waterfall_chart.tsx | 27 +- .../synthetics/data_formatting.test.ts | 687 ------------------ .../consumers/synthetics/data_formatting.ts | 336 --------- .../uptime/public/hooks/use_telemetry.ts | 1 + x-pack/plugins/uptime/public/pages/index.ts | 1 + .../uptime/public/pages/step_detail_page.tsx | 20 + x-pack/plugins/uptime/public/routes.tsx | 10 +- .../public/state/actions/network_events.ts | 27 + .../uptime/public/state/api/network_events.ts | 25 + .../uptime/public/state/effects/index.ts | 2 + .../public/state/effects/network_events.ts | 39 + .../uptime/public/state/reducers/index.ts | 2 + .../uptime/public/state/reducers/journey.ts | 4 +- .../public/state/reducers/network_events.ts | 122 ++++ .../state/selectors/__tests__/index.test.ts | 1 + .../uptime/public/state/selectors/index.ts | 2 + .../__tests__/get_network_events.test.ts | 251 +++++++ .../lib/requests/get_journey_details.ts | 127 ++++ .../server/lib/requests/get_network_events.ts | 59 ++ .../uptime/server/lib/requests/index.ts | 4 + .../plugins/uptime/server/rest_api/index.ts | 2 + .../network_events/get_network_events.ts | 33 + .../server/rest_api/network_events/index.ts | 7 + .../uptime/server/rest_api/pings/journeys.ts | 6 + 44 files changed, 1892 insertions(+), 1146 deletions(-) create mode 100644 x-pack/plugins/uptime/common/runtime_types/network_events.ts create mode 100644 x-pack/plugins/uptime/public/components/common/step_detail_link.tsx create mode 100644 x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/step_detail.tsx create mode 100644 x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/step_detail_container.tsx create mode 100644 x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/data_formatting.test.ts create mode 100644 x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/data_formatting.ts rename x-pack/plugins/uptime/public/components/monitor/synthetics/{waterfall/consumers/synthetics => step_detail/waterfall}/types.ts (86%) create mode 100644 x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_container.tsx rename x-pack/plugins/uptime/public/components/monitor/synthetics/{waterfall/consumers/synthetics => step_detail/waterfall}/waterfall_chart_wrapper.tsx (91%) delete mode 100644 x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/consumers/synthetics/data_formatting.test.ts delete mode 100644 x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/consumers/synthetics/data_formatting.ts create mode 100644 x-pack/plugins/uptime/public/pages/step_detail_page.tsx create mode 100644 x-pack/plugins/uptime/public/state/actions/network_events.ts create mode 100644 x-pack/plugins/uptime/public/state/api/network_events.ts create mode 100644 x-pack/plugins/uptime/public/state/effects/network_events.ts create mode 100644 x-pack/plugins/uptime/public/state/reducers/network_events.ts create mode 100644 x-pack/plugins/uptime/server/lib/requests/__tests__/get_network_events.test.ts create mode 100644 x-pack/plugins/uptime/server/lib/requests/get_journey_details.ts create mode 100644 x-pack/plugins/uptime/server/lib/requests/get_network_events.ts create mode 100644 x-pack/plugins/uptime/server/rest_api/network_events/get_network_events.ts create mode 100644 x-pack/plugins/uptime/server/rest_api/network_events/index.ts diff --git a/x-pack/plugins/uptime/common/constants/ui.ts b/x-pack/plugins/uptime/common/constants/ui.ts index 2fc7c33e71630..24482004ba4e8 100644 --- a/x-pack/plugins/uptime/common/constants/ui.ts +++ b/x-pack/plugins/uptime/common/constants/ui.ts @@ -12,6 +12,8 @@ export const SETTINGS_ROUTE = '/settings'; export const CERTIFICATES_ROUTE = '/certificates'; +export const STEP_DETAIL_ROUTE = '/journey/:checkGroupId/step/:stepIndex'; + export enum STATUS { UP = 'up', DOWN = 'down', diff --git a/x-pack/plugins/uptime/common/runtime_types/index.ts b/x-pack/plugins/uptime/common/runtime_types/index.ts index e80471bf8b56f..43487eca69e9b 100644 --- a/x-pack/plugins/uptime/common/runtime_types/index.ts +++ b/x-pack/plugins/uptime/common/runtime_types/index.ts @@ -12,3 +12,4 @@ export * from './monitor'; export * from './overview_filters'; export * from './ping'; export * from './snapshot'; +export * from './network_events'; diff --git a/x-pack/plugins/uptime/common/runtime_types/network_events.ts b/x-pack/plugins/uptime/common/runtime_types/network_events.ts new file mode 100644 index 0000000000000..6104758f28fd8 --- /dev/null +++ b/x-pack/plugins/uptime/common/runtime_types/network_events.ts @@ -0,0 +1,48 @@ +/* + * 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'; + +const NetworkTimingsType = t.type({ + queueing: t.number, + connect: t.number, + total: t.number, + send: t.number, + blocked: t.number, + receive: t.number, + wait: t.number, + dns: t.number, + proxy: t.number, + ssl: t.number, +}); + +export type NetworkTimings = t.TypeOf; + +const NetworkEventType = t.intersection([ + t.type({ + timestamp: t.string, + requestSentTime: t.number, + loadEndTime: t.number, + }), + t.partial({ + method: t.string, + url: t.string, + status: t.number, + mimeType: t.string, + requestStartTime: t.number, + timings: NetworkTimingsType, + }), +]); + +export type NetworkEvent = t.TypeOf; + +export const SyntheticsNetworkEventsApiResponseType = t.type({ + events: t.array(NetworkEventType), +}); + +export type SyntheticsNetworkEventsApiResponse = t.TypeOf< + typeof SyntheticsNetworkEventsApiResponseType +>; diff --git a/x-pack/plugins/uptime/common/runtime_types/ping/ping.ts b/x-pack/plugins/uptime/common/runtime_types/ping/ping.ts index 17b2d143eeab0..bbb6a8b915e05 100644 --- a/x-pack/plugins/uptime/common/runtime_types/ping/ping.ts +++ b/x-pack/plugins/uptime/common/runtime_types/ping/ping.ts @@ -240,10 +240,32 @@ export const PingType = t.intersection([ }), ]); -export const SyntheticsJourneyApiResponseType = t.type({ - checkGroup: t.string, - steps: t.array(PingType), -}); +export const SyntheticsJourneyApiResponseType = t.intersection([ + t.type({ + checkGroup: t.string, + steps: t.array(PingType), + }), + t.partial({ + details: t.union([ + t.intersection([ + t.type({ + timestamp: t.string, + }), + t.partial({ + next: t.type({ + timestamp: t.string, + checkGroup: t.string, + }), + previous: t.type({ + timestamp: t.string, + checkGroup: t.string, + }), + }), + ]), + t.null, + ]), + }), +]); export type SyntheticsJourneyApiResponse = t.TypeOf; diff --git a/x-pack/plugins/uptime/public/components/common/header/page_header.tsx b/x-pack/plugins/uptime/public/components/common/header/page_header.tsx index 63bcb6663619d..7d093efd31be0 100644 --- a/x-pack/plugins/uptime/public/components/common/header/page_header.tsx +++ b/x-pack/plugins/uptime/public/components/common/header/page_header.tsx @@ -11,7 +11,12 @@ import { useRouteMatch } from 'react-router-dom'; import { UptimeDatePicker } from '../uptime_date_picker'; import { SyntheticsCallout } from '../../overview/synthetics_callout'; import { PageTabs } from './page_tabs'; -import { CERTIFICATES_ROUTE, MONITOR_ROUTE, SETTINGS_ROUTE } from '../../../../common/constants'; +import { + CERTIFICATES_ROUTE, + MONITOR_ROUTE, + SETTINGS_ROUTE, + STEP_DETAIL_ROUTE, +} from '../../../../common/constants'; import { CertRefreshBtn } from '../../certificates/cert_refresh_btn'; import { ToggleAlertFlyoutButton } from '../../overview/alerts/alerts_containers'; @@ -34,11 +39,12 @@ const StyledPicker = styled(EuiFlexItem)` export const PageHeader = () => { const isCertRoute = useRouteMatch(CERTIFICATES_ROUTE); const isSettingsRoute = useRouteMatch(SETTINGS_ROUTE); + const isStepDetailRoute = useRouteMatch(STEP_DETAIL_ROUTE); const DatePickerComponent = () => isCertRoute ? ( - ) : ( + ) : isStepDetailRoute ? null : ( diff --git a/x-pack/plugins/uptime/public/components/common/step_detail_link.tsx b/x-pack/plugins/uptime/public/components/common/step_detail_link.tsx new file mode 100644 index 0000000000000..a8e4c90f2d29a --- /dev/null +++ b/x-pack/plugins/uptime/public/components/common/step_detail_link.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 React, { FC } from 'react'; +import { EuiLink } from '@elastic/eui'; +import { Link } from 'react-router-dom'; + +interface StepDetailLinkProps { + /** + * The ID of the check group (the journey run) + */ + checkGroupId: string; + /** + * The index of the step + */ + stepIndex: number; +} + +export const StepDetailLink: FC = ({ children, checkGroupId, stepIndex }) => { + const to = `/journey/${checkGroupId}/step/${stepIndex}`; + + return ( + + + {children} + + + ); +}; diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/__tests__/executed_journey.test.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/__tests__/executed_journey.test.tsx index d6f422b5c7094..030b1a49009ef 100644 --- a/x-pack/plugins/uptime/public/components/monitor/synthetics/__tests__/executed_journey.test.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/__tests__/executed_journey.test.tsx @@ -206,6 +206,7 @@ describe('ExecutedJourney component', () => { direction="column" > { } /> { let step: Ping; @@ -34,8 +34,11 @@ describe('ExecutedStep', () => { }); it('renders correct step heading', () => { - expect(mountWithIntl().find('EuiText')) - .toMatchInlineSnapshot(` + expect( + mountWithRouter().find( + 'EuiText' + ) + ).toMatchInlineSnapshot(`
{ `); }); + it('renders a link to the step detail view', () => { + expect( + mountWithRouter().find( + 'StepDetailLink' + ) + ).toMatchInlineSnapshot(` + + + + + + `); + }); + it('supplies status badge correct status', () => { step.synthetics = { payload: { status: 'THE_STATUS' }, }; - expect(shallowWithIntl().find('StatusBadge')) - .toMatchInlineSnapshot(` + expect( + mountWithRouter().find( + 'StatusBadge' + ) + ).toMatchInlineSnapshot(` + > + + + + + + + + `); }); @@ -86,8 +170,11 @@ describe('ExecutedStep', () => { }, }; - expect(shallowWithIntl().find('CodeBlockAccordion')) - .toMatchInlineSnapshot(` + expect( + mountWithRouter().find( + 'CodeBlockAccordion' + ) + ).toMatchInlineSnapshot(` Array [ { language="javascript" overflowHeight={360} > - const someVar = "the var" + +
+
+ +
+
+ +
+
+ + +
+
+                              
+                                const someVar = "the var"
+                              
+                            
+
+
+
+
+
+
+
+
+
, { language="html" overflowHeight={360} > - There was an error executing the step. + +
+
+ +
+
+ +
+
+ + +
+
+                              
+                                There was an error executing the step.
+                              
+                            
+
+
+
+
+
+
+
+
+
, { language="html" overflowHeight={360} > - some.stack.trace.string + +
+
+ +
+
+ +
+
+ + +
+
+                              
+                                some.stack.trace.string
+                              
+                            
+
+
+
+
+
+
+
+
+
, ] `); diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/executed_journey.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/executed_journey.tsx index 0c47e4c73e976..a9748524d1bb3 100644 --- a/x-pack/plugins/uptime/public/components/monitor/synthetics/executed_journey.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/executed_journey.tsx @@ -78,7 +78,7 @@ export const ExecutedJourney: FC = ({ journey }) => { {journey.steps.filter(isStepEnd).map((step, index) => ( - + ))} diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/executed_step.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/executed_step.tsx index 5966851973af2..01a599f8e8a60 100644 --- a/x-pack/plugins/uptime/public/components/monitor/synthetics/executed_step.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/executed_step.tsx @@ -12,80 +12,104 @@ import { CodeBlockAccordion } from './code_block_accordion'; import { StepScreenshotDisplay } from './step_screenshot_display'; import { StatusBadge } from './status_badge'; import { Ping } from '../../../../common/runtime_types'; +import { StepDetailLink } from '../../common/step_detail_link'; const CODE_BLOCK_OVERFLOW_HEIGHT = 360; interface ExecutedStepProps { step: Ping; index: number; + checkGroup: string; } -export const ExecutedStep: FC = ({ step, index }) => ( - <> -
-
- - - - - +export const ExecutedStep: FC = ({ step, index, checkGroup }) => { + return ( + <> +
+
+ {step.synthetics?.step?.index && checkGroup ? ( + + + + + + + + ) : ( + + + + + + )} +
+ +
+ +
+ +
+ + + + + + + {step.synthetics?.payload?.source} + + + {step.synthetics?.error?.message} + + + {step.synthetics?.error?.stack} + + + +
- -
- -
- -
- - - - - - - {step.synthetics?.payload?.source} - - - {step.synthetics?.error?.message} - - - {step.synthetics?.error?.stack} - - - -
-
- -); + + ); +}; diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/step_detail.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/step_detail.tsx new file mode 100644 index 0000000000000..fd68edef3226b --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/step_detail.tsx @@ -0,0 +1,142 @@ +/* + * 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 { + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiTitle, + EuiButtonEmpty, + EuiText, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import React from 'react'; +import moment from 'moment'; +import { WaterfallChartContainer } from './waterfall/waterfall_chart_container'; + +export const PREVIOUS_CHECK_BUTTON_TEXT = i18n.translate( + 'xpack.uptime.synthetics.stepDetail.previousCheckButtonText', + { + defaultMessage: 'Previous check', + } +); + +export const NEXT_CHECK_BUTTON_TEXT = i18n.translate( + 'xpack.uptime.synthetics.stepDetail.nextCheckButtonText', + { + defaultMessage: 'Next check', + } +); + +interface Props { + checkGroup: string; + stepName?: string; + stepIndex: number; + totalSteps: number; + hasPreviousStep: boolean; + hasNextStep: boolean; + handlePreviousStep: () => void; + handleNextStep: () => void; + handleNextRun: () => void; + handlePreviousRun: () => void; + previousCheckGroup?: string; + nextCheckGroup?: string; + checkTimestamp?: string; + dateFormat: string; +} + +export const StepDetail: React.FC = ({ + dateFormat, + stepName, + checkGroup, + stepIndex, + totalSteps, + hasPreviousStep, + hasNextStep, + handlePreviousStep, + handleNextStep, + handlePreviousRun, + handleNextRun, + previousCheckGroup, + nextCheckGroup, + checkTimestamp, +}) => { + return ( + <> + + + + + +

{stepName}

+
+
+ + + + + + + + + + + + + + + +
+
+ + + + + {PREVIOUS_CHECK_BUTTON_TEXT} + + + + {moment(checkTimestamp).format(dateFormat)} + + + + {NEXT_CHECK_BUTTON_TEXT} + + + + +
+ + + + ); +}; diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/step_detail_container.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/step_detail_container.tsx new file mode 100644 index 0000000000000..58cf8d6e492da --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/step_detail_container.tsx @@ -0,0 +1,114 @@ +/* + * 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 { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiText, EuiLoadingSpinner } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React, { useEffect, useCallback, useMemo } from 'react'; +import { useSelector, useDispatch } from 'react-redux'; +import { useHistory } from 'react-router-dom'; +import moment from 'moment'; +import { useBreadcrumbs } from '../../../../hooks/use_breadcrumbs'; +import { getJourneySteps } from '../../../../state/actions/journey'; +import { journeySelector } from '../../../../state/selectors'; +import { useUiSetting$ } from '../../../../../../../../src/plugins/kibana_react/public'; +import { StepDetail } from './step_detail'; + +export const NO_STEP_DATA = i18n.translate('xpack.uptime.synthetics.stepDetail.noData', { + defaultMessage: 'No data could be found for this step', +}); + +interface Props { + checkGroup: string; + stepIndex: number; +} + +export const StepDetailContainer: React.FC = ({ checkGroup, stepIndex }) => { + const dispatch = useDispatch(); + const history = useHistory(); + + const [dateFormat] = useUiSetting$('dateFormat'); + + useEffect(() => { + if (checkGroup) { + dispatch(getJourneySteps({ checkGroup })); + } + }, [dispatch, checkGroup]); + + const journeys = useSelector(journeySelector); + const journey = journeys[checkGroup ?? '']; + + const { activeStep, hasPreviousStep, hasNextStep } = useMemo(() => { + return { + hasPreviousStep: stepIndex > 1 ? true : false, + activeStep: journey?.steps?.find((step) => step.synthetics?.step?.index === stepIndex), + hasNextStep: journey && journey.steps && stepIndex < journey.steps.length ? true : false, + }; + }, [stepIndex, journey]); + + useBreadcrumbs([ + ...(activeStep?.monitor?.name ? [{ text: activeStep?.monitor?.name }] : []), + ...(journey?.details?.timestamp + ? [{ text: moment(journey?.details?.timestamp).format(dateFormat) }] + : []), + ]); + + const handleNextStep = useCallback(() => { + history.push(`/journey/${checkGroup}/step/${stepIndex + 1}`); + }, [history, checkGroup, stepIndex]); + + const handlePreviousStep = useCallback(() => { + history.push(`/journey/${checkGroup}/step/${stepIndex - 1}`); + }, [history, checkGroup, stepIndex]); + + const handleNextRun = useCallback(() => { + history.push(`/journey/${journey?.details?.next?.checkGroup}/step/1`); + }, [history, journey?.details?.next?.checkGroup]); + + const handlePreviousRun = useCallback(() => { + history.push(`/journey/${journey?.details?.previous?.checkGroup}/step/1`); + }, [history, journey?.details?.previous?.checkGroup]); + + return ( + <> + + {(!journey || journey.loading) && ( + + + + + + )} + {journey && !activeStep && !journey.loading && ( + + + +

{NO_STEP_DATA}

+
+
+
+ )} + {journey && activeStep && !journey.loading && ( + + )} +
+ + ); +}; diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/data_formatting.test.ts b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/data_formatting.test.ts new file mode 100644 index 0000000000000..fff14376667b2 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/data_formatting.test.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 { colourPalette } from './data_formatting'; + +describe('Palettes', () => { + it('A colour palette comprising timing and mime type colours is correctly generated', () => { + expect(colourPalette).toEqual({ + blocked: '#b9a888', + connect: '#da8b45', + dns: '#54b399', + font: '#aa6556', + html: '#f3b3a6', + media: '#d6bf57', + other: '#e7664c', + receive: '#54b399', + script: '#9170b8', + send: '#d36086', + ssl: '#edc5a2', + stylesheet: '#ca8eae', + wait: '#b0c9e0', + }); + }); +}); diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/data_formatting.ts b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/data_formatting.ts new file mode 100644 index 0000000000000..7c6e176315b5b --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/data_formatting.ts @@ -0,0 +1,209 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { euiPaletteColorBlind } from '@elastic/eui'; + +import { + NetworkItems, + NetworkItem, + FriendlyTimingLabels, + FriendlyMimetypeLabels, + MimeType, + MimeTypesMap, + Timings, + TIMING_ORDER, + SidebarItems, + LegendItems, +} from './types'; +import { WaterfallData } from '../../waterfall'; +import { NetworkEvent } from '../../../../../../common/runtime_types'; + +export const extractItems = (data: NetworkEvent[]): NetworkItems => { + // NOTE: This happens client side as the "payload" property is mapped + // in such a way it can't be queried (or sorted on) via ES. + return data.sort((a: NetworkItem, b: NetworkItem) => { + return a.requestSentTime - b.requestSentTime; + }); +}; + +const formatValueForDisplay = (value: number, points: number = 3) => { + return Number(value).toFixed(points); +}; + +const getColourForMimeType = (mimeType?: string) => { + const key = mimeType && MimeTypesMap[mimeType] ? MimeTypesMap[mimeType] : MimeType.Other; + return colourPalette[key]; +}; + +export const getSeriesAndDomain = (items: NetworkItems) => { + const getValueForOffset = (item: NetworkItem) => { + return item.requestSentTime; + }; + + // The earliest point in time a request is sent or started. This will become our notion of "0". + const zeroOffset = items.reduce((acc, item) => { + const offsetValue = getValueForOffset(item); + return offsetValue < acc ? offsetValue : acc; + }, Infinity); + + const getValue = (timings: NetworkEvent['timings'], timing: Timings) => { + if (!timings) return; + + // SSL is a part of the connect timing + if (timing === Timings.Connect && timings.ssl > 0) { + return timings.connect - timings.ssl; + } else { + return timings[timing]; + } + }; + + const series = items.reduce((acc, item, index) => { + if (!item.timings) return acc; + + const offsetValue = getValueForOffset(item); + + let currentOffset = offsetValue - zeroOffset; + + TIMING_ORDER.forEach((timing) => { + const value = getValue(item.timings, timing); + const colour = + timing === Timings.Receive ? getColourForMimeType(item.mimeType) : colourPalette[timing]; + if (value && value >= 0) { + const y = currentOffset + value; + + acc.push({ + x: index, + y0: currentOffset, + y, + config: { + colour, + tooltipProps: { + value: `${FriendlyTimingLabels[timing]}: ${formatValueForDisplay( + y - currentOffset + )}ms`, + colour, + }, + }, + }); + currentOffset = y; + } + }); + return acc; + }, []); + + const yValues = series.map((serie) => serie.y); + const domain = { min: 0, max: Math.max(...yValues) }; + return { series, domain }; +}; + +export const getSidebarItems = (items: NetworkItems): SidebarItems => { + return items.map((item) => { + const { url, status, method } = item; + return { url, status, method }; + }); +}; + +export const getLegendItems = (): LegendItems => { + let timingItems: LegendItems = []; + Object.values(Timings).forEach((timing) => { + // The "receive" timing is mapped to a mime type colour, so we don't need to show this in the legend + if (timing === Timings.Receive) { + return; + } + timingItems = [ + ...timingItems, + { name: FriendlyTimingLabels[timing], colour: TIMING_PALETTE[timing] }, + ]; + }); + + let mimeTypeItems: LegendItems = []; + Object.values(MimeType).forEach((mimeType) => { + mimeTypeItems = [ + ...mimeTypeItems, + { name: FriendlyMimetypeLabels[mimeType], colour: MIME_TYPE_PALETTE[mimeType] }, + ]; + }); + return [...timingItems, ...mimeTypeItems]; +}; + +// Timing colour palette +type TimingColourPalette = { + [K in Timings]: string; +}; + +const SAFE_PALETTE = euiPaletteColorBlind({ rotations: 2 }); + +const buildTimingPalette = (): TimingColourPalette => { + const palette = Object.values(Timings).reduce>((acc, value) => { + switch (value) { + case Timings.Blocked: + acc[value] = SAFE_PALETTE[6]; + break; + case Timings.Dns: + acc[value] = SAFE_PALETTE[0]; + break; + case Timings.Connect: + acc[value] = SAFE_PALETTE[7]; + break; + case Timings.Ssl: + acc[value] = SAFE_PALETTE[17]; + break; + case Timings.Send: + acc[value] = SAFE_PALETTE[2]; + break; + case Timings.Wait: + acc[value] = SAFE_PALETTE[11]; + break; + case Timings.Receive: + acc[value] = SAFE_PALETTE[0]; + break; + } + return acc; + }, {}); + + return palette as TimingColourPalette; +}; + +const TIMING_PALETTE = buildTimingPalette(); + +// MimeType colour palette +type MimeTypeColourPalette = { + [K in MimeType]: string; +}; + +const buildMimeTypePalette = (): MimeTypeColourPalette => { + const palette = Object.values(MimeType).reduce>((acc, value) => { + switch (value) { + case MimeType.Html: + acc[value] = SAFE_PALETTE[19]; + break; + case MimeType.Script: + acc[value] = SAFE_PALETTE[3]; + break; + case MimeType.Stylesheet: + acc[value] = SAFE_PALETTE[4]; + break; + case MimeType.Media: + acc[value] = SAFE_PALETTE[5]; + break; + case MimeType.Font: + acc[value] = SAFE_PALETTE[8]; + break; + case MimeType.Other: + acc[value] = SAFE_PALETTE[9]; + break; + } + return acc; + }, {}); + + return palette as MimeTypeColourPalette; +}; + +const MIME_TYPE_PALETTE = buildMimeTypePalette(); + +type ColourPalette = TimingColourPalette & MimeTypeColourPalette; + +export const colourPalette: ColourPalette = { ...TIMING_PALETTE, ...MIME_TYPE_PALETTE }; diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/consumers/synthetics/types.ts b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/types.ts similarity index 86% rename from x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/consumers/synthetics/types.ts rename to x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/types.ts index 1dd58b4f86db3..738929741ddaf 100644 --- a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/consumers/synthetics/types.ts +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/types.ts @@ -5,6 +5,7 @@ */ import { i18n } from '@kbn/i18n'; +import { NetworkEvent } from '../../../../../../common/runtime_types'; export enum Timings { Blocked = 'blocked', @@ -33,7 +34,7 @@ export const FriendlyTimingLabels = { } ), [Timings.Ssl]: i18n.translate('xpack.uptime.synthetics.waterfallChart.labels.timings.ssl', { - defaultMessage: 'SSL', + defaultMessage: 'TLS', }), [Timings.Send]: i18n.translate('xpack.uptime.synthetics.waterfallChart.labels.timings.send', { defaultMessage: 'Sending request', @@ -144,21 +145,7 @@ export const MimeTypesMap: Record = { 'application/font-sfnt': MimeType.Font, }; -export interface NetworkItem { - timestamp: string; - method: string; - url: string; - status: number; - mimeType?: string; - // NOTE: This is the time the request was actually issued. timing.request_time might be later if the request was queued. - requestSentTime: number; - responseReceivedTime: number; - // NOTE: Denotes the earlier figure out of request sent time and request start time (part of timings). This can vary based on queue times, and - // also whether an entry actually has timings available. - // Ref: https://github.com/ChromeDevTools/devtools-frontend/blob/ed2a064ac194bfae4e25c4748a9fa3513b3e9f7d/front_end/network/RequestTimingView.js#L154 - earliestRequestTime: number; - timings: CalculatedTimings | null; -} +export type NetworkItem = NetworkEvent; export type NetworkItems = NetworkItem[]; // NOTE: A number will always be present if the property exists, but that number might be -1, which represents no value. diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_container.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_container.tsx new file mode 100644 index 0000000000000..7657ca7f9c64a --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_container.tsx @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiFlexGroup, EuiFlexItem, EuiText, EuiLoadingChart } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React, { useEffect } from 'react'; +import { useSelector, useDispatch } from 'react-redux'; +import { getNetworkEvents } from '../../../../../state/actions/network_events'; +import { networkEventsSelector } from '../../../../../state/selectors'; +import { WaterfallChartWrapper } from './waterfall_chart_wrapper'; +import { extractItems } from './data_formatting'; + +export const NO_DATA_TEXT = i18n.translate('xpack.uptime.synthetics.stepDetail.waterfallNoData', { + defaultMessage: 'No waterfall data could be found for this step', +}); + +interface Props { + checkGroup: string; + stepIndex: number; +} + +export const WaterfallChartContainer: React.FC = ({ checkGroup, stepIndex }) => { + const dispatch = useDispatch(); + + useEffect(() => { + if (checkGroup && stepIndex) { + dispatch( + getNetworkEvents({ + checkGroup, + stepIndex, + }) + ); + } + }, [dispatch, stepIndex, checkGroup]); + + const _networkEvents = useSelector(networkEventsSelector); + const networkEvents = _networkEvents[checkGroup ?? '']?.[stepIndex]; + + return ( + <> + {!networkEvents || + (networkEvents.loading && ( + + + + + + ))} + {networkEvents && !networkEvents.loading && networkEvents.events.length === 0 && ( + + + +

{NO_DATA_TEXT}

+
+
+
+ )} + {networkEvents && !networkEvents.loading && networkEvents.events.length > 0 && ( + + )} + + ); +}; diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/consumers/synthetics/waterfall_chart_wrapper.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_wrapper.tsx similarity index 91% rename from x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/consumers/synthetics/waterfall_chart_wrapper.tsx rename to x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_wrapper.tsx index 434b44a94b79f..b10c3844f3002 100644 --- a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/consumers/synthetics/waterfall_chart_wrapper.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_wrapper.tsx @@ -13,7 +13,7 @@ import { WaterfallChart, MiddleTruncatedText, RenderItem, -} from '../../../waterfall'; +} from '../../waterfall'; const renderSidebarItem: RenderItem = (item, index) => { const { status } = item; @@ -27,7 +27,7 @@ const renderSidebarItem: RenderItem = (item, index) => { return ( <> - {!isErrorStatusCode(status) ? ( + {!status || !isErrorStatusCode(status) ? ( ) : ( @@ -47,9 +47,12 @@ const renderLegendItem: RenderItem = (item) => { return {item.name}; }; -export const WaterfallChartWrapper = () => { - // TODO: Will be sourced via an API - const [networkData] = useState([]); +interface Props { + data: NetworkItems; +} + +export const WaterfallChartWrapper: React.FC = ({ data }) => { + const [networkData] = useState(data); const { series, domain } = useMemo(() => { return getSeriesAndDomain(networkData); diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/constants.ts b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/constants.ts index ac650c5ef0ddd..95ec298e2e349 100644 --- a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/constants.ts +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/constants.ts @@ -10,3 +10,6 @@ export const BAR_HEIGHT = 32; export const MAIN_GROW_SIZE = 8; // Flex grow value export const SIDEBAR_GROW_SIZE = 2; +// Axis height +// NOTE: This isn't a perfect solution - changes in font size etc within charts could change the ideal height here. +export const FIXED_AXIS_HEIGHT = 32; diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/sidebar.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/sidebar.tsx index 9ff544fc1946b..c551561d5ad4f 100644 --- a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/sidebar.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/sidebar.tsx @@ -27,7 +27,11 @@ export const Sidebar: React.FC = ({ items, height, render }) => { - + {items.map((item, index) => { return ( diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/styles.ts b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/styles.ts index 25f5e5f8f5cc9..320e415585ca3 100644 --- a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/styles.ts +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/styles.ts @@ -6,9 +6,7 @@ import { EuiPanel, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { euiStyled } from '../../../../../../../observability/public'; - -// NOTE: This isn't a perfect solution - changes in font size etc within charts could change the ideal height here. -const FIXED_AXIS_HEIGHT = 33; +import { FIXED_AXIS_HEIGHT } from './constants'; interface WaterfallChartOuterContainerProps { height?: number; @@ -24,6 +22,7 @@ export const WaterfallChartFixedTopContainer = euiStyled.div` position: sticky; top: 0; z-index: ${(props) => props.theme.eui.euiZLevel4}; + border-bottom: ${(props) => `1px solid ${props.theme.eui.euiColorLightShade}`}; `; export const WaterfallChartFixedTopContainerSidebarCover = euiStyled(EuiPanel)` diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/waterfall_chart.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/waterfall_chart.tsx index de4be0ea34b2c..d92e26335a6bd 100644 --- a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/waterfall_chart.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/waterfall_chart.tsx @@ -33,7 +33,7 @@ import { WaterfallChartTooltip, } from './styles'; import { WaterfallData } from '../types'; -import { BAR_HEIGHT, MAIN_GROW_SIZE, SIDEBAR_GROW_SIZE } from './constants'; +import { BAR_HEIGHT, MAIN_GROW_SIZE, SIDEBAR_GROW_SIZE, FIXED_AXIS_HEIGHT } from './constants'; import { Sidebar } from './sidebar'; import { Legend } from './legend'; @@ -77,7 +77,8 @@ const getUniqueBars = (data: WaterfallData) => { }, new Set()); }; -const getChartHeight = (data: WaterfallData): number => getUniqueBars(data).size * BAR_HEIGHT; +const getChartHeight = (data: WaterfallData): number => + getUniqueBars(data).size * BAR_HEIGHT + FIXED_AXIS_HEIGHT; export const WaterfallChart = ({ tickFormat, @@ -85,7 +86,7 @@ export const WaterfallChart = ({ barStyleAccessor, renderSidebarItem, renderLegendItem, - maxHeight = 600, + maxHeight = 800, }: WaterfallChartProps) => { const { data, sidebarItems, legendItems } = useWaterfallContext(); @@ -108,10 +109,10 @@ export const WaterfallChart = ({ <> - + {shouldRenderSidebar && ( - + )} @@ -130,10 +131,13 @@ export const WaterfallChart = ({ tickFormat={tickFormat} domain={domain} showGridLines={true} + style={{ + axisLine: { + visible: false, + }, + }} /> - ''} /> - - + {shouldRenderSidebar && ( )} @@ -169,10 +173,13 @@ export const WaterfallChart = ({ tickFormat={tickFormat} domain={domain} showGridLines={true} + style={{ + axisLine: { + visible: false, + }, + }} /> - ''} /> - seconds * 1000; - -// describe('getTimings', () => { -// it('Calculates timings for network events correctly', () => { -// // NOTE: Uses these timings as the file protocol events don't have timing information -// const eventOneTimings = getTimings( -// TEST_DATA[0].synthetics.payload.response.timing!, -// toMillis(TEST_DATA[0].synthetics.payload.start), -// toMillis(TEST_DATA[0].synthetics.payload.end) -// ); -// expect(eventOneTimings).toEqual({ -// blocked: 162.4549999999106, -// connect: -1, -// dns: -1, -// receive: 0.5629999989271255, -// send: 0.5149999999999864, -// ssl: undefined, -// wait: 28.494, -// }); - -// const eventFourTimings = getTimings( -// TEST_DATA[3].synthetics.payload.response.timing!, -// toMillis(TEST_DATA[3].synthetics.payload.start), -// toMillis(TEST_DATA[3].synthetics.payload.end) -// ); -// expect(eventFourTimings).toEqual({ -// blocked: 1.8559999997466803, -// connect: 25.52200000000002, -// dns: 4.683999999999999, -// receive: 0.6780000009983667, -// send: 0.6490000000000009, -// ssl: 130.541, -// wait: 27.245000000000005, -// }); -// }); -// }); - -// describe('getSeriesAndDomain', () => { -// let seriesAndDomain: any; -// let NetworkItems: any; - -// beforeAll(() => { -// NetworkItems = extractItems(TEST_DATA); -// seriesAndDomain = getSeriesAndDomain(NetworkItems); -// }); - -// it('Correctly calculates the domain', () => { -// expect(seriesAndDomain.domain).toEqual({ max: 218.34699999913573, min: 0 }); -// }); - -// it('Correctly calculates the series', () => { -// expect(seriesAndDomain.series).toEqual([ -// { -// config: { colour: '#f3b3a6', tooltipProps: { colour: '#f3b3a6', value: '3.635ms' } }, -// x: 0, -// y: 3.6349999997764826, -// y0: 0, -// }, -// { -// config: { -// colour: '#b9a888', -// tooltipProps: { colour: '#b9a888', value: 'Queued / Blocked: 1.856ms' }, -// }, -// x: 1, -// y: 27.889999999731778, -// y0: 26.0339999999851, -// }, -// { -// config: { colour: '#54b399', tooltipProps: { colour: '#54b399', value: 'DNS: 4.684ms' } }, -// x: 1, -// y: 32.573999999731775, -// y0: 27.889999999731778, -// }, -// { -// config: { -// colour: '#da8b45', -// tooltipProps: { colour: '#da8b45', value: 'Connecting: 25.522ms' }, -// }, -// x: 1, -// y: 58.095999999731795, -// y0: 32.573999999731775, -// }, -// { -// config: { colour: '#edc5a2', tooltipProps: { colour: '#edc5a2', value: 'SSL: 130.541ms' } }, -// x: 1, -// y: 188.63699999973178, -// y0: 58.095999999731795, -// }, -// { -// config: { -// colour: '#d36086', -// tooltipProps: { colour: '#d36086', value: 'Sending request: 0.649ms' }, -// }, -// x: 1, -// y: 189.28599999973179, -// y0: 188.63699999973178, -// }, -// { -// config: { -// colour: '#b0c9e0', -// tooltipProps: { colour: '#b0c9e0', value: 'Waiting (TTFB): 27.245ms' }, -// }, -// x: 1, -// y: 216.5309999997318, -// y0: 189.28599999973179, -// }, -// { -// config: { -// colour: '#ca8eae', -// tooltipProps: { colour: '#ca8eae', value: 'Content downloading: 0.678ms' }, -// }, -// x: 1, -// y: 217.20900000073016, -// y0: 216.5309999997318, -// }, -// { -// config: { -// colour: '#b9a888', -// tooltipProps: { colour: '#b9a888', value: 'Queued / Blocked: 162.455ms' }, -// }, -// x: 2, -// y: 188.77500000020862, -// y0: 26.320000000298023, -// }, -// { -// config: { -// colour: '#d36086', -// tooltipProps: { colour: '#d36086', value: 'Sending request: 0.515ms' }, -// }, -// x: 2, -// y: 189.2900000002086, -// y0: 188.77500000020862, -// }, -// { -// config: { -// colour: '#b0c9e0', -// tooltipProps: { colour: '#b0c9e0', value: 'Waiting (TTFB): 28.494ms' }, -// }, -// x: 2, -// y: 217.7840000002086, -// y0: 189.2900000002086, -// }, -// { -// config: { -// colour: '#9170b8', -// tooltipProps: { colour: '#9170b8', value: 'Content downloading: 0.563ms' }, -// }, -// x: 2, -// y: 218.34699999913573, -// y0: 217.7840000002086, -// }, -// { -// config: { colour: '#9170b8', tooltipProps: { colour: '#9170b8', value: '12.139ms' } }, -// x: 3, -// y: 46.15699999965727, -// y0: 34.01799999922514, -// }, -// { -// config: { colour: '#9170b8', tooltipProps: { colour: '#9170b8', value: '8.453ms' } }, -// x: 4, -// y: 43.506999999284744, -// y0: 35.053999999538064, -// }, -// ]); -// }); -// }); - -describe('Palettes', () => { - it('A colour palette comprising timing and mime type colours is correctly generated', () => { - expect(colourPalette).toEqual({ - blocked: '#b9a888', - connect: '#da8b45', - dns: '#54b399', - font: '#aa6556', - html: '#f3b3a6', - media: '#d6bf57', - other: '#e7664c', - receive: '#54b399', - script: '#9170b8', - send: '#d36086', - ssl: '#edc5a2', - stylesheet: '#ca8eae', - wait: '#b0c9e0', - }); - }); -}); diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/consumers/synthetics/data_formatting.ts b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/consumers/synthetics/data_formatting.ts deleted file mode 100644 index 9c66ea638c942..0000000000000 --- a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/consumers/synthetics/data_formatting.ts +++ /dev/null @@ -1,336 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { euiPaletteColorBlind } from '@elastic/eui'; - -import { - PayloadTimings, - CalculatedTimings, - NetworkItems, - FriendlyTimingLabels, - FriendlyMimetypeLabels, - MimeType, - MimeTypesMap, - Timings, - TIMING_ORDER, - SidebarItems, - LegendItems, -} from './types'; -import { WaterfallData } from '../../../waterfall'; - -const microToMillis = (micro: number): number => (micro === -1 ? -1 : micro * 1000); - -// The timing calculations here are based off several sources: -// https://github.com/ChromeDevTools/devtools-frontend/blob/2fe91adefb2921b4deb2b4b125370ef9ccdb8d1b/front_end/sdk/HARLog.js#L307 -// and -// https://chromium.googlesource.com/chromium/blink.git/+/master/Source/devtools/front_end/sdk/HAREntry.js#131 -// and -// https://github.com/cyrus-and/chrome-har-capturer/blob/master/lib/har.js#L195 -// Order of events: request_start = 0, [proxy], [dns], [connect [ssl]], [send], receive_headers_end - -export const getTimings = ( - timings: PayloadTimings, - requestSentTime: number, - responseReceivedTime: number -): CalculatedTimings => { - if (!timings) return { blocked: -1, dns: -1, connect: -1, send: 0, wait: 0, receive: 0, ssl: -1 }; - - const getLeastNonNegative = (values: number[]) => - values.reduce((best, value) => (value >= 0 && value < best ? value : best), Infinity); - const getOptionalTiming = (_timings: PayloadTimings, key: keyof PayloadTimings) => - _timings[key] >= 0 ? _timings[key] : -1; - - // NOTE: Request sent and request start can differ due to queue times - const requestStartTime = microToMillis(timings.request_time); - - // Queued - const queuedTime = requestSentTime < requestStartTime ? requestStartTime - requestSentTime : -1; - - // Blocked - // "blocked" represents both queued time + blocked/stalled time + proxy time (ie: anything before the request was actually started). - let blocked = queuedTime; - - const blockedStart = getLeastNonNegative([ - timings.dns_start, - timings.connect_start, - timings.send_start, - ]); - - if (blockedStart !== Infinity) { - blocked += blockedStart; - } - - // Proxy - // Proxy is part of blocked, but it can be quirky in that blocked can be -1 even though there are proxy timings. This can happen with - // protocols like Quic. - if (timings.proxy_end !== -1) { - const blockedProxy = timings.proxy_end - timings.proxy_start; - - if (blockedProxy && blockedProxy > blocked) { - blocked = blockedProxy; - } - } - - // DNS - const dnsStart = timings.dns_end >= 0 ? blockedStart : 0; - const dnsEnd = getOptionalTiming(timings, 'dns_end'); - const dns = dnsEnd - dnsStart; - - // SSL - const sslStart = getOptionalTiming(timings, 'ssl_start'); - const sslEnd = getOptionalTiming(timings, 'ssl_end'); - let ssl; - - if (sslStart >= 0 && sslEnd >= 0) { - ssl = timings.ssl_end - timings.ssl_start; - } - - // Connect - let connect = -1; - if (timings.connect_start >= 0) { - connect = timings.send_start - timings.connect_start; - } - - // Send - const send = timings.send_end - timings.send_start; - - // Wait - const wait = timings.receive_headers_end - timings.send_end; - - // Receive - const receive = responseReceivedTime - (requestStartTime + timings.receive_headers_end); - - // SSL connection is a part of the overall connection time - if (connect && ssl) { - connect = connect - ssl; - } - - return { blocked, dns, connect, send, wait, receive, ssl }; -}; - -// TODO: Switch to real API data, and type data as the payload response (if server response isn't preformatted) -export const extractItems = (data: any): NetworkItems => { - const items = data - .map((entry: any) => { - const requestSentTime = microToMillis(entry.synthetics.payload.start); - const responseReceivedTime = microToMillis(entry.synthetics.payload.end); - const requestStartTime = - entry.synthetics.payload.response && entry.synthetics.payload.response.timing - ? microToMillis(entry.synthetics.payload.response.timing.request_time) - : null; - - return { - timestamp: entry['@timestamp'], - method: entry.synthetics.payload.method, - url: entry.synthetics.payload.url, - status: entry.synthetics.payload.status, - mimeType: entry.synthetics.payload?.response?.mime_type, - requestSentTime, - responseReceivedTime, - earliestRequestTime: requestStartTime - ? Math.min(requestSentTime, requestStartTime) - : requestSentTime, - timings: - entry.synthetics.payload.response && entry.synthetics.payload.response.timing - ? getTimings( - entry.synthetics.payload.response.timing, - requestSentTime, - responseReceivedTime - ) - : null, - }; - }) - .sort((a: any, b: any) => { - return a.earliestRequestTime - b.earliestRequestTime; - }); - - return items; -}; - -const formatValueForDisplay = (value: number, points: number = 3) => { - return Number(value).toFixed(points); -}; - -const getColourForMimeType = (mimeType?: string) => { - const key = mimeType && MimeTypesMap[mimeType] ? MimeTypesMap[mimeType] : MimeType.Other; - return colourPalette[key]; -}; - -export const getSeriesAndDomain = (items: NetworkItems) => { - // The earliest point in time a request is sent or started. This will become our notion of "0". - const zeroOffset = items.reduce((acc, item) => { - const { earliestRequestTime } = item; - return earliestRequestTime < acc ? earliestRequestTime : acc; - }, Infinity); - - const series = items.reduce((acc, item, index) => { - const { earliestRequestTime } = item; - - // Entries without timings should be handled differently: - // https://github.com/ChromeDevTools/devtools-frontend/blob/ed2a064ac194bfae4e25c4748a9fa3513b3e9f7d/front_end/network/RequestTimingView.js#L140 - // If there are no concrete timings just plot one block via start and end - if (!item.timings || item.timings === null) { - const duration = item.responseReceivedTime - item.earliestRequestTime; - const colour = getColourForMimeType(item.mimeType); - return [ - ...acc, - { - x: index, - y0: item.earliestRequestTime - zeroOffset, - y: item.responseReceivedTime - zeroOffset, - config: { - colour, - tooltipProps: { - value: `${formatValueForDisplay(duration)}ms`, - colour, - }, - }, - }, - ]; - } - - let currentOffset = earliestRequestTime - zeroOffset; - - TIMING_ORDER.forEach((timing) => { - const value = item.timings![timing]; - const colour = - timing === Timings.Receive ? getColourForMimeType(item.mimeType) : colourPalette[timing]; - if (value && value >= 0) { - const y = currentOffset + value; - - acc.push({ - x: index, - y0: currentOffset, - y, - config: { - colour, - tooltipProps: { - value: `${FriendlyTimingLabels[timing]}: ${formatValueForDisplay( - y - currentOffset - )}ms`, - colour, - }, - }, - }); - currentOffset = y; - } - }); - return acc; - }, []); - - const yValues = series.map((serie) => serie.y); - const domain = { min: 0, max: Math.max(...yValues) }; - return { series, domain }; -}; - -export const getSidebarItems = (items: NetworkItems): SidebarItems => { - return items.map((item) => { - const { url, status, method } = item; - return { url, status, method }; - }); -}; - -export const getLegendItems = (): LegendItems => { - let timingItems: LegendItems = []; - Object.values(Timings).forEach((timing) => { - // The "receive" timing is mapped to a mime type colour, so we don't need to show this in the legend - if (timing === Timings.Receive) { - return; - } - timingItems = [ - ...timingItems, - { name: FriendlyTimingLabels[timing], colour: TIMING_PALETTE[timing] }, - ]; - }); - - let mimeTypeItems: LegendItems = []; - Object.values(MimeType).forEach((mimeType) => { - mimeTypeItems = [ - ...mimeTypeItems, - { name: FriendlyMimetypeLabels[mimeType], colour: MIME_TYPE_PALETTE[mimeType] }, - ]; - }); - return [...timingItems, ...mimeTypeItems]; -}; - -// Timing colour palette -type TimingColourPalette = { - [K in Timings]: string; -}; - -const SAFE_PALETTE = euiPaletteColorBlind({ rotations: 2 }); - -const buildTimingPalette = (): TimingColourPalette => { - const palette = Object.values(Timings).reduce>((acc, value) => { - switch (value) { - case Timings.Blocked: - acc[value] = SAFE_PALETTE[6]; - break; - case Timings.Dns: - acc[value] = SAFE_PALETTE[0]; - break; - case Timings.Connect: - acc[value] = SAFE_PALETTE[7]; - break; - case Timings.Ssl: - acc[value] = SAFE_PALETTE[17]; - break; - case Timings.Send: - acc[value] = SAFE_PALETTE[2]; - break; - case Timings.Wait: - acc[value] = SAFE_PALETTE[11]; - break; - case Timings.Receive: - acc[value] = SAFE_PALETTE[0]; - break; - } - return acc; - }, {}); - - return palette as TimingColourPalette; -}; - -const TIMING_PALETTE = buildTimingPalette(); - -// MimeType colour palette -type MimeTypeColourPalette = { - [K in MimeType]: string; -}; - -const buildMimeTypePalette = (): MimeTypeColourPalette => { - const palette = Object.values(MimeType).reduce>((acc, value) => { - switch (value) { - case MimeType.Html: - acc[value] = SAFE_PALETTE[19]; - break; - case MimeType.Script: - acc[value] = SAFE_PALETTE[3]; - break; - case MimeType.Stylesheet: - acc[value] = SAFE_PALETTE[4]; - break; - case MimeType.Media: - acc[value] = SAFE_PALETTE[5]; - break; - case MimeType.Font: - acc[value] = SAFE_PALETTE[8]; - break; - case MimeType.Other: - acc[value] = SAFE_PALETTE[9]; - break; - } - return acc; - }, {}); - - return palette as MimeTypeColourPalette; -}; - -const MIME_TYPE_PALETTE = buildMimeTypePalette(); - -type ColourPalette = TimingColourPalette & MimeTypeColourPalette; - -export const colourPalette: ColourPalette = { ...TIMING_PALETTE, ...MIME_TYPE_PALETTE }; diff --git a/x-pack/plugins/uptime/public/hooks/use_telemetry.ts b/x-pack/plugins/uptime/public/hooks/use_telemetry.ts index 9b4a441fe5ade..21665e93dd274 100644 --- a/x-pack/plugins/uptime/public/hooks/use_telemetry.ts +++ b/x-pack/plugins/uptime/public/hooks/use_telemetry.ts @@ -14,6 +14,7 @@ export enum UptimePage { Monitor = 'Monitor', Settings = 'Settings', Certificates = 'Certificates', + StepDetail = 'StepDetail', NotFound = '__not-found__', } diff --git a/x-pack/plugins/uptime/public/pages/index.ts b/x-pack/plugins/uptime/public/pages/index.ts index cea47d6ccf79c..cb95fb8558cfb 100644 --- a/x-pack/plugins/uptime/public/pages/index.ts +++ b/x-pack/plugins/uptime/public/pages/index.ts @@ -5,5 +5,6 @@ */ export { MonitorPage } from './monitor'; +export { StepDetailPage } from './step_detail_page'; export { SettingsPage } from './settings'; export { NotFoundPage } from './not_found'; diff --git a/x-pack/plugins/uptime/public/pages/step_detail_page.tsx b/x-pack/plugins/uptime/public/pages/step_detail_page.tsx new file mode 100644 index 0000000000000..5bacad7e9a2d2 --- /dev/null +++ b/x-pack/plugins/uptime/public/pages/step_detail_page.tsx @@ -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 React from 'react'; +import { useParams } from 'react-router-dom'; +import { useTrackPageview } from '../../../observability/public'; +import { useInitApp } from '../hooks/use_init_app'; +import { StepDetailContainer } from '../components/monitor/synthetics/step_detail/step_detail_container'; + +export const StepDetailPage: React.FC = () => { + useInitApp(); + const { checkGroupId, stepIndex } = useParams<{ checkGroupId: string; stepIndex: string }>(); + useTrackPageview({ app: 'uptime', path: 'stepDetail' }); + useTrackPageview({ app: 'uptime', path: 'stepDetail', delay: 15000 }); + + return ; +}; diff --git a/x-pack/plugins/uptime/public/routes.tsx b/x-pack/plugins/uptime/public/routes.tsx index 9b54c52cc674c..65526f9bca4fc 100644 --- a/x-pack/plugins/uptime/public/routes.tsx +++ b/x-pack/plugins/uptime/public/routes.tsx @@ -12,8 +12,9 @@ import { MONITOR_ROUTE, OVERVIEW_ROUTE, SETTINGS_ROUTE, + STEP_DETAIL_ROUTE, } from '../common/constants'; -import { MonitorPage, NotFoundPage, SettingsPage } from './pages'; +import { MonitorPage, StepDetailPage, NotFoundPage, SettingsPage } from './pages'; import { CertificatesPage } from './pages/certificates'; import { UptimePage, useUptimeTelemetry } from './hooks'; import { PageHeader } from './components/common/header/page_header'; @@ -50,6 +51,13 @@ const Routes: RouteProps[] = [ dataTestSubj: 'uptimeCertificatesPage', telemetryId: UptimePage.Certificates, }, + { + title: baseTitle, + path: STEP_DETAIL_ROUTE, + component: StepDetailPage, + dataTestSubj: 'uptimeStepDetailPage', + telemetryId: UptimePage.StepDetail, + }, { title: baseTitle, path: OVERVIEW_ROUTE, diff --git a/x-pack/plugins/uptime/public/state/actions/network_events.ts b/x-pack/plugins/uptime/public/state/actions/network_events.ts new file mode 100644 index 0000000000000..e3564689fcd48 --- /dev/null +++ b/x-pack/plugins/uptime/public/state/actions/network_events.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 { createAction } from 'redux-actions'; +import { SyntheticsNetworkEventsApiResponse } from '../../../common/runtime_types'; + +export interface FetchNetworkEventsParams { + checkGroup: string; + stepIndex: number; +} + +export interface FetchNetworkEventsFailPayload { + checkGroup: string; + stepIndex: number; + error: Error; +} + +export const getNetworkEvents = createAction('GET_NETWORK_EVENTS'); +export const getNetworkEventsSuccess = createAction< + Pick & SyntheticsNetworkEventsApiResponse +>('GET_NETWORK_EVENTS_SUCCESS'); +export const getNetworkEventsFail = createAction( + 'GET_NETWORK_EVENTS_FAIL' +); diff --git a/x-pack/plugins/uptime/public/state/api/network_events.ts b/x-pack/plugins/uptime/public/state/api/network_events.ts new file mode 100644 index 0000000000000..a4eceb4812d28 --- /dev/null +++ b/x-pack/plugins/uptime/public/state/api/network_events.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { apiService } from './utils'; +import { FetchNetworkEventsParams } from '../actions/network_events'; +import { + SyntheticsNetworkEventsApiResponse, + SyntheticsNetworkEventsApiResponseType, +} from '../../../common/runtime_types'; + +export async function fetchNetworkEvents( + params: FetchNetworkEventsParams +): Promise { + return (await apiService.get( + `/api/uptime/network_events`, + { + checkGroup: params.checkGroup, + stepIndex: params.stepIndex, + }, + SyntheticsNetworkEventsApiResponseType + )) as SyntheticsNetworkEventsApiResponse; +} diff --git a/x-pack/plugins/uptime/public/state/effects/index.ts b/x-pack/plugins/uptime/public/state/effects/index.ts index 4951f2102c8a7..3c75e75871882 100644 --- a/x-pack/plugins/uptime/public/state/effects/index.ts +++ b/x-pack/plugins/uptime/public/state/effects/index.ts @@ -19,6 +19,7 @@ import { fetchIndexStatusEffect } from './index_status'; import { fetchCertificatesEffect } from '../certificates/certificates'; import { fetchAlertsEffect } from '../alerts/alerts'; import { fetchJourneyStepsEffect } from './journey'; +import { fetchNetworkEventsEffect } from './network_events'; export function* rootEffect() { yield fork(fetchMonitorDetailsEffect); @@ -37,4 +38,5 @@ export function* rootEffect() { yield fork(fetchCertificatesEffect); yield fork(fetchAlertsEffect); yield fork(fetchJourneyStepsEffect); + yield fork(fetchNetworkEventsEffect); } diff --git a/x-pack/plugins/uptime/public/state/effects/network_events.ts b/x-pack/plugins/uptime/public/state/effects/network_events.ts new file mode 100644 index 0000000000000..95d24fbe37ae2 --- /dev/null +++ b/x-pack/plugins/uptime/public/state/effects/network_events.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Action } from 'redux-actions'; +import { call, put, takeLatest } from 'redux-saga/effects'; +import { + getNetworkEvents, + getNetworkEventsSuccess, + getNetworkEventsFail, + FetchNetworkEventsParams, +} from '../actions/network_events'; +import { fetchNetworkEvents } from '../api/network_events'; + +export function* fetchNetworkEventsEffect() { + yield takeLatest(getNetworkEvents, function* (action: Action) { + try { + const response = yield call(fetchNetworkEvents, action.payload); + + yield put( + getNetworkEventsSuccess({ + checkGroup: action.payload.checkGroup, + stepIndex: action.payload.stepIndex, + ...response, + }) + ); + } catch (e) { + yield put( + getNetworkEventsFail({ + checkGroup: action.payload.checkGroup, + stepIndex: action.payload.stepIndex, + error: e, + }) + ); + } + }); +} diff --git a/x-pack/plugins/uptime/public/state/reducers/index.ts b/x-pack/plugins/uptime/public/state/reducers/index.ts index c0bab124d5f9d..661b637802707 100644 --- a/x-pack/plugins/uptime/public/state/reducers/index.ts +++ b/x-pack/plugins/uptime/public/state/reducers/index.ts @@ -22,6 +22,7 @@ import { certificatesReducer } from '../certificates/certificates'; import { selectedFiltersReducer } from './selected_filters'; import { alertsReducer } from '../alerts/alerts'; import { journeyReducer } from './journey'; +import { networkEventsReducer } from './network_events'; export const rootReducer = combineReducers({ monitor: monitorReducer, @@ -41,4 +42,5 @@ export const rootReducer = combineReducers({ selectedFilters: selectedFiltersReducer, alerts: alertsReducer, journeys: journeyReducer, + networkEvents: networkEventsReducer, }); diff --git a/x-pack/plugins/uptime/public/state/reducers/journey.ts b/x-pack/plugins/uptime/public/state/reducers/journey.ts index e1c3dc808f1bf..133a5d1edb2c2 100644 --- a/x-pack/plugins/uptime/public/state/reducers/journey.ts +++ b/x-pack/plugins/uptime/public/state/reducers/journey.ts @@ -18,6 +18,7 @@ import { export interface JourneyState { checkGroup: string; steps: Ping[]; + details?: SyntheticsJourneyApiResponse['details']; loading: boolean; error?: Error; } @@ -56,13 +57,14 @@ export const journeyReducer = handleActions( [String(getJourneyStepsSuccess)]: ( state: JourneyKVP, - { payload: { checkGroup, steps } }: Action + { payload: { checkGroup, steps, details } }: Action ) => ({ ...state, [checkGroup]: { loading: false, checkGroup, steps, + details, }, }), diff --git a/x-pack/plugins/uptime/public/state/reducers/network_events.ts b/x-pack/plugins/uptime/public/state/reducers/network_events.ts new file mode 100644 index 0000000000000..44a23b0fa53d7 --- /dev/null +++ b/x-pack/plugins/uptime/public/state/reducers/network_events.ts @@ -0,0 +1,122 @@ +/* + * 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 { handleActions, Action } from 'redux-actions'; +import { NetworkEvent, SyntheticsNetworkEventsApiResponse } from '../../../common/runtime_types'; +import { + FetchNetworkEventsParams, + FetchNetworkEventsFailPayload, + getNetworkEvents, + getNetworkEventsFail, + getNetworkEventsSuccess, +} from '../actions/network_events'; + +export interface NetworkEventsState { + [checkGroup: string]: { + [stepIndex: number]: { + events: NetworkEvent[]; + loading: boolean; + error?: Error; + }; + }; +} + +const initialState: NetworkEventsState = {}; + +type Payload = FetchNetworkEventsParams & + SyntheticsNetworkEventsApiResponse & + FetchNetworkEventsFailPayload & + string[]; + +export const networkEventsReducer = handleActions( + { + [String(getNetworkEvents)]: ( + state: NetworkEventsState, + { payload: { checkGroup, stepIndex } }: Action + ) => ({ + ...state, + [checkGroup]: state[checkGroup] + ? { + [stepIndex]: state[checkGroup][stepIndex] + ? { + ...state[checkGroup][stepIndex], + loading: true, + events: [], + } + : { + loading: true, + events: [], + }, + } + : { + [stepIndex]: { + loading: true, + events: [], + }, + }, + }), + + [String(getNetworkEventsSuccess)]: ( + state: NetworkEventsState, + { + payload: { events, checkGroup, stepIndex }, + }: Action + ) => { + return { + ...state, + [checkGroup]: state[checkGroup] + ? { + [stepIndex]: state[checkGroup][stepIndex] + ? { + ...state[checkGroup][stepIndex], + loading: false, + events, + } + : { + loading: false, + events, + }, + } + : { + [stepIndex]: { + loading: false, + events, + }, + }, + }; + }, + + [String(getNetworkEventsFail)]: ( + state: NetworkEventsState, + { payload: { checkGroup, stepIndex, error } }: Action + ) => ({ + ...state, + [checkGroup]: state[checkGroup] + ? { + [stepIndex]: state[checkGroup][stepIndex] + ? { + ...state[checkGroup][stepIndex], + loading: false, + events: [], + error, + } + : { + loading: false, + events: [], + error, + }, + } + : { + [stepIndex]: { + loading: false, + events: [], + error, + }, + }, + }), + }, + initialState +); diff --git a/x-pack/plugins/uptime/public/state/selectors/__tests__/index.test.ts b/x-pack/plugins/uptime/public/state/selectors/__tests__/index.test.ts index f1a68318be863..64410b860b197 100644 --- a/x-pack/plugins/uptime/public/state/selectors/__tests__/index.test.ts +++ b/x-pack/plugins/uptime/public/state/selectors/__tests__/index.test.ts @@ -116,6 +116,7 @@ describe('state selectors', () => { anomalyAlertDeletion: { data: null, loading: false }, }, journeys: {}, + networkEvents: {}, }; it('selects base path from state', () => { diff --git a/x-pack/plugins/uptime/public/state/selectors/index.ts b/x-pack/plugins/uptime/public/state/selectors/index.ts index 6bfe67468aae5..eef53e1100029 100644 --- a/x-pack/plugins/uptime/public/state/selectors/index.ts +++ b/x-pack/plugins/uptime/public/state/selectors/index.ts @@ -96,3 +96,5 @@ export const selectedFiltersSelector = ({ selectedFilters }: AppState) => select export const monitorIdSelector = ({ ui: { monitorId } }: AppState) => monitorId; export const journeySelector = ({ journeys }: AppState) => journeys; + +export const networkEventsSelector = ({ networkEvents }: AppState) => networkEvents; diff --git a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_network_events.test.ts b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_network_events.test.ts new file mode 100644 index 0000000000000..bb88911eedfb0 --- /dev/null +++ b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_network_events.test.ts @@ -0,0 +1,251 @@ +/* + * 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 { getUptimeESMockClient } from './helper'; +import { getNetworkEvents } from '../get_network_events'; + +describe('getNetworkEvents', () => { + let mockHits: any; + + beforeEach(() => { + mockHits = [ + { + _index: 'heartbeat-2020.12.14', + _id: 'YMfcYHYBOm8nKLizQt1o', + _score: null, + _source: { + '@timestamp': '2020-12-14T10:46:39.183Z', + synthetics: { + step: { + name: 'Click next link', + index: 2, + }, + journey: { + name: 'inline', + id: 'inline', + }, + type: 'journey/network_info', + package_version: '0.0.1-alpha.8', + payload: { + load_end_time: 3287.298251, + response_received_time: 3287.299074, + method: 'GET', + step: { + index: 2, + name: 'Click next link', + }, + status: 200, + type: 'Image', + request_sent_time: 3287.154973, + url: 'www.test.com', + request: { + initial_priority: 'Low', + referrer_policy: 'no-referrer-when-downgrade', + url: 'www.test.com', + method: 'GET', + headers: { + referer: 'www.test.com', + user_agent: + 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/88.0.4324.0 Safari/537.36', + }, + mixed_content_type: 'none', + }, + response: { + from_service_worker: false, + security_details: { + protocol: 'TLS 1.2', + key_exchange: 'ECDHE_RSA', + valid_to: 1638230399, + certificate_transparency_compliance: 'unknown', + cipher: 'AES_128_GCM', + issuer: 'DigiCert TLS RSA SHA256 2020 CA1', + subject_name: 'syndication.twitter.com', + valid_from: 1606694400, + signed_certificate_timestamp_list: [], + key_exchange_group: 'P-256', + san_list: [ + 'syndication.twitter.com', + 'syndication.twimg.com', + 'cdn.syndication.twitter.com', + 'cdn.syndication.twimg.com', + 'syndication-o.twitter.com', + 'syndication-o.twimg.com', + ], + certificate_id: 0, + }, + security_state: 'secure', + connection_reused: true, + remote_port: 443, + timing: { + ssl_start: -1, + send_start: 0.214, + ssl_end: -1, + connect_start: -1, + connect_end: -1, + send_end: 0.402, + dns_start: -1, + request_time: 3287.155502, + push_end: 0, + worker_fetch_start: -1, + worker_ready: -1, + worker_start: -1, + proxy_end: -1, + push_start: 0, + worker_respond_with_settled: -1, + proxy_start: -1, + dns_end: -1, + receive_headers_end: 142.215, + }, + connection_id: 852, + remote_i_p_address: '104.244.42.200', + encoded_data_length: 337, + response_time: 1.60794279932414e12, + from_prefetch_cache: false, + mime_type: 'image/gif', + from_disk_cache: false, + url: 'www.test.com', + protocol: 'h2', + headers: { + x_frame_options: 'SAMEORIGIN', + cache_control: 'no-cache, no-store, must-revalidate, pre-check=0, post-check=0', + strict_transport_security: 'max-age=631138519', + x_twitter_response_tags: 'BouncerCompliant', + content_type: 'image/gif;charset=utf-8', + expires: 'Tue, 31 Mar 1981 05:00:00 GMT', + date: 'Mon, 14 Dec 2020 10:46:39 GMT', + x_transaction: '008fff3d00a1e64c', + x_connection_hash: 'cb6fe99b8676f4e4b827cc3e6512c90d', + last_modified: 'Mon, 14 Dec 2020 10:46:39 GMT', + x_content_type_options: 'nosniff', + content_encoding: 'gzip', + x_xss_protection: '0', + server: 'tsa_f', + x_response_time: '108', + pragma: 'no-cache', + content_length: '65', + status: '200 OK', + }, + status_text: '', + status: 200, + }, + timings: { + proxy: -1, + connect: -1, + receive: 0.5340000002433953, + blocked: 0.21400000014182297, + ssl: -1, + send: 0.18799999998009298, + total: 143.27800000000934, + queueing: 0.5289999999149586, + wait: 141.81299999972907, + dns: -1, + }, + is_navigation_request: false, + timestamp: 1607942799183375, + }, + }, + }, + }, + ]; + }); + + it('Uses the correct query', async () => { + const { uptimeEsClient, esClient } = getUptimeESMockClient(); + + esClient.search.mockResolvedValueOnce({ + body: { + hits: { + hits: mockHits, + }, + }, + } as any); + + await getNetworkEvents({ + uptimeEsClient, + checkGroup: 'my-fake-group', + stepIndex: '1', + }); + + expect(esClient.search.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "body": Object { + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "term": Object { + "synthetics.type": "journey/network_info", + }, + }, + Object { + "term": Object { + "monitor.check_group": "my-fake-group", + }, + }, + Object { + "term": Object { + "synthetics.step.index": 1, + }, + }, + ], + }, + }, + "size": 1000, + }, + "index": "heartbeat-8*", + }, + ], + ] + `); + }); + + it('Returns the correct result', async () => { + const { esClient, uptimeEsClient } = getUptimeESMockClient(); + + esClient.search.mockResolvedValueOnce({ + body: { + hits: { + hits: mockHits, + }, + }, + } as any); + + const result = await getNetworkEvents({ + uptimeEsClient, + checkGroup: 'my-fake-group', + stepIndex: '1', + }); + + expect(result).toMatchInlineSnapshot(` + Array [ + Object { + "loadEndTime": 3287298.251, + "method": "GET", + "mimeType": "image/gif", + "requestSentTime": 3287154.973, + "requestStartTime": 3287155.502, + "status": 200, + "timestamp": "2020-12-14T10:46:39.183Z", + "timings": Object { + "blocked": 0.21400000014182297, + "connect": -1, + "dns": -1, + "proxy": -1, + "queueing": 0.5289999999149586, + "receive": 0.5340000002433953, + "send": 0.18799999998009298, + "ssl": -1, + "total": 143.27800000000934, + "wait": 141.81299999972907, + }, + "url": "www.test.com", + }, + ] + `); + }); +}); diff --git a/x-pack/plugins/uptime/server/lib/requests/get_journey_details.ts b/x-pack/plugins/uptime/server/lib/requests/get_journey_details.ts new file mode 100644 index 0000000000000..ef11b00604490 --- /dev/null +++ b/x-pack/plugins/uptime/server/lib/requests/get_journey_details.ts @@ -0,0 +1,127 @@ +/* + * 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 { UMElasticsearchQueryFn } from '../adapters/framework'; +import { SyntheticsJourneyApiResponse } from '../../../common/runtime_types'; + +interface GetJourneyDetails { + checkGroup: string; +} + +export const getJourneyDetails: UMElasticsearchQueryFn< + GetJourneyDetails, + SyntheticsJourneyApiResponse['details'] +> = async ({ uptimeEsClient, checkGroup }) => { + const baseParams = { + query: { + bool: { + filter: [ + { + term: { + 'monitor.check_group': checkGroup, + }, + }, + { + term: { + 'synthetics.type': 'journey/end', + }, + }, + ], + }, + }, + _source: ['@timestamp', 'monitor.id'], + size: 1, + }; + + const { body: thisJourney } = await uptimeEsClient.search({ body: baseParams }); + + if (thisJourney?.hits?.hits.length > 0) { + const thisJourneySource: any = thisJourney.hits.hits[0]._source; + + const baseSiblingParams = { + query: { + bool: { + filter: [ + { + term: { + 'monitor.id': thisJourneySource.monitor.id, + }, + }, + { + term: { + 'synthetics.type': 'journey/end', + }, + }, + ], + }, + }, + _source: ['@timestamp', 'monitor.check_group'], + size: 1, + }; + + const previousParams = { + ...baseSiblingParams, + query: { + bool: { + filter: [ + ...baseSiblingParams.query.bool.filter, + { + range: { + '@timestamp': { + lt: thisJourneySource['@timestamp'], + }, + }, + }, + ], + }, + }, + sort: [{ '@timestamp': { order: 'desc' } }], + }; + + const nextParams = { + ...baseSiblingParams, + query: { + bool: { + filter: [ + ...baseSiblingParams.query.bool.filter, + { + range: { + '@timestamp': { + gt: thisJourneySource['@timestamp'], + }, + }, + }, + ], + }, + }, + sort: [{ '@timestamp': { order: 'asc' } }], + }; + + const { body: previousJourneyResult } = await uptimeEsClient.search({ body: previousParams }); + const { body: nextJourneyResult } = await uptimeEsClient.search({ body: nextParams }); + const previousJourney: any = + previousJourneyResult?.hits?.hits.length > 0 ? previousJourneyResult?.hits?.hits[0] : null; + const nextJourney: any = + nextJourneyResult?.hits?.hits.length > 0 ? nextJourneyResult?.hits?.hits[0] : null; + return { + timestamp: thisJourneySource['@timestamp'], + previous: previousJourney + ? { + checkGroup: previousJourney._source.monitor.check_group, + timestamp: previousJourney._source['@timestamp'], + } + : undefined, + next: nextJourney + ? { + checkGroup: nextJourney._source.monitor.check_group, + timestamp: nextJourney._source['@timestamp'], + } + : undefined, + } as SyntheticsJourneyApiResponse['details']; + } else { + return null; + } +}; diff --git a/x-pack/plugins/uptime/server/lib/requests/get_network_events.ts b/x-pack/plugins/uptime/server/lib/requests/get_network_events.ts new file mode 100644 index 0000000000000..1353175a8f94d --- /dev/null +++ b/x-pack/plugins/uptime/server/lib/requests/get_network_events.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 { UMElasticsearchQueryFn } from '../adapters/framework'; +import { NetworkEvent } from '../../../common/runtime_types'; + +interface GetNetworkEventsParams { + checkGroup: string; + stepIndex: string; +} + +export const getNetworkEvents: UMElasticsearchQueryFn< + GetNetworkEventsParams, + NetworkEvent[] +> = async ({ uptimeEsClient, checkGroup, stepIndex }) => { + const params = { + query: { + bool: { + filter: [ + { term: { 'synthetics.type': 'journey/network_info' } }, + { term: { 'monitor.check_group': checkGroup } }, + { term: { 'synthetics.step.index': Number(stepIndex) } }, + ], + }, + }, + // NOTE: This limit may need tweaking in the future. Users can technically perform multiple + // navigations within one step, and may push up against this limit, however this manner + // of usage isn't advised. + size: 1000, + }; + + const { body: result } = await uptimeEsClient.search({ body: params }); + + const microToMillis = (micro: number): number => (micro === -1 ? -1 : micro * 1000); + + return result.hits.hits.map((event: any) => { + const requestSentTime = microToMillis(event._source.synthetics.payload.request_sent_time); + const loadEndTime = microToMillis(event._source.synthetics.payload.load_end_time); + const requestStartTime = + event._source.synthetics.payload.response && event._source.synthetics.payload.response.timing + ? microToMillis(event._source.synthetics.payload.response.timing.request_time) + : undefined; + + return { + timestamp: event._source['@timestamp'], + method: event._source.synthetics.payload?.method, + url: event._source.synthetics.payload?.url, + status: event._source.synthetics.payload?.status, + mimeType: event._source.synthetics.payload?.response?.mime_type, + requestSentTime, + requestStartTime, + loadEndTime, + timings: event._source.synthetics.payload.timings, + }; + }); +}; diff --git a/x-pack/plugins/uptime/server/lib/requests/index.ts b/x-pack/plugins/uptime/server/lib/requests/index.ts index fd7e5f6041719..34137fe400b00 100644 --- a/x-pack/plugins/uptime/server/lib/requests/index.ts +++ b/x-pack/plugins/uptime/server/lib/requests/index.ts @@ -20,6 +20,8 @@ import { getSnapshotCount } from './get_snapshot_counts'; import { getIndexStatus } from './get_index_status'; import { getJourneySteps } from './get_journey_steps'; import { getJourneyScreenshot } from './get_journey_screenshot'; +import { getJourneyDetails } from './get_journey_details'; +import { getNetworkEvents } from './get_network_events'; import { getJourneyFailedSteps } from './get_journey_failed_steps'; export const requests = { @@ -40,6 +42,8 @@ export const requests = { getJourneySteps, getJourneyFailedSteps, getJourneyScreenshot, + getJourneyDetails, + getNetworkEvents, }; export type UptimeRequests = typeof requests; diff --git a/x-pack/plugins/uptime/server/rest_api/index.ts b/x-pack/plugins/uptime/server/rest_api/index.ts index a2475792edfbe..4db2da541079c 100644 --- a/x-pack/plugins/uptime/server/rest_api/index.ts +++ b/x-pack/plugins/uptime/server/rest_api/index.ts @@ -24,6 +24,7 @@ import { } from './monitors'; import { createGetMonitorDurationRoute } from './monitors/monitors_durations'; import { createGetIndexPatternRoute, createGetIndexStatusRoute } from './index_state'; +import { createNetworkEventsRoute } from './network_events'; import { createJourneyFailedStepsRoute } from './pings/journeys'; export * from './types'; @@ -48,5 +49,6 @@ export const restApiRoutes: UMRestApiRouteFactory[] = [ createGetMonitorDurationRoute, createJourneyRoute, createJourneyScreenshotRoute, + createNetworkEventsRoute, createJourneyFailedStepsRoute, ]; diff --git a/x-pack/plugins/uptime/server/rest_api/network_events/get_network_events.ts b/x-pack/plugins/uptime/server/rest_api/network_events/get_network_events.ts new file mode 100644 index 0000000000000..f24b319baff00 --- /dev/null +++ b/x-pack/plugins/uptime/server/rest_api/network_events/get_network_events.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 { schema } from '@kbn/config-schema'; +import { UMServerLibs } from '../../lib/lib'; +import { UMRestApiRouteFactory } from '../types'; + +export const createNetworkEventsRoute: UMRestApiRouteFactory = (libs: UMServerLibs) => ({ + method: 'GET', + path: '/api/uptime/network_events', + validate: { + query: schema.object({ + checkGroup: schema.string(), + stepIndex: schema.number(), + }), + }, + handler: async ({ uptimeEsClient, request }): Promise => { + const { checkGroup, stepIndex } = request.query; + + const result = await libs.requests.getNetworkEvents({ + uptimeEsClient, + checkGroup, + stepIndex, + }); + + return { + events: result, + }; + }, +}); diff --git a/x-pack/plugins/uptime/server/rest_api/network_events/index.ts b/x-pack/plugins/uptime/server/rest_api/network_events/index.ts new file mode 100644 index 0000000000000..3f3c1afe06f99 --- /dev/null +++ b/x-pack/plugins/uptime/server/rest_api/network_events/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 { createNetworkEventsRoute } from './get_network_events'; diff --git a/x-pack/plugins/uptime/server/rest_api/pings/journeys.ts b/x-pack/plugins/uptime/server/rest_api/pings/journeys.ts index 8ebd4b4609c75..b2559ee8d7054 100644 --- a/x-pack/plugins/uptime/server/rest_api/pings/journeys.ts +++ b/x-pack/plugins/uptime/server/rest_api/pings/journeys.ts @@ -24,9 +24,15 @@ export const createJourneyRoute: UMRestApiRouteFactory = (libs: UMServerLibs) => checkGroup, }); + const details = await libs.requests.getJourneyDetails({ + uptimeEsClient, + checkGroup, + }); + return { checkGroup, steps: result, + details, }; }, }); From 4d398f24613c0ec9c88677dc97e184441f7a1d17 Mon Sep 17 00:00:00 2001 From: Constance Date: Mon, 14 Dec 2020 13:02:49 -0800 Subject: [PATCH 44/95] [App Search] Temporarily remove sidebar layout and internal engine links for 7.11 release (#85820) * Hide layout/sidebar for main Engines Overview * Remove internal links to Engine pages Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../components/engines/engines_table.test.tsx | 7 ++- .../components/engines/engines_table.tsx | 15 +++--- .../applications/app_search/index.test.tsx | 2 +- .../public/applications/app_search/index.tsx | 50 ++++++++++--------- 4 files changed, 39 insertions(+), 35 deletions(-) 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 ea7eeea750cc4..a30b5c6858f7c 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 @@ -9,8 +9,7 @@ import '../../../__mocks__/enterprise_search_url.mock'; import { mockTelemetryActions, mountWithIntl } from '../../../__mocks__/'; import React from 'react'; -import { EuiBasicTable, EuiPagination, EuiButtonEmpty } from '@elastic/eui'; -import { EuiLinkTo } from '../../../shared/react_router_helpers'; +import { EuiBasicTable, EuiPagination, EuiButtonEmpty, EuiLink } from '@elastic/eui'; import { EnginesTable } from './engines_table'; @@ -55,10 +54,10 @@ describe('EnginesTable', () => { }); it('contains engine links which send telemetry', () => { - const engineLinks = wrapper.find(EuiLinkTo); + const engineLinks = wrapper.find(EuiLink); engineLinks.forEach((link) => { - expect(link.prop('to')).toEqual('/engines/test-engine'); + expect(link.prop('href')).toEqual('http://localhost:3002/as/engines/test-engine'); link.simulate('click'); expect(mockTelemetryActions.sendAppSearchTelemetry).toHaveBeenCalledWith({ 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 e9805ab8f2711..58922e439fc76 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 @@ -6,12 +6,12 @@ import React from 'react'; import { useActions } from 'kea'; -import { EuiBasicTable, EuiBasicTableColumn } from '@elastic/eui'; +import { EuiBasicTable, EuiBasicTableColumn, EuiLink } from '@elastic/eui'; import { FormattedMessage, FormattedDate, FormattedNumber } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { TelemetryLogic } from '../../../shared/telemetry'; -import { EuiLinkTo } from '../../../shared/react_router_helpers'; +import { getAppSearchUrl } from '../../../shared/enterprise_search_url'; import { getEngineRoute } from '../../routes'; import { ENGINES_PAGE_SIZE } from '../../../../../common/constants'; @@ -47,7 +47,8 @@ export const EnginesTable: React.FC = ({ const { sendAppSearchTelemetry } = useActions(TelemetryLogic); const engineLinkProps = (name: string) => ({ - to: getEngineRoute(name), + href: getAppSearchUrl(getEngineRoute(name)), + target: '_blank', onClick: () => sendAppSearchTelemetry({ action: 'clicked', @@ -62,9 +63,9 @@ export const EnginesTable: React.FC = ({ defaultMessage: 'Name', }), render: (name: string) => ( - + {name} - + ), width: '30%', truncateText: true, @@ -137,12 +138,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/app_search/index.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx index 11387734e9f9e..ce1e82ab2d57e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/index.test.tsx @@ -55,7 +55,7 @@ describe('AppSearchConfigured', () => { it('renders with layout', () => { const wrapper = shallow(); - expect(wrapper.find(Layout)).toHaveLength(2); + expect(wrapper.find(Layout)).toHaveLength(1); expect(wrapper.find(Layout).last().prop('readOnlyMode')).toBeFalsy(); expect(wrapper.find(EnginesOverview)).toHaveLength(1); expect(wrapper.find(EngineRouter)).toHaveLength(1); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx index 769230ccffd22..efa95d2033c10 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/index.tsx @@ -8,6 +8,8 @@ import React, { useEffect } from 'react'; import { Route, Redirect, Switch } from 'react-router-dom'; import { useActions, useValues } from 'kea'; +import { EuiPage, EuiPageBody } from '@elastic/eui'; + import { getAppSearchUrl } from '../shared/enterprise_search_url'; import { KibanaLogic } from '../shared/kibana'; import { HttpLogic } from '../shared/http'; @@ -79,29 +81,31 @@ export const AppSearchConfigured: React.FC = (props) => { - } readOnlyMode={readOnlyMode}> - {errorConnecting ? ( - - ) : ( - - - - - - - - - - - - - - - - - - )} - + + + {errorConnecting ? ( + + ) : ( + + + + + + + + + + + + + + + + + + )} + + ); From 23c5daa6229c0a7451f3843b683610522e3a5a45 Mon Sep 17 00:00:00 2001 From: nnamdifrankie <56440728+nnamdifrankie@users.noreply.github.com> Date: Mon, 14 Dec 2020 16:15:55 -0500 Subject: [PATCH 45/95] [Fleet] add ilm policy per data stream (#85492) Co-authored-by: kevinlog Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../plugins/fleet/common/types/models/epm.ts | 2 + .../fleet/sections/epm/constants.tsx | 1 + .../elasticsearch/datastream_ilm/install.ts | 137 ++++++++++++++++++ .../elasticsearch/datastream_ilm/remove.ts | 42 ++++++ .../epm/elasticsearch/template/install.ts | 1 + .../epm/elasticsearch/template/template.ts | 6 +- .../services/epm/packages/_install_package.ts | 9 ++ .../server/services/epm/packages/remove.ts | 3 + .../apis/epm/install_remove_assets.ts | 9 ++ .../apis/epm/update_assets.ts | 4 + .../elasticsearch/ilm_policy/all_assets.json | 15 ++ 11 files changed, 228 insertions(+), 1 deletion(-) create mode 100644 x-pack/plugins/fleet/server/services/epm/elasticsearch/datastream_ilm/install.ts create mode 100644 x-pack/plugins/fleet/server/services/epm/elasticsearch/datastream_ilm/remove.ts create mode 100644 x-pack/test/fleet_api_integration/apis/fixtures/test_packages/all_assets/0.1.0/data_stream/test_metrics/elasticsearch/ilm_policy/all_assets.json diff --git a/x-pack/plugins/fleet/common/types/models/epm.ts b/x-pack/plugins/fleet/common/types/models/epm.ts index f518c606d6959..77625e48dbc96 100644 --- a/x-pack/plugins/fleet/common/types/models/epm.ts +++ b/x-pack/plugins/fleet/common/types/models/epm.ts @@ -65,6 +65,7 @@ export enum ElasticsearchAssetType { indexTemplate = 'index_template', ilmPolicy = 'ilm_policy', transform = 'transform', + dataStreamIlmPolicy = 'data_stream_ilm_policy', } export type DataType = typeof dataTypes; @@ -207,6 +208,7 @@ export type ElasticsearchAssetTypeToParts = Record< export interface RegistryDataStream { type: string; + ilm_policy?: string; hidden?: boolean; dataset: string; title: string; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/constants.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/constants.tsx index fe5390e75f6a1..26e36621802fd 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/constants.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/constants.tsx @@ -27,6 +27,7 @@ export const AssetTitleMap: Record = { visualization: 'Visualization', input: 'Agent input', map: 'Map', + data_stream_ilm_policy: 'Data Stream ILM Policy', }; export const ServiceTitleMap: Record, string> = { diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/datastream_ilm/install.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/datastream_ilm/install.ts new file mode 100644 index 0000000000000..6b5950416af56 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/datastream_ilm/install.ts @@ -0,0 +1,137 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { SavedObjectsClientContract } from 'kibana/server'; +import { + ElasticsearchAssetType, + EsAssetReference, + InstallablePackage, + RegistryDataStream, +} from '../../../../../common/types/models'; +import { CallESAsCurrentUser } from '../../../../types'; +import { getInstallation } from '../../packages'; +import { deleteIlmRefs, deleteIlms } from './remove'; +import { saveInstalledEsRefs } from '../../packages/install'; +import { getAsset } from '../transform/common'; + +interface IlmInstallation { + installationName: string; + content: string; +} + +interface IlmPathDataset { + path: string; + dataStream: RegistryDataStream; +} + +export const installIlmForDataStream = async ( + registryPackage: InstallablePackage, + paths: string[], + callCluster: CallESAsCurrentUser, + savedObjectsClient: SavedObjectsClientContract +) => { + const installation = await getInstallation({ savedObjectsClient, pkgName: registryPackage.name }); + let previousInstalledIlmEsAssets: EsAssetReference[] = []; + if (installation) { + previousInstalledIlmEsAssets = installation.installed_es.filter( + ({ type, id }) => type === ElasticsearchAssetType.dataStreamIlmPolicy + ); + } + + // delete all previous ilm + await deleteIlms( + callCluster, + previousInstalledIlmEsAssets.map((asset) => asset.id) + ); + // install the latest dataset + const dataStreams = registryPackage.data_streams; + if (!dataStreams?.length) return []; + const dataStreamIlmPaths = paths.filter((path) => isDataStreamIlm(path)); + let installedIlms: EsAssetReference[] = []; + if (dataStreamIlmPaths.length > 0) { + const ilmPathDatasets = dataStreams.reduce((acc, dataStream) => { + dataStreamIlmPaths.forEach((path) => { + if (isDatasetIlm(path, dataStream.path)) { + acc.push({ path, dataStream }); + } + }); + return acc; + }, []); + + const ilmRefs = ilmPathDatasets.reduce((acc, ilmPathDataset) => { + if (ilmPathDataset) { + acc.push({ + id: getIlmNameForInstallation(ilmPathDataset), + type: ElasticsearchAssetType.dataStreamIlmPolicy, + }); + } + return acc; + }, []); + + await saveInstalledEsRefs(savedObjectsClient, registryPackage.name, ilmRefs); + + const ilmInstallations: IlmInstallation[] = ilmPathDatasets.map( + (ilmPathDataset: IlmPathDataset) => { + return { + installationName: getIlmNameForInstallation(ilmPathDataset), + content: getAsset(ilmPathDataset.path).toString('utf-8'), + }; + } + ); + + const installationPromises = ilmInstallations.map(async (ilmInstallation) => { + return handleIlmInstall({ callCluster, ilmInstallation }); + }); + + installedIlms = await Promise.all(installationPromises).then((results) => results.flat()); + } + + if (previousInstalledIlmEsAssets.length > 0) { + const currentInstallation = await getInstallation({ + savedObjectsClient, + pkgName: registryPackage.name, + }); + + // remove the saved object reference + await deleteIlmRefs( + savedObjectsClient, + currentInstallation?.installed_es || [], + registryPackage.name, + previousInstalledIlmEsAssets.map((asset) => asset.id), + installedIlms.map((installed) => installed.id) + ); + } + return installedIlms; +}; + +async function handleIlmInstall({ + callCluster, + ilmInstallation, +}: { + callCluster: CallESAsCurrentUser; + ilmInstallation: IlmInstallation; +}): Promise { + await callCluster('transport.request', { + method: 'PUT', + path: `/_ilm/policy/${ilmInstallation.installationName}`, + body: ilmInstallation.content, + }); + + return { id: ilmInstallation.installationName, type: ElasticsearchAssetType.dataStreamIlmPolicy }; +} + +const isDataStreamIlm = (path: string) => { + return new RegExp('(?.*)/data_stream/(?.*)/elasticsearch/ilm/*.*').test(path); +}; + +const isDatasetIlm = (path: string, datasetName: string) => { + return new RegExp(`(?.*)/data_stream\\/${datasetName}/elasticsearch/ilm/*.*`).test(path); +}; + +const getIlmNameForInstallation = (ilmPathDataset: IlmPathDataset) => { + const filename = ilmPathDataset?.path.split('/')?.pop()?.split('.')[0]; + return `${ilmPathDataset.dataStream.type}-${ilmPathDataset.dataStream.package}.${ilmPathDataset.dataStream.path}-${filename}`; +}; diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/datastream_ilm/remove.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/datastream_ilm/remove.ts new file mode 100644 index 0000000000000..f36599365734c --- /dev/null +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/datastream_ilm/remove.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 { SavedObjectsClientContract } from 'kibana/server'; +import { CallESAsCurrentUser, ElasticsearchAssetType, EsAssetReference } from '../../../../types'; +import { PACKAGES_SAVED_OBJECT_TYPE } from '../../../../../common/constants'; + +export const deleteIlms = async (callCluster: CallESAsCurrentUser, ilmPolicyIds: string[]) => { + await Promise.all( + ilmPolicyIds.map(async (ilmPolicyId) => { + await callCluster('transport.request', { + method: 'DELETE', + path: `_ilm/policy/${ilmPolicyId}`, + ignore: [404, 400], + }); + }) + ); +}; + +export const deleteIlmRefs = async ( + savedObjectsClient: SavedObjectsClientContract, + installedEsAssets: EsAssetReference[], + pkgName: string, + installedEsIdToRemove: string[], + currentInstalledEsIlmIds: string[] +) => { + const seen = new Set(); + const filteredAssets = installedEsAssets.filter(({ type, id }) => { + if (type !== ElasticsearchAssetType.dataStreamIlmPolicy) return true; + const add = + (currentInstalledEsIlmIds.includes(id) || !installedEsIdToRemove.includes(id)) && + !seen.has(id); + seen.add(id); + return add; + }); + return savedObjectsClient.update(PACKAGES_SAVED_OBJECT_TYPE, pkgName, { + installed_es: filteredAssets, + }); +}; diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.ts index 944f742e54546..8b018f4a2a906 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.ts @@ -314,6 +314,7 @@ export async function installTemplate({ pipelineName, packageName, composedOfTemplates, + ilmPolicy: dataStream.ilm_policy, hidden: dataStream.hidden, }); diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts index d80d54d098db7..fd75139d4cd45 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts @@ -45,6 +45,7 @@ export function getTemplate({ pipelineName, packageName, composedOfTemplates, + ilmPolicy, hidden, }: { type: string; @@ -53,6 +54,7 @@ export function getTemplate({ pipelineName?: string | undefined; packageName: string; composedOfTemplates: string[]; + ilmPolicy?: string | undefined; hidden?: boolean; }): IndexTemplate { const template = getBaseTemplate( @@ -61,6 +63,7 @@ export function getTemplate({ mappings, packageName, composedOfTemplates, + ilmPolicy, hidden ); if (pipelineName) { @@ -263,6 +266,7 @@ function getBaseTemplate( mappings: IndexTemplateMappings, packageName: string, composedOfTemplates: string[], + ilmPolicy?: string | undefined, hidden?: boolean ): IndexTemplate { // Meta information to identify Ingest Manager's managed templates and indices @@ -287,7 +291,7 @@ function getBaseTemplate( index: { // ILM Policy must be added here, for now point to the default global ILM policy name lifecycle: { - name: type, + name: ilmPolicy ? ilmPolicy : type, }, // What should be our default for the compression? codec: 'best_compression', diff --git a/x-pack/plugins/fleet/server/services/epm/packages/_install_package.ts b/x-pack/plugins/fleet/server/services/epm/packages/_install_package.ts index 5e6ecad9b72f1..c0e2fcb12bcf8 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/_install_package.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/_install_package.ts @@ -29,6 +29,7 @@ import { updateCurrentWriteIndices } from '../elasticsearch/template/template'; import { deleteKibanaSavedObjectsAssets } from './remove'; import { installTransform } from '../elasticsearch/transform/install'; import { createInstallation, saveKibanaAssetsRefs, updateVersion } from './install'; +import { installIlmForDataStream } from '../elasticsearch/datastream_ilm/install'; import { saveArchiveEntries } from '../archive/storage'; import { ConcurrentInstallOperationError } from '../../../errors'; @@ -134,6 +135,13 @@ export async function _installPackage({ // per data stream and we should then save them await installILMPolicy(paths, callCluster); + const installedDataStreamIlm = await installIlmForDataStream( + packageInfo, + paths, + callCluster, + savedObjectsClient + ); + // installs versionized pipelines without removing currently installed ones const installedPipelines = await installPipelines( packageInfo, @@ -212,6 +220,7 @@ export async function _installPackage({ return [ ...installedKibanaAssetsRefs, ...installedPipelines, + ...installedDataStreamIlm, ...installedTemplateRefs, ...installedTransforms, ]; diff --git a/x-pack/plugins/fleet/server/services/epm/packages/remove.ts b/x-pack/plugins/fleet/server/services/epm/packages/remove.ts index 63bf1ed53fb97..331b6bfa882da 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/remove.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/remove.ts @@ -23,6 +23,7 @@ import { deleteTransforms } from '../elasticsearch/transform/remove'; import { packagePolicyService, appContextService } from '../..'; import { splitPkgKey } from '../registry'; import { deletePackageCache } from '../archive'; +import { deleteIlms } from '../elasticsearch/datastream_ilm/remove'; import { removeArchiveEntries } from '../archive/storage'; export async function removeInstallation(options: { @@ -93,6 +94,8 @@ function deleteESAssets(installedObjects: EsAssetReference[], callCluster: CallE return deleteTemplate(callCluster, id); } else if (assetType === ElasticsearchAssetType.transform) { return deleteTransforms(callCluster, [id]); + } else if (assetType === ElasticsearchAssetType.dataStreamIlmPolicy) { + return deleteIlms(callCluster, [id]); } }); } diff --git a/x-pack/test/fleet_api_integration/apis/epm/install_remove_assets.ts b/x-pack/test/fleet_api_integration/apis/epm/install_remove_assets.ts index a7d46b9c6677e..1d5f864c27eea 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/install_remove_assets.ts +++ b/x-pack/test/fleet_api_integration/apis/epm/install_remove_assets.ts @@ -459,6 +459,14 @@ const expectAssetsInstalled = ({ }, ], installed_es: [ + { + id: 'logs-all_assets.test_logs-all_assets', + type: 'data_stream_ilm_policy', + }, + { + id: 'metrics-all_assets.test_metrics-all_assets', + type: 'data_stream_ilm_policy', + }, { id: 'logs-all_assets.test_logs', type: 'index_template', @@ -496,6 +504,7 @@ const expectAssetsInstalled = ({ { id: '96c6eb85-fe2e-56c6-84be-5fda976796db', type: 'epm-packages-assets' }, { id: '2d73a161-fa69-52d0-aa09-1bdc691b95bb', type: 'epm-packages-assets' }, { id: '0a00c2d2-ce63-5b9c-9aa0-0cf1938f7362', type: 'epm-packages-assets' }, + { id: '691f0505-18c5-57a6-9f40-06e8affbdf7a', type: 'epm-packages-assets' }, { id: 'b36e6dd0-58f7-5dd0-a286-8187e4019274', type: 'epm-packages-assets' }, { id: 'f839c76e-d194-555a-90a1-3265a45789e4', type: 'epm-packages-assets' }, { id: '9af7bbb3-7d8a-50fa-acc9-9dde6f5efca2', type: 'epm-packages-assets' }, diff --git a/x-pack/test/fleet_api_integration/apis/epm/update_assets.ts b/x-pack/test/fleet_api_integration/apis/epm/update_assets.ts index 37aa94beec8b0..7b264f949532e 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/update_assets.ts +++ b/x-pack/test/fleet_api_integration/apis/epm/update_assets.ts @@ -293,6 +293,10 @@ export default function (providerContext: FtrProviderContext) { }, ], installed_es: [ + { + id: 'logs-all_assets.test_logs-all_assets', + type: 'data_stream_ilm_policy', + }, { id: 'logs-all_assets.test_logs-0.2.0', type: 'ingest_pipeline', diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/all_assets/0.1.0/data_stream/test_metrics/elasticsearch/ilm_policy/all_assets.json b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/all_assets/0.1.0/data_stream/test_metrics/elasticsearch/ilm_policy/all_assets.json new file mode 100644 index 0000000000000..7cf62e890f865 --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/all_assets/0.1.0/data_stream/test_metrics/elasticsearch/ilm_policy/all_assets.json @@ -0,0 +1,15 @@ +{ + "policy": { + "phases": { + "hot": { + "min_age": "0ms", + "actions": { + "rollover": { + "max_size": "50gb", + "max_age": "30d" + } + } + } + } + } +} \ No newline at end of file From 1f774bb2e62c2cde8840d29aa595e1c0889700d5 Mon Sep 17 00:00:00 2001 From: Patrick Mueller Date: Mon, 14 Dec 2020 16:38:05 -0500 Subject: [PATCH 46/95] [task manager] provide warning when setting max_workers greater than limit (#85574) resolves https://github.com/elastic/kibana/issues/56573 In this PR we create a new task manager limit on the config property `xpack.task_manager.max_workers` of 100, but only log a deprecation warning if that property exceeds the limit. We'll enforce the limit in 8.0. The rationale is that it's unlikely going to be useful to run with more than some number of workers, due to the amount of simultaneous work that would end up happening. In practice, too many workers can slow things down more than speed them up. We're setting the limit to 100 for now, but may increase / decrease it based on further research. --- x-pack/plugins/task_manager/server/config.ts | 1 + x-pack/plugins/task_manager/server/index.test.ts | 9 +++++++++ x-pack/plugins/task_manager/server/index.ts | 7 ++++++- 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/task_manager/server/config.ts b/x-pack/plugins/task_manager/server/config.ts index 157f01281836d..a22c4484389ae 100644 --- a/x-pack/plugins/task_manager/server/config.ts +++ b/x-pack/plugins/task_manager/server/config.ts @@ -6,6 +6,7 @@ import { schema, TypeOf } from '@kbn/config-schema'; +export const MAX_WORKERS_LIMIT = 100; export const DEFAULT_MAX_WORKERS = 10; export const DEFAULT_POLL_INTERVAL = 3000; export const DEFAULT_MAX_POLL_INACTIVITY_CYCLES = 10; diff --git a/x-pack/plugins/task_manager/server/index.test.ts b/x-pack/plugins/task_manager/server/index.test.ts index 3f25f4403d358..873950f229147 100644 --- a/x-pack/plugins/task_manager/server/index.test.ts +++ b/x-pack/plugins/task_manager/server/index.test.ts @@ -40,4 +40,13 @@ describe('deprecations', () => { `); }); }); + + it('logs a warning if max_workers is over limit', () => { + const { messages } = applyTaskManagerDeprecations({ max_workers: 1000 }); + expect(messages).toMatchInlineSnapshot(` + Array [ + "setting \\"xpack.task_manager.max_workers\\" (1000) greater than 100 is deprecated. Values greater than 100 will not be supported starting in 8.0.", + ] + `); + }); }); diff --git a/x-pack/plugins/task_manager/server/index.ts b/x-pack/plugins/task_manager/server/index.ts index 8f96e10430b39..1696a3ec69c05 100644 --- a/x-pack/plugins/task_manager/server/index.ts +++ b/x-pack/plugins/task_manager/server/index.ts @@ -7,7 +7,7 @@ import { get } from 'lodash'; import { PluginConfigDescriptor, PluginInitializerContext } from 'src/core/server'; import { TaskManagerPlugin } from './plugin'; -import { configSchema, TaskManagerConfig } from './config'; +import { configSchema, TaskManagerConfig, MAX_WORKERS_LIMIT } from './config'; export const plugin = (initContext: PluginInitializerContext) => new TaskManagerPlugin(initContext); @@ -37,6 +37,11 @@ export const config: PluginConfigDescriptor = { `"${fromPath}.index" is deprecated. Multitenancy by changing "kibana.index" will not be supported starting in 8.0. See https://ela.st/kbn-remove-legacy-multitenancy for more details` ); } + if (taskManager?.max_workers > MAX_WORKERS_LIMIT) { + log( + `setting "${fromPath}.max_workers" (${taskManager?.max_workers}) greater than ${MAX_WORKERS_LIMIT} is deprecated. Values greater than ${MAX_WORKERS_LIMIT} will not be supported starting in 8.0.` + ); + } return settings; }, ], From 6bf83fc37a30cc7595687f797115ca62d83e31f4 Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Mon, 14 Dec 2020 16:04:24 -0600 Subject: [PATCH 47/95] skip 'should show failed shards popup' #78743 --- x-pack/test/functional/apps/discover/async_scripted_fields.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/test/functional/apps/discover/async_scripted_fields.js b/x-pack/test/functional/apps/discover/async_scripted_fields.js index 33a64e4f9cdd3..247c9c81015d3 100644 --- a/x-pack/test/functional/apps/discover/async_scripted_fields.js +++ b/x-pack/test/functional/apps/discover/async_scripted_fields.js @@ -40,7 +40,7 @@ export default function ({ getService, getPageObjects }) { await security.testUser.restoreDefaults(); }); - it('query should show failed shards pop up', async function () { + it.skip('query should show failed shards pop up', async function () { if (false) { /* If you had to modify the scripted fields, you could un-comment all this, run it, use es_archiver to update 'kibana_scripted_fields_on_logstash' */ From 504c8739de1cc9a597987df8e466a45ba290f282 Mon Sep 17 00:00:00 2001 From: Tyler Smalley Date: Mon, 14 Dec 2020 14:07:50 -0800 Subject: [PATCH 48/95] test:jest improvements to better support our monorepo (#84848) Signed-off-by: Tyler Smalley --- .ci/teamcity/default/jest.sh | 4 +- .ci/teamcity/oss/jest.sh | 2 +- .ci/teamcity/oss/jest_integration.sh | 2 +- .../development-functional-tests.asciidoc | 2 +- .../contributing/development-tests.asciidoc | 106 ++++++++++------- package.json | 2 - packages/kbn-test/src/index.ts | 2 + packages/kbn-test/src/jest/run.test.ts | 38 ++++++ packages/kbn-test/src/jest/run.ts | 110 ++++++++++++++++++ scripts/jest.js | 25 +--- scripts/jest_integration.js | 25 +--- .../server/saved_objects/migrations/README.md | 8 +- src/plugins/embeddable/README.asciidoc | 2 +- test/scripts/jenkins_unit.sh | 2 +- test/scripts/jenkins_xpack.sh | 18 +-- test/scripts/test/jest_integration.sh | 2 +- test/scripts/test/jest_unit.sh | 2 +- test/scripts/test/xpack_jest_unit.sh | 4 +- x-pack/README.md | 39 +------ x-pack/package.json | 2 +- x-pack/plugins/beats_management/readme.md | 8 +- .../plugins/encrypted_saved_objects/README.md | 9 +- x-pack/plugins/enterprise_search/README.md | 6 +- x-pack/plugins/event_log/README.md | 7 +- x-pack/plugins/graph/README.md | 2 +- x-pack/plugins/lens/readme.md | 2 +- x-pack/plugins/maps/README.md | 2 +- x-pack/plugins/ml/readme.md | 12 +- x-pack/plugins/task_manager/README.md | 6 +- x-pack/plugins/transform/readme.md | 12 +- x-pack/plugins/uptime/README.md | 6 +- x-pack/scripts/jest.js | 13 +-- 32 files changed, 285 insertions(+), 197 deletions(-) create mode 100644 packages/kbn-test/src/jest/run.test.ts create mode 100644 packages/kbn-test/src/jest/run.ts diff --git a/.ci/teamcity/default/jest.sh b/.ci/teamcity/default/jest.sh index 93ca7f76f3a21..b900d1b6d6b4e 100755 --- a/.ci/teamcity/default/jest.sh +++ b/.ci/teamcity/default/jest.sh @@ -6,7 +6,5 @@ source "$(dirname "${0}")/../util.sh" export JOB=kibana-default-jest -cd "$XPACK_DIR" - checks-reporter-with-killswitch "Jest Unit Tests" \ - node scripts/jest --bail --debug + node scripts/jest x-pack --ci --verbose --maxWorkers=5 diff --git a/.ci/teamcity/oss/jest.sh b/.ci/teamcity/oss/jest.sh index 3ba9ab0c31c57..0dee07d00d2be 100755 --- a/.ci/teamcity/oss/jest.sh +++ b/.ci/teamcity/oss/jest.sh @@ -7,4 +7,4 @@ source "$(dirname "${0}")/../util.sh" export JOB=kibana-oss-jest checks-reporter-with-killswitch "OSS Jest Unit Tests" \ - node scripts/jest --ci --verbose + node scripts/jest --config jest.config.oss.js --ci --verbose --maxWorkers=5 diff --git a/.ci/teamcity/oss/jest_integration.sh b/.ci/teamcity/oss/jest_integration.sh index 1a23c46c8a2c2..4c51d2ff29888 100755 --- a/.ci/teamcity/oss/jest_integration.sh +++ b/.ci/teamcity/oss/jest_integration.sh @@ -7,4 +7,4 @@ source "$(dirname "${0}")/../util.sh" export JOB=kibana-oss-jest-integration checks-reporter-with-killswitch "OSS Jest Integration Tests" \ - node scripts/jest_integration --verbose + node scripts/jest_integration --ci --verbose diff --git a/docs/developer/contributing/development-functional-tests.asciidoc b/docs/developer/contributing/development-functional-tests.asciidoc index 580a5a000f391..f149e9de7aaba 100644 --- a/docs/developer/contributing/development-functional-tests.asciidoc +++ b/docs/developer/contributing/development-functional-tests.asciidoc @@ -6,7 +6,7 @@ We use functional tests to make sure the {kib} UI works as expected. It replaces [discrete] === Running functional tests -The `FunctionalTestRunner` is very bare bones and gets most of its functionality from its config file, located at {blob}test/functional/config.js[test/functional/config.js]. If you’re writing a plugin outside the {kib} repo, you will have your own config file. +The `FunctionalTestRunner` is very bare bones and gets most of its functionality from its config file, located at {blob}test/functional/config.js[test/functional/config.js] or {blob}x-pack/test/functional/config.js[x-pack/test/functional/config.js]. If you’re writing a plugin outside the {kib} repo, you will have your own config file. See <> for more info. There are three ways to run the tests depending on your goals: diff --git a/docs/developer/contributing/development-tests.asciidoc b/docs/developer/contributing/development-tests.asciidoc index 4cf667195153d..647dc8b3f3b26 100644 --- a/docs/developer/contributing/development-tests.asciidoc +++ b/docs/developer/contributing/development-tests.asciidoc @@ -1,8 +1,6 @@ [[development-tests]] == Testing -To ensure that your changes will not break other functionality, please run the test suite and build (<>) before submitting your Pull Request. - [discrete] === Running specific {kib} tests @@ -13,63 +11,57 @@ invoke them: |=== |Test runner |Test location |Runner command (working directory is {kib} root) -|Jest |`src/**/*.test.js` `src/**/*.test.ts` -|`yarn test:jest -t regexp [test path]` +|Jest |`**/*.test.{js,mjs,ts,tsx}` +|`yarn test:jest [test path]` -|Jest (integration) |`**/integration_tests/**/*.test.js` -|`yarn test:jest_integration -t regexp [test path]` +|Jest (integration) |`**/integration_tests/**/*.test.{js,mjs,ts,tsx}` +|`yarn test:jest_integration [test path]` |Mocha -|`src/**/__tests__/**/*.js` `!src/**/public/__tests__/*.js` `packages/kbn-dev-utils/src/**/__tests__/**/*.js` `tasks/**/__tests__/**/*.js` +|`**/__tests__/**/*.js` |`node scripts/mocha --grep=regexp [test path]` |Functional -|`test/*integration/**/config.js` `test/*functional/**/config.js` `test/accessibility/config.js` -|`yarn test:ftr:server --config test/[directory]/config.js``yarn test:ftr:runner --config test/[directory]/config.js --grep=regexp` +|`test/**/config.js` `x-pack/test/**/config.js` +|`node scripts/functional_tests_server --config [directory]/config.js``node scripts/functional_test_runner_ --config [directory]/config.js --grep=regexp` |=== -For X-Pack tests located in `x-pack/` see -link:{kib-repo}tree/{branch}/x-pack/README.md#testing[X-Pack Testing] - Test runner arguments: - Where applicable, the optional arguments -`-t=regexp` or `--grep=regexp` will only run tests or test suites +`--grep=regexp` will only run tests or test suites whose descriptions matches the regular expression. - `[test path]` is the relative path to the test file. -Examples: - Run the entire elasticsearch_service test suite: -`yarn test:jest src/core/server/elasticsearch/elasticsearch_service.test.ts` -- Run the jest test case whose description matches -`stops both admin and data clients`: -`yarn test:jest -t 'stops both admin and data clients' src/core/server/elasticsearch/elasticsearch_service.test.ts` -- Run the api integration test case whose description matches the given -string: ``` yarn test:ftr:server –config test/api_integration/config.js -yarn test:ftr:runner –config test/api_integration/config +=== Unit Testing -[discrete] -=== Cross-browser compatibility +Kibana primarily uses Jest for unit testing. Each plugin or package defines a `jest.config.js` that extends link:{kib-repo}tree/{branch}/packages/kbn-test/jest-preset.js[a preset] provided by the link:{kib-repo}tree/{branch}/packages/kbn-test[`@kbn/test`] package. Unless you intend to run all unit tests within the project, it's most efficient to provide the Jest configuration file for the plugin or package you're testing. -**Testing IE on OS X** +[source,bash] +---- +yarn jest --config src/plugins/dashboard/jest.config.js +---- -**Note:** IE11 is not supported from 7.9 onwards. +A script is available to provide a better user experience when testing while navigating throughout the repository. To run the tests within your current working directory, use `yarn test:jest`. Like the Jest CLI, you can also supply a path to determine which tests to run. + +[source,bash] +---- +kibana/src/plugins/dashboard/server$ yarn test:jest #or +kibana/src/plugins/dashboard$ yarn test:jest server #or +kibana$ yarn test:jest src/plugins/dashboard/server +---- + +Any additional options supplied to `test:jest` will be passed onto the Jest CLI with the resulting Jest command always being outputted. + +[source,bash] +---- +kibana/src/plugins/dashboard/server$ yarn test:jest --coverage + +# is equivelant to + +yarn jest --coverage --verbose --config /home/tyler/elastic/kibana/src/plugins/dashboard/jest.config.js server +---- + +NOTE: There are still a handful of legacy tests that use the Mocha test runner. For those tests, use `node scripts/mocha --grep=regexp [test path]`. Tests using Mocha are located within `__tests__` directories. -* http://www.vmware.com/products/fusion/fusion-evaluation.html[Download -VMWare Fusion]. -* https://developer.microsoft.com/en-us/microsoft-edge/tools/vms/#downloads[Download -IE virtual machines] for VMWare. -* Open VMWare and go to Window > Virtual Machine Library. Unzip the -virtual machine and drag the .vmx file into your Virtual Machine -Library. -* Right-click on the virtual machine you just added to your library and -select "`Snapshots…`", and then click the "`Take`" button in the modal -that opens. You can roll back to this snapshot when the VM expires in 90 -days. -* In System Preferences > Sharing, change your computer name to be -something simple, e.g. "`computer`". -* Run {kib} with `yarn start --host=computer.local` (substituting -your computer name). -* Now you can run your VM, open the browser, and navigate to -`http://computer.local:5601` to test {kib}. -* Alternatively you can use browserstack [discrete] === Running browser automation tests @@ -93,4 +85,30 @@ include::development-functional-tests.asciidoc[leveloffset=+1] include::development-unit-tests.asciidoc[leveloffset=+1] -include::development-accessibility-tests.asciidoc[leveloffset=+1] \ No newline at end of file +include::development-accessibility-tests.asciidoc[leveloffset=+1] + +[discrete] +=== Cross-browser compatibility + +**Testing IE on OS X** + +**Note:** IE11 is not supported from 7.9 onwards. + +* http://www.vmware.com/products/fusion/fusion-evaluation.html[Download +VMWare Fusion]. +* https://developer.microsoft.com/en-us/microsoft-edge/tools/vms/#downloads[Download +IE virtual machines] for VMWare. +* Open VMWare and go to Window > Virtual Machine Library. Unzip the +virtual machine and drag the .vmx file into your Virtual Machine +Library. +* Right-click on the virtual machine you just added to your library and +select "`Snapshots…`", and then click the "`Take`" button in the modal +that opens. You can roll back to this snapshot when the VM expires in 90 +days. +* In System Preferences > Sharing, change your computer name to be +something simple, e.g. "`computer`". +* Run {kib} with `yarn start --host=computer.local` (substituting +your computer name). +* Now you can run your VM, open the browser, and navigate to +`http://computer.local:5601` to test {kib}. +* Alternatively you can use browserstack \ No newline at end of file diff --git a/package.json b/package.json index 4fb88706be16f..ba6ac1e70248b 100644 --- a/package.json +++ b/package.json @@ -43,14 +43,12 @@ "preinstall": "node ./preinstall_check", "kbn": "node scripts/kbn", "es": "node scripts/es", - "test": "grunt test", "test:jest": "node scripts/jest", "test:jest_integration": "node scripts/jest_integration", "test:mocha": "node scripts/mocha", "test:ftr": "node scripts/functional_tests", "test:ftr:server": "node scripts/functional_tests_server", "test:ftr:runner": "node scripts/functional_test_runner", - "test:coverage": "grunt test:coverage", "checkLicenses": "node scripts/check_licenses --dev", "build": "node scripts/build --all-platforms", "start": "node scripts/kibana --dev", diff --git a/packages/kbn-test/src/index.ts b/packages/kbn-test/src/index.ts index 54b064f5cd49e..a88820eb281cc 100644 --- a/packages/kbn-test/src/index.ts +++ b/packages/kbn-test/src/index.ts @@ -62,3 +62,5 @@ export * from './functional_test_runner'; export { getUrl } from './jest/utils/get_url'; export { runCheckJestConfigsCli } from './jest/run_check_jest_configs_cli'; + +export { runJest } from './jest/run'; diff --git a/packages/kbn-test/src/jest/run.test.ts b/packages/kbn-test/src/jest/run.test.ts new file mode 100644 index 0000000000000..5be033baade6a --- /dev/null +++ b/packages/kbn-test/src/jest/run.test.ts @@ -0,0 +1,38 @@ +/* + * 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 { commonBasePath } from './run'; + +describe('commonBasePath', () => { + it('returns a common path', () => { + expect(commonBasePath(['foo/bar/baz', 'foo/bar/quux', 'foo/bar'])).toBe('foo/bar'); + }); + + it('handles an empty array', () => { + expect(commonBasePath([])).toBe(''); + }); + + it('handles no common path', () => { + expect(commonBasePath(['foo', 'bar'])).toBe(''); + }); + + it('matches full paths', () => { + expect(commonBasePath(['foo/bar', 'foo/bar_baz'])).toBe('foo'); + }); +}); diff --git a/packages/kbn-test/src/jest/run.ts b/packages/kbn-test/src/jest/run.ts new file mode 100644 index 0000000000000..3283b6c8901fa --- /dev/null +++ b/packages/kbn-test/src/jest/run.ts @@ -0,0 +1,110 @@ +/* + * 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. + */ + +// Run Jest tests +// +// Provides Jest with `--config` to the first jest.config.js file found in the current +// directory, or while going up in the directory chain. If the current working directory +// is nested under the config path, a pattern will be provided to Jest to only run the +// tests within that directory. +// +// Any additional options passed will be forwarded to Jest. +// +// See all cli options in https://facebook.github.io/jest/docs/cli.html + +import { resolve, relative, sep as osSep } from 'path'; +import { existsSync } from 'fs'; +import { run } from 'jest'; +import { buildArgv } from 'jest-cli/build/cli'; +import { ToolingLog } from '@kbn/dev-utils'; + +// yarn test:jest src/core/server/saved_objects +// yarn test:jest src/core/public/core_system.test.ts +// :kibana/src/core/server/saved_objects yarn test:jest + +export function runJest(configName = 'jest.config.js') { + const argv = buildArgv(process.argv); + + const log = new ToolingLog({ + level: argv.verbose ? 'verbose' : 'info', + writeTo: process.stdout, + }); + + if (!argv.config) { + const cwd = process.env.INIT_CWD || process.cwd(); + const testFiles = argv._.splice(2).map((p) => resolve(cwd, p)); + const commonTestFiles = commonBasePath(testFiles); + const testFilesProvided = testFiles.length > 0; + + log.verbose('cwd:', cwd); + log.verbose('testFiles:', testFiles.join(', ')); + log.verbose('commonTestFiles:', commonTestFiles); + + let configPath; + + // sets the working directory to the cwd or the common + // base directory of the provided test files + let wd = testFilesProvided ? commonTestFiles : cwd; + + configPath = resolve(wd, configName); + + while (!existsSync(configPath)) { + wd = resolve(wd, '..'); + configPath = resolve(wd, configName); + } + + log.verbose(`no config provided, found ${configPath}`); + process.argv.push('--config', configPath); + + if (!testFilesProvided) { + log.verbose(`no test files provided, setting to current directory`); + process.argv.push(relative(wd, cwd)); + } + + log.info('yarn jest', process.argv.slice(2).join(' ')); + } + + if (process.env.NODE_ENV == null) { + process.env.NODE_ENV = 'test'; + } + + run(); +} + +/** + * Finds the common basePath by sorting the array + * and comparing the first and last element + */ +export function commonBasePath(paths: string[] = [], sep = osSep) { + if (paths.length === 0) return ''; + + paths = paths.concat().sort(); + + const first = paths[0].split(sep); + const last = paths[paths.length - 1].split(sep); + + const length = first.length; + let i = 0; + + while (i < length && first[i] === last[i]) { + i++; + } + + return first.slice(0, i).join(sep); +} diff --git a/scripts/jest.js b/scripts/jest.js index 90f8da10f4c90..cb31d7785898d 100755 --- a/scripts/jest.js +++ b/scripts/jest.js @@ -17,27 +17,4 @@ * under the License. */ -// # Run Jest tests -// -// All args will be forwarded directly to Jest, e.g. to watch tests run: -// -// node scripts/jest --watch -// -// or to build code coverage: -// -// node scripts/jest --coverage -// -// See all cli options in https://facebook.github.io/jest/docs/cli.html - -if (process.argv.indexOf('--config') === -1) { - // append correct jest.config if none is provided - var configPath = require('path').resolve(__dirname, '../jest.config.oss.js'); - process.argv.push('--config', configPath); - console.log('Running Jest with --config', configPath); -} - -if (process.env.NODE_ENV == null) { - process.env.NODE_ENV = 'test'; -} - -require('jest').run(); +require('@kbn/test').runJest(); diff --git a/scripts/jest_integration.js b/scripts/jest_integration.js index f07d28939ef0c..1df79781fe26d 100755 --- a/scripts/jest_integration.js +++ b/scripts/jest_integration.js @@ -17,29 +17,6 @@ * under the License. */ -// # Run Jest integration tests -// -// All args will be forwarded directly to Jest, e.g. to watch tests run: -// -// node scripts/jest_integration --watch -// -// or to build code coverage: -// -// node scripts/jest_integration --coverage -// -// See all cli options in https://facebook.github.io/jest/docs/cli.html - process.argv.push('--runInBand'); -if (process.argv.indexOf('--config') === -1) { - // append correct jest.config if none is provided - var configPath = require('path').resolve(__dirname, '../jest.config.integration.js'); - process.argv.push('--config', configPath); - console.log('Running Jest with --config', configPath); -} - -if (process.env.NODE_ENV == null) { - process.env.NODE_ENV = 'test'; -} - -require('jest').run(); +require('@kbn/test').runJest('jest.config.integration.js'); diff --git a/src/core/server/saved_objects/migrations/README.md b/src/core/server/saved_objects/migrations/README.md index 91249024358ac..7db0b38773a67 100644 --- a/src/core/server/saved_objects/migrations/README.md +++ b/src/core/server/saved_objects/migrations/README.md @@ -206,9 +206,13 @@ There are three core entry points. ## Testing -Run jest tests: +Run Jest tests: -`node scripts/jest --testPathPattern=migrations --watch` +Documentation: https://www.elastic.co/guide/en/kibana/current/development-tests.html#_unit_testing + +``` +yarn test:jest src/core/server/saved_objects/migrations --watch +``` Run integration tests: diff --git a/src/plugins/embeddable/README.asciidoc b/src/plugins/embeddable/README.asciidoc index 10ec2b840ffa7..5e3c5066f46c7 100644 --- a/src/plugins/embeddable/README.asciidoc +++ b/src/plugins/embeddable/README.asciidoc @@ -40,5 +40,5 @@ Run unit tests [source,sh] -- -node scripts/jest embeddable +yarn test:jest src/plugins/embeddable -- diff --git a/test/scripts/jenkins_unit.sh b/test/scripts/jenkins_unit.sh index c788a4a5b01ae..34b194ba5d4a7 100755 --- a/test/scripts/jenkins_unit.sh +++ b/test/scripts/jenkins_unit.sh @@ -35,7 +35,7 @@ if [[ -z "$CODE_COVERAGE" ]] ; then ./test/scripts/checks/test_hardening.sh else # echo " -> Running jest tests with coverage" - # node scripts/jest --ci --verbose --coverage + # node scripts/jest --ci --verbose --coverage --config jest.config.oss.js # rename_coverage_file "oss" # echo "" # echo "" diff --git a/test/scripts/jenkins_xpack.sh b/test/scripts/jenkins_xpack.sh index 438a85aa86142..6199aa0e5cdfc 100755 --- a/test/scripts/jenkins_xpack.sh +++ b/test/scripts/jenkins_xpack.sh @@ -4,23 +4,15 @@ source test/scripts/jenkins_test_setup.sh if [[ -z "$CODE_COVERAGE" ]] ; then echo " -> Running jest tests" - cd "$XPACK_DIR" - checks-reporter-with-killswitch "X-Pack Jest" node --max-old-space-size=6144 scripts/jest --ci --verbose - echo "" - echo "" - - # echo " -> Running jest integration tests" - # cd "$XPACK_DIR" - # node scripts/jest_integration --ci --verbose - # echo "" - # echo "" + + ./test/scripts/test/xpack_jest_unit.sh else echo " -> Running jest tests with coverage" - cd "$XPACK_DIR" + # build runtime for canvas echo "NODE_ENV=$NODE_ENV" - node ./plugins/canvas/scripts/shareable_runtime - node --max-old-space-size=6144 scripts/jest --ci --verbose --coverage + node ./x-pack/plugins/canvas/scripts/shareable_runtime + node --max-old-space-size=6144 scripts/jest x-pack --ci --verbose --coverage # rename file in order to be unique one test -f ../target/kibana-coverage/jest/coverage-final.json \ && mv ../target/kibana-coverage/jest/coverage-final.json \ diff --git a/test/scripts/test/jest_integration.sh b/test/scripts/test/jest_integration.sh index 8791248e9a166..78ed804f88430 100755 --- a/test/scripts/test/jest_integration.sh +++ b/test/scripts/test/jest_integration.sh @@ -3,4 +3,4 @@ source src/dev/ci_setup/setup_env.sh checks-reporter-with-killswitch "Jest Integration Tests" \ - node scripts/jest_integration + node scripts/jest_integration --ci --verbose diff --git a/test/scripts/test/jest_unit.sh b/test/scripts/test/jest_unit.sh index de5e16c2b1366..88c0fe528b88c 100755 --- a/test/scripts/test/jest_unit.sh +++ b/test/scripts/test/jest_unit.sh @@ -3,4 +3,4 @@ source src/dev/ci_setup/setup_env.sh checks-reporter-with-killswitch "Jest Unit Tests" \ - node scripts/jest + node scripts/jest --config jest.config.oss.js --ci --verbose --maxWorkers=5 diff --git a/test/scripts/test/xpack_jest_unit.sh b/test/scripts/test/xpack_jest_unit.sh index 93d70ec355391..33b1c8a2b5183 100755 --- a/test/scripts/test/xpack_jest_unit.sh +++ b/test/scripts/test/xpack_jest_unit.sh @@ -2,5 +2,5 @@ source src/dev/ci_setup/setup_env.sh -cd x-pack -checks-reporter-with-killswitch "X-Pack Jest" node --max-old-space-size=6144 scripts/jest --ci --verbose --maxWorkers=10 +checks-reporter-with-killswitch "X-Pack Jest" \ + node scripts/jest x-pack --ci --verbose --maxWorkers=5 diff --git a/x-pack/README.md b/x-pack/README.md index 0210b00d8efc8..41ea4cc4e469a 100644 --- a/x-pack/README.md +++ b/x-pack/README.md @@ -15,46 +15,11 @@ Example: `yarn es snapshot --license trial --password changeme` By default, this will also set the password for native realm accounts to the password provided (`changeme` by default). This includes that of the `kibana_system` user which `elasticsearch.username` defaults to in development. If you wish to specify a password for a given native realm account, you can do that like so: `--password.kibana_system=notsecure` # Testing -## Running specific tests -| Test runner | Test location | Runner command (working directory is kibana/x-pack) | -| ------------ | ----------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | -| Jest | `x-pack/**/*.test.js`
`x-pack/**/*.test.ts` | `cd x-pack && node scripts/jest -t regexp [test path]` | -| Functional | `x-pack/test/*integration/**/config.js`
`x-pack/test/*functional/config.js`
`x-pack/test/accessibility/config.js` | `node scripts/functional_tests_server --config x-pack/test/[directory]/config.js`
`node scripts/functional_test_runner --config x-pack/test/[directory]/config.js --grep=regexp` | -Examples: - - Run the jest test case whose description matches 'filtering should skip values of null': - `cd x-pack && yarn test:jest -t 'filtering should skip values of null' plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container_service.test.js` - - Run the x-pack api integration test case whose description matches the given string: - `node scripts/functional_tests_server --config x-pack/test/api_integration/config.ts` - `node scripts/functional_test_runner --config x-pack/test/api_integration/config.ts --grep='apis Monitoring Beats list with restarted beat instance should load multiple clusters'` - -In addition to to providing a regular expression argument, specific tests can also be run by appeding `.only` to an `it` or `describe` function block. E.g. `describe(` to `describe.only(`. - -## Running all tests - -You can run unit tests by running: - -``` -yarn test -``` - -If you want to run tests only for a specific plugin (to save some time), you can run: - -``` -yarn test --plugins [,]* # where is "reporting", etc. -``` - -#### Running server unit tests -You can run mocha unit tests by running: - -``` -yarn test:mocha -``` +For information on testing, see [the Elastic functional test development guide](https://www.elastic.co/guide/en/kibana/current/development-functional-tests.html). #### Running functional tests -For more info, see [the Elastic functional test development guide](https://www.elastic.co/guide/en/kibana/current/development-tests.html#development-functional-tests). - The functional UI tests, the API integration tests, and the SAML API integration tests are all run against a live browser, Kibana, and Elasticsearch install. Each set of tests is specified with a unique config that describes how to start the Elasticsearch server, the Kibana server, and what tests to run against them. The sets of tests that exist today are *functional UI tests* ([specified by this config](test/functional/config.js)), *API integration tests* ([specified by this config](test/api_integration/config.ts)), and *SAML API integration tests* ([specified by this config](test/security_api_integration/saml.config.ts)). The script runs all sets of tests sequentially like so: @@ -116,7 +81,7 @@ node scripts/functional_tests --config test/security_api_integration/saml.config Jest integration tests can be used to test behavior with Elasticsearch and the Kibana server. ```sh -node scripts/jest_integration +yarn test:jest_integration ``` An example test exists at [test_utils/jest/integration_tests/example_integration.test.ts](test_utils/jest/integration_tests/example_integration.test.ts) diff --git a/x-pack/package.json b/x-pack/package.json index ddcb14348ed17..34ef8bb589b44 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -12,7 +12,7 @@ "build": "../node_modules/.bin/gulp build", "testonly": "echo 'Deprecated, use `yarn test`'", "test": "../node_modules/.bin/gulp test", - "test:jest": "node scripts/jest", + "test:jest": "node ../scripts/jest", "test:mocha": "node scripts/mocha" }, "kibana": { diff --git a/x-pack/plugins/beats_management/readme.md b/x-pack/plugins/beats_management/readme.md index 36db612f7affd..75adf428772e4 100644 --- a/x-pack/plugins/beats_management/readme.md +++ b/x-pack/plugins/beats_management/readme.md @@ -7,7 +7,13 @@ Failure to have auth enabled in Kibana will make for a broken UI. UI-based error ### Unit tests -From `~/kibana/x-pack`, run `node scripts/jest.js plugins/beats --watch`. +Run Jest tests: + +Documentation: https://www.elastic.co/guide/en/kibana/current/development-tests.html#_unit_testing + +``` +yarn test:jest x-pack/plugins/beats --watch +``` ### API tests diff --git a/x-pack/plugins/encrypted_saved_objects/README.md b/x-pack/plugins/encrypted_saved_objects/README.md index 0a5e79a96f02a..99ebf771126d5 100644 --- a/x-pack/plugins/encrypted_saved_objects/README.md +++ b/x-pack/plugins/encrypted_saved_objects/README.md @@ -235,9 +235,12 @@ const migration780 = encryptedSavedObjects.createMigration( ### Unit tests -From `kibana-root-folder/x-pack`, run: -```bash -$ node scripts/jest.js +Run Jest tests: + +Documentation: https://www.elastic.co/guide/en/kibana/current/development-tests.html#_unit_testing + +``` +yarn test:jest x-pack/plugins/encrypted_saved_objects --watch ``` ### API Integration tests diff --git a/x-pack/plugins/enterprise_search/README.md b/x-pack/plugins/enterprise_search/README.md index 711c7c7b065d2..3fd6154b1d265 100644 --- a/x-pack/plugins/enterprise_search/README.md +++ b/x-pack/plugins/enterprise_search/README.md @@ -29,10 +29,10 @@ To debug Kea state in-browser, Kea recommends [Redux Devtools](https://kea.js.or ### Unit tests -From `kibana-root-folder/x-pack`, run: +Documentation: https://www.elastic.co/guide/en/kibana/current/development-tests.html#_unit_testing -```bash -yarn test:jest plugins/enterprise_search +``` +yarn test:jest x-pack/plugins/enterprise_search --watch ``` ### E2E tests diff --git a/x-pack/plugins/event_log/README.md b/x-pack/plugins/event_log/README.md index 941dedc3d1093..eb7fbc9d590fa 100644 --- a/x-pack/plugins/event_log/README.md +++ b/x-pack/plugins/event_log/README.md @@ -53,9 +53,10 @@ public setup(core: CoreSetup, { eventLog }: PluginSetupDependencies) { ### Unit tests -From `kibana-root-folder/x-pack`, run: -```bash -$ node node scripts/jest plugins/event_log +Documentation: https://www.elastic.co/guide/en/kibana/current/development-tests.html#_unit_testing + +``` +yarn test:jest x-pack/plugins/event_log --watch ``` ### API Integration tests diff --git a/x-pack/plugins/graph/README.md b/x-pack/plugins/graph/README.md index 9cc2617abe94c..99becabf70002 100644 --- a/x-pack/plugins/graph/README.md +++ b/x-pack/plugins/graph/README.md @@ -6,7 +6,7 @@ Graph shows only up in the side bar if your server is running on a platinum or t ## Common commands -* Run tests `node x-pack/scripts/jest.js --watch plugins/graph` +* Run tests `yarn test:jest x-pack/plugins/graph --watch` * Run type check `node scripts/type_check.js --project=x-pack/tsconfig.json` * Run linter `node scripts/eslint.js x-pack/plugins/graph` * Run functional tests (make sure to stop dev server) diff --git a/x-pack/plugins/lens/readme.md b/x-pack/plugins/lens/readme.md index 98bb60827af42..9fa6ad8ee30af 100644 --- a/x-pack/plugins/lens/readme.md +++ b/x-pack/plugins/lens/readme.md @@ -4,7 +4,7 @@ Run all tests from the `x-pack` root directory -- Unit tests: `node scripts/jest --watch lens` +- Unit tests: `yarn test:jest x-pack/plugins/lens` - Functional tests: - Run `node scripts/functional_tests_server` - Run `node ../scripts/functional_test_runner.js --config ./test/functional/config.js --grep="lens app"` diff --git a/x-pack/plugins/maps/README.md b/x-pack/plugins/maps/README.md index aae5a708b680b..729cba26f72ab 100644 --- a/x-pack/plugins/maps/README.md +++ b/x-pack/plugins/maps/README.md @@ -7,7 +7,7 @@ Visualize geo data from Elasticsearch or 3rd party geo-services. Run all tests from the `x-pack` root directory -- Unit tests: `node scripts/jest --watch maps` +- Unit tests: `yarn test:jest x-pack/plugins/maps --watch` - Functional tests: - Run `node scripts/functional_tests_server` - Run `node ../scripts/functional_test_runner.js --config ./test/functional/config.js --grep="maps app"` \ No newline at end of file diff --git a/x-pack/plugins/ml/readme.md b/x-pack/plugins/ml/readme.md index 2369f3d077037..b97e7cbc2ee04 100644 --- a/x-pack/plugins/ml/readme.md +++ b/x-pack/plugins/ml/readme.md @@ -68,30 +68,32 @@ These data sets are now ready be analyzed in ML jobs in Kibana. ### Jest tests -Run the test following jest tests from `kibana/x-pack`. +Documentation: https://www.elastic.co/guide/en/kibana/current/development-tests.html#_unit_testing + +Run the test following jest tests from `kibana/x-pack/plugins/ml`. New snapshots, all plugins: ``` -node scripts/jest +yarn test:jest ``` Update snapshots for the ML plugin: ``` -node scripts/jest plugins/ml -u +yarn test:jest -u ``` Update snapshots for a specific directory only: ``` -node scripts/jest plugins/ml/public/application/settings/filter_lists +yarn test:jest public/application/settings/filter_lists ``` Run tests with verbose output: ``` -node scripts/jest plugins/ml --verbose +yarn test:jest --verbose ``` ### Functional tests diff --git a/x-pack/plugins/task_manager/README.md b/x-pack/plugins/task_manager/README.md index d3c8ecb6c4505..6cd42cda9af6a 100644 --- a/x-pack/plugins/task_manager/README.md +++ b/x-pack/plugins/task_manager/README.md @@ -505,9 +505,11 @@ The task manager's public API is create / delete / list. Updates aren't directly ## Testing - Unit tests: + + Documentation: https://www.elastic.co/guide/en/kibana/current/development-tests.html#_unit_testing + ``` - cd x-pack - node scripts/jest --testPathPattern=task_manager --watch + yarn test:jest x-pack/plugins/task_manager --watch ``` - Integration tests: ``` diff --git a/x-pack/plugins/transform/readme.md b/x-pack/plugins/transform/readme.md index 07500876f55c2..a1005c43687e2 100644 --- a/x-pack/plugins/transform/readme.md +++ b/x-pack/plugins/transform/readme.md @@ -67,30 +67,32 @@ These data sets are now ready to be used for creating transforms in Kibana. ### Jest tests -Run the test following jest tests from `kibana/x-pack`. +Documentation: https://www.elastic.co/guide/en/kibana/current/development-tests.html#_unit_testing + +Run the test following jest tests from `kibana/x-pack/plugins/transform. New snapshots, all plugins: ``` -node scripts/jest +yarn test:jest ``` Update snapshots for the transform plugin: ``` -node scripts/jest plugins/transform -u +yarn test:jest -u ``` Update snapshots for a specific directory only: ``` -node scripts/jest x-pack/plugins/transform/public/app/sections +yarn test:jest public/app/sections ``` Run tests with verbose output: ``` -node scripts/jest plugins/transform --verbose +yarn test:jest --verbose ``` ### Functional tests diff --git a/x-pack/plugins/uptime/README.md b/x-pack/plugins/uptime/README.md index 54bf48e8d3c86..34d206a1fdcb8 100644 --- a/x-pack/plugins/uptime/README.md +++ b/x-pack/plugins/uptime/README.md @@ -42,7 +42,11 @@ There's also a `rest_api` folder that defines the structure of the RESTful API e ### Unit tests -From `~/kibana/x-pack`, run `node scripts/jest.js`. +Documentation: https://www.elastic.co/guide/en/kibana/current/development-tests.html#_unit_testing + +``` +yarn test:jest x-pack/plugins/uptime +``` ### Functional tests diff --git a/x-pack/scripts/jest.js b/x-pack/scripts/jest.js index 68cfcf082f818..aca7e558301df 100644 --- a/x-pack/scripts/jest.js +++ b/x-pack/scripts/jest.js @@ -4,15 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -if (process.argv.indexOf('--config') === -1) { - // append correct jest.config if none is provided - const configPath = require('path').resolve(__dirname, '../jest.config.js'); - process.argv.push('--config', configPath); - console.log('Running Jest with --config', configPath); -} - -if (process.env.NODE_ENV == null) { - process.env.NODE_ENV = 'test'; -} - -require('jest').run(); +require('@kbn/test').runJest(); From f6cd2648afa02050de32991c5a463dc44cf22926 Mon Sep 17 00:00:00 2001 From: Paul Tavares <56442535+paul-tavares@users.noreply.github.com> Date: Mon, 14 Dec 2020 17:09:34 -0500 Subject: [PATCH 49/95] [Security_Solution][Endpoint] Register Custom tab into Fleet Endpoint Integration Detail (#85643) * Fleet: add component props to the Package Custom UI extension * Endpoint: Register UI Extension with fleet for endpoint custom content * Endpoint: UI for Trusted Apps custom entry --- .../sections/epm/screens/detail/content.tsx | 33 +++-- .../applications/fleet/types/ui_extensions.ts | 10 +- x-pack/plugins/fleet/public/index.ts | 1 + .../components/endpoint/link_to_app.tsx | 2 +- .../components/fleet_trusted_apps_card.tsx | 92 ++++++++++++ .../components/link_with_icon.tsx | 29 ++++ .../components/trusted_app_items_summary.tsx | 68 +++++++++ .../index.tsx | 21 +++ .../endpoint_policy_edit_extension.tsx | 136 +----------------- ...lazy_endpoint_package_custom_extension.tsx | 29 ++++ .../security_solution/public/plugin.tsx | 7 + .../apps/endpoint/fleet_integrations.ts | 40 ++++++ .../apps/endpoint/index.ts | 1 + .../apps/endpoint/policy_details.ts | 29 ---- .../page_objects/fleet_integrations_page.ts | 32 +++++ .../page_objects/index.ts | 2 + .../services/endpoint_policy.ts | 11 ++ 17 files changed, 367 insertions(+), 176 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_trusted_apps_card.tsx create mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/link_with_icon.tsx create mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/trusted_app_items_summary.tsx create mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/index.tsx create mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/lazy_endpoint_package_custom_extension.tsx create mode 100644 x-pack/test/security_solution_endpoint/apps/endpoint/fleet_integrations.ts create mode 100644 x-pack/test/security_solution_endpoint/page_objects/fleet_integrations_page.ts diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/content.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/content.tsx index b19a82d3100c5..f0051fb54e124 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/content.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/content.tsx @@ -5,11 +5,11 @@ */ import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; -import React from 'react'; +import React, { memo, useMemo } from 'react'; import styled from 'styled-components'; import { Redirect } from 'react-router-dom'; import { DetailParams } from '.'; -import { PackageInfo } from '../../../../types'; +import { DetailViewPanelName, PackageInfo } from '../../../../types'; import { AssetsFacetGroup } from '../../components/assets_facet_group'; import { CenterColumn, LeftColumn, RightColumn } from './layout'; import { OverviewPanel } from './overview_panel'; @@ -18,6 +18,7 @@ import { SettingsPanel } from './settings_panel'; import { useUIExtension } from '../../../../hooks/use_ui_extension'; import { ExtensionWrapper } from '../../../../components/extension_wrapper'; import { useLink } from '../../../../hooks'; +import { pkgKeyFromPackageInfo } from '../../../../services/pkg_key_from_package_info'; type ContentProps = PackageInfo & Pick; @@ -34,12 +35,17 @@ const ContentFlexGroup = styled(EuiFlexGroup)` `; export function Content(props: ContentProps) { - const showRightColumn = props.panel !== 'policies'; + const { panel } = props; + const showRightColumn = useMemo(() => { + const fullWidthContentPages: DetailViewPanelName[] = ['policies', 'custom']; + return !fullWidthContentPages.includes(panel!); + }, [panel]); + return ( - + {showRightColumn && ( @@ -50,9 +56,14 @@ export function Content(props: ContentProps) { ); } -type ContentPanelProps = PackageInfo & Pick; -export function ContentPanel(props: ContentPanelProps) { - const { panel, name, version, assets, title, removable, latestVersion } = props; +interface ContentPanelProps { + packageInfo: PackageInfo; + panel: DetailViewPanelName; +} +export const ContentPanel = memo(({ panel, packageInfo }) => { + const { name, version, assets, title, removable, latestVersion } = packageInfo; + const pkgkey = pkgKeyFromPackageInfo(packageInfo); + const CustomView = useUIExtension(name, 'package-detail-custom'); const { getPath } = useLink(); @@ -73,16 +84,16 @@ export function ContentPanel(props: ContentPanelProps) { case 'custom': return CustomView ? ( - + ) : ( - + ); case 'overview': default: - return ; + return ; } -} +}); type RightColumnContentProps = PackageInfo & Pick; function RightColumnContent(props: RightColumnContentProps) { diff --git a/x-pack/plugins/fleet/public/applications/fleet/types/ui_extensions.ts b/x-pack/plugins/fleet/public/applications/fleet/types/ui_extensions.ts index d35e5f4744449..b3a741806aa88 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/types/ui_extensions.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/types/ui_extensions.ts @@ -5,7 +5,7 @@ */ import { ComponentType, LazyExoticComponent } from 'react'; -import { NewPackagePolicy, PackagePolicy } from './index'; +import { NewPackagePolicy, PackageInfo, PackagePolicy } from './index'; /** Register a Fleet UI extension */ export type UIExtensionRegistrationCallback = (extensionPoint: UIExtensionPoint) => void; @@ -80,7 +80,13 @@ export interface PackagePolicyCreateExtension { /** * UI Component Extension is used to display a Custom tab (and view) under a given Integration */ -export type PackageCustomExtensionComponent = ComponentType; +export type PackageCustomExtensionComponent = ComponentType; + +export interface PackageCustomExtensionComponentProps { + /** The package key value that should be used used for URLs */ + pkgkey: string; + packageInfo: PackageInfo; +} /** Extension point registration contract for Integration details Custom view */ export interface PackageCustomExtension { diff --git a/x-pack/plugins/fleet/public/index.ts b/x-pack/plugins/fleet/public/index.ts index be53af77f4b46..6271a0713cbdb 100644 --- a/x-pack/plugins/fleet/public/index.ts +++ b/x-pack/plugins/fleet/public/index.ts @@ -17,3 +17,4 @@ export * from './applications/fleet/types/intra_app_route_state'; export * from './applications/fleet/types/ui_extensions'; export { pagePathGetters } from './applications/fleet/constants'; +export { pkgKeyFromPackageInfo } from './applications/fleet/services/pkg_key_from_package_info'; diff --git a/x-pack/plugins/security_solution/public/common/components/endpoint/link_to_app.tsx b/x-pack/plugins/security_solution/public/common/components/endpoint/link_to_app.tsx index 66cfb0398b44c..f4a3306f45e36 100644 --- a/x-pack/plugins/security_solution/public/common/components/endpoint/link_to_app.tsx +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/link_to_app.tsx @@ -8,7 +8,7 @@ import React, { memo, MouseEventHandler } from 'react'; import { EuiLink, EuiLinkProps, EuiButton, EuiButtonProps } from '@elastic/eui'; import { useNavigateToAppEventHandler } from '../../hooks/endpoint/use_navigate_to_app_event_handler'; -type LinkToAppProps = (EuiLinkProps | EuiButtonProps) & { +export type LinkToAppProps = (EuiLinkProps | EuiButtonProps) & { /** the app id - normally the value of the `id` in that plugin's `kibana.json` */ appId: string; /** Any app specific path (route) */ diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_trusted_apps_card.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_trusted_apps_card.tsx new file mode 100644 index 0000000000000..3dc0356bfd518 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/fleet_trusted_apps_card.tsx @@ -0,0 +1,92 @@ +/* + * 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, { memo, useMemo } from 'react'; +import { ApplicationStart } from 'kibana/public'; +import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiText } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + PackageCustomExtensionComponentProps, + pagePathGetters, +} from '../../../../../../../../../fleet/public'; +import { useKibana } from '../../../../../../../../../../../src/plugins/kibana_react/public'; +import { getTrustedAppsListPath } from '../../../../../../common/routing'; +import { TrustedAppsListPageRouteState } from '../../../../../../../../common/endpoint/types'; +import { PLUGIN_ID as FLEET_PLUGIN_ID } from '../../../../../../../../../fleet/common'; +import { MANAGEMENT_APP_ID } from '../../../../../../common/constants'; +import { LinkWithIcon } from './link_with_icon'; +import { TrustedAppItemsSummary } from './trusted_app_items_summary'; + +export const FleetTrustedAppsCard = memo(({ pkgkey }) => { + const { + services: { + application: { getUrlForApp }, + }, + } = useKibana<{ application: ApplicationStart }>(); + + const trustedAppsListUrlPath = getTrustedAppsListPath(); + + const trustedAppRouteState = useMemo(() => { + const fleetPackageCustomUrlPath = `#${pagePathGetters.integration_details({ + pkgkey, + panel: 'custom', + })}`; + return { + backButtonLabel: i18n.translate( + 'xpack.securitySolution.endpoint.fleetCustomExtension.backButtonLabel', + { defaultMessage: 'Back to Endpoint Integration' } + ), + onBackButtonNavigateTo: [ + FLEET_PLUGIN_ID, + { + path: fleetPackageCustomUrlPath, + }, + ], + backButtonUrl: getUrlForApp(FLEET_PLUGIN_ID, { + path: fleetPackageCustomUrlPath, + }), + }; + }, [getUrlForApp, pkgkey]); + + return ( + + + + +

+ +

+
+
+ + + + + + + + + + +
+
+ ); +}); + +FleetTrustedAppsCard.displayName = 'FleetTrustedAppsCard'; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/link_with_icon.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/link_with_icon.tsx new file mode 100644 index 0000000000000..954c30670bbe4 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/link_with_icon.tsx @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import styled from 'styled-components'; +import React, { FC, memo } from 'react'; +import { EuiIcon } from '@elastic/eui'; +import { + LinkToApp, + LinkToAppProps, +} from '../../../../../../../common/components/endpoint/link_to_app'; + +const LinkLabel = styled.span` + display: inline-block; + padding-right: ${(props) => props.theme.eui.paddingSizes.s}; +`; + +export const LinkWithIcon: FC = memo(({ children, ...props }) => { + return ( + + {children} + + + ); +}); + +LinkWithIcon.displayName = 'LinkWithIcon'; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/trusted_app_items_summary.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/trusted_app_items_summary.tsx new file mode 100644 index 0000000000000..f0d3b51d20d67 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/components/trusted_app_items_summary.tsx @@ -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 { EuiBadge, EuiBadgeProps, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; +import React, { FC, memo, useEffect, useState } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { CoreStart } from 'kibana/public'; +import { useKibana } from '../../../../../../../../../../../src/plugins/kibana_react/public'; +import { TrustedAppsHttpService } from '../../../../../trusted_apps/service'; + +export const TrustedAppItemsSummary = memo(() => { + const { + services: { http }, + } = useKibana(); + const [total, setTotal] = useState(0); + const [trustedAppsApi] = useState(() => new TrustedAppsHttpService(http)); + + useEffect(() => { + trustedAppsApi + .getTrustedAppsList({ + page: 1, + per_page: 1, + }) + .then((response) => { + setTotal(response.total); + }); + }, [trustedAppsApi]); + + return ( +
+ + + +
+ ); +}); + +TrustedAppItemsSummary.displayName = 'TrustedAppItemsSummary'; + +const SummaryStat: FC<{ value: number; color?: EuiBadgeProps['color'] }> = memo( + ({ children, value, color, ...commonProps }) => { + return ( + + + + {children} + + + {value} + + + + ); + } +); + +SummaryStat.displayName = 'SummaryState'; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/index.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/index.tsx new file mode 100644 index 0000000000000..385ddeab33feb --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_package_custom_extension/index.tsx @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { memo } from 'react'; +import { PackageCustomExtensionComponentProps } from '../../../../../../../../fleet/public'; +import { FleetTrustedAppsCard } from './components/fleet_trusted_apps_card'; + +export const EndpointPackageCustomExtension = memo( + (props) => { + return ( +
+ +
+ ); + } +); + +EndpointPackageCustomExtension.displayName = 'EndpointPackageCustomExtension'; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_edit_extension.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_edit_extension.tsx index 6d464280b2763..916a11ac65dd1 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_edit_extension.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_edit_extension.tsx @@ -4,35 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { memo, useCallback, useEffect, useMemo, useState } from 'react'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { - EuiCallOut, - EuiText, - EuiSpacer, - EuiFlexGroup, - EuiFlexItem, - EuiContextMenuPanel, - EuiPopover, - EuiButton, - EuiContextMenuItem, - EuiContextMenuPanelProps, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; +import React, { memo, useEffect, useState } from 'react'; +import { EuiSpacer } from '@elastic/eui'; import { useDispatch } from 'react-redux'; import { - pagePathGetters, PackagePolicyEditExtensionComponentProps, NewPackagePolicy, } from '../../../../../../../fleet/public'; -import { getPolicyDetailPath, getTrustedAppsListPath } from '../../../../common/routing'; -import { MANAGEMENT_APP_ID } from '../../../../common/constants'; -import { - PolicyDetailsRouteState, - TrustedAppsListPageRouteState, -} from '../../../../../../common/endpoint/types'; -import { useKibana } from '../../../../../common/lib/kibana'; -import { useNavigateToAppEventHandler } from '../../../../../common/hooks/endpoint/use_navigate_to_app_event_handler'; +import { getPolicyDetailPath } from '../../../../common/routing'; import { PolicyDetailsForm } from '../policy_details_form'; import { AppAction } from '../../../../../common/store/actions'; import { usePolicyDetailsSelector } from '../policy_hooks'; @@ -46,12 +25,6 @@ export const EndpointPolicyEditExtension = memo { return ( <> - - - - - - @@ -126,106 +99,3 @@ const WrappedPolicyDetailsForm = memo<{ ); }); WrappedPolicyDetailsForm.displayName = 'WrappedPolicyDetailsForm'; - -const EditFlowMessage = memo<{ - agentPolicyId: string; - integrationPolicyId: string; -}>(({ agentPolicyId, integrationPolicyId }) => { - const { - services: { - application: { getUrlForApp }, - }, - } = useKibana(); - - const [isMenuOpen, setIsMenuOpen] = useState(false); - - const navigateBackToIngest = useMemo< - PolicyDetailsRouteState['onSaveNavigateTo'] & - PolicyDetailsRouteState['onCancelNavigateTo'] & - TrustedAppsListPageRouteState['onBackButtonNavigateTo'] - >(() => { - return [ - 'fleet', - { - path: `#${pagePathGetters.edit_integration({ - policyId: agentPolicyId, - packagePolicyId: integrationPolicyId!, - })}`, - }, - ]; - }, [agentPolicyId, integrationPolicyId]); - - const handleClosePopup = useCallback(() => setIsMenuOpen(false), []); - - const handleTrustedAppsAction = useNavigateToAppEventHandler( - MANAGEMENT_APP_ID, - { - path: getTrustedAppsListPath(), - state: { - backButtonUrl: navigateBackToIngest[1]?.path - ? `${getUrlForApp('fleet')}${navigateBackToIngest[1].path}` - : undefined, - onBackButtonNavigateTo: navigateBackToIngest, - backButtonLabel: i18n.translate( - 'xpack.securitySolution.endpoint.fleet.editPackagePolicy.trustedAppsMessageReturnBackLabel', - { defaultMessage: 'Back to Edit Integration' } - ), - }, - } - ); - - const menuButton = useMemo(() => { - return ( - setIsMenuOpen((prevState) => !prevState)} - data-test-subj="endpointActions" - > - - - ); - }, []); - - const actionItems = useMemo(() => { - return [ - - - , - ]; - }, [handleTrustedAppsAction]); - - return ( - - - - - - - - - - - ); -}); -EditFlowMessage.displayName = 'EditFlowMessage'; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/lazy_endpoint_package_custom_extension.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/lazy_endpoint_package_custom_extension.tsx new file mode 100644 index 0000000000000..d9616b3862893 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/lazy_endpoint_package_custom_extension.tsx @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CoreStart } from 'kibana/public'; +import { lazy } from 'react'; +import { StartPlugins } from '../../../../../types'; +import { PackageCustomExtensionComponent } from '../../../../../../../fleet/public'; + +export const getLazyEndpointPackageCustomExtension = ( + coreStart: CoreStart, + depsStart: Pick +) => { + return lazy(async () => { + const [{ withSecurityContext }, { EndpointPackageCustomExtension }] = await Promise.all([ + import('./with_security_context'), + import('./endpoint_package_custom_extension'), + ]); + return { + default: withSecurityContext({ + coreStart, + depsStart, + WrappedComponent: EndpointPackageCustomExtension, + }), + }; + }); +}; diff --git a/x-pack/plugins/security_solution/public/plugin.tsx b/x-pack/plugins/security_solution/public/plugin.tsx index 0b5093ff50c39..015a965be8f78 100644 --- a/x-pack/plugins/security_solution/public/plugin.tsx +++ b/x-pack/plugins/security_solution/public/plugin.tsx @@ -64,6 +64,7 @@ import { getCaseConnectorUI } from './cases/components/connectors'; import { licenseService } from './common/hooks/use_license'; import { getLazyEndpointPolicyEditExtension } from './management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_edit_extension'; import { LazyEndpointPolicyCreateExtension } from './management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_create_extension'; +import { getLazyEndpointPackageCustomExtension } from './management/pages/policy/view/ingest_manager_integration/lazy_endpoint_package_custom_extension'; export class Plugin implements IPlugin { private kibanaVersion: string; @@ -346,6 +347,12 @@ export class Plugin implements IPlugin { + beforeEach(async () => { + await fleetIntegrations.navigateToIntegrationDetails( + await policyTestResources.getEndpointPkgKey() + ); + }); + + it('should show the Custom tab', async () => { + await fleetIntegrations.integrationDetailCustomTabExistsOrFail(); + }); + + it('should display the endpoint custom content', async () => { + await (await fleetIntegrations.findIntegrationDetailCustomTab()).click(); + await testSubjects.existOrFail('fleetEndpointPackageCustomContent'); + }); + + it('should show the Trusted Apps page when link is clicked', async () => { + await (await fleetIntegrations.findIntegrationDetailCustomTab()).click(); + await (await testSubjects.find('linkToTrustedApps')).click(); + await trustedApps.ensureIsOnTrustedAppsListPage(); + }); + }); + }); +} diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts index 3103d461669f1..bb740ef8acb88 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/index.ts @@ -33,5 +33,6 @@ export default function (providerContext: FtrProviderContext) { loadTestFile(require.resolve('./resolver')); loadTestFile(require.resolve('./endpoint_telemetry')); loadTestFile(require.resolve('./trusted_apps_list')); + loadTestFile(require.resolve('./fleet_integrations')); }); } diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts index 355e494cb459e..1a5c99294c281 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts @@ -553,35 +553,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { } }); - it('should show callout', async () => { - await testSubjects.existOrFail('endpointPackagePolicy_edit'); - }); - - it('should show actions button with expected action items', async () => { - const actionsButton = await pageObjects.ingestManagerCreatePackagePolicy.findEndpointActionsButton(); - await actionsButton.click(); - const menuPanel = await testSubjects.find('endpointActionsMenuPanel'); - const actionItems = await menuPanel.findAllByTagName<'button'>('button'); - const expectedItems = ['Edit Trusted Applications']; - - for (const action of actionItems) { - const buttonText = await action.getVisibleText(); - expect(buttonText).to.be(expectedItems.find((item) => item === buttonText)); - } - }); - - it('should navigate to Trusted Apps', async () => { - await pageObjects.ingestManagerCreatePackagePolicy.selectEndpointAction('trustedApps'); - await pageObjects.trustedApps.ensureIsOnTrustedAppsListPage(); - }); - - it('should show the back button on Trusted Apps Page and navigate back to fleet', async () => { - await pageObjects.ingestManagerCreatePackagePolicy.selectEndpointAction('trustedApps'); - const backButton = await pageObjects.trustedApps.findTrustedAppsListPageBackButton(); - await backButton.click(); - await pageObjects.ingestManagerCreatePackagePolicy.ensureOnEditPageOrFail(); - }); - it('should show the endpoint policy form', async () => { await testSubjects.existOrFail('endpointIntegrationPolicyForm'); }); diff --git a/x-pack/test/security_solution_endpoint/page_objects/fleet_integrations_page.ts b/x-pack/test/security_solution_endpoint/page_objects/fleet_integrations_page.ts new file mode 100644 index 0000000000000..3c747afab48c8 --- /dev/null +++ b/x-pack/test/security_solution_endpoint/page_objects/fleet_integrations_page.ts @@ -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 { FtrProviderContext } from '../ftr_provider_context'; +import { PLUGIN_ID } from '../../../plugins/fleet/common'; + +// NOTE: import path below should be the deep path to the actual module - else we get CI errors +import { pagePathGetters } from '../../../plugins/fleet/public/applications/fleet/constants/page_paths'; + +export function FleetIntegrations({ getService, getPageObjects }: FtrProviderContext) { + const pageObjects = getPageObjects(['common']); + const testSubjects = getService('testSubjects'); + + return { + async navigateToIntegrationDetails(pkgkey: string) { + await pageObjects.common.navigateToApp(PLUGIN_ID, { + hash: pagePathGetters.integration_details({ pkgkey }), + }); + }, + + async integrationDetailCustomTabExistsOrFail() { + await testSubjects.existOrFail('tab-custom'); + }, + + async findIntegrationDetailCustomTab() { + return await testSubjects.find('tab-custom'); + }, + }; +} diff --git a/x-pack/test/security_solution_endpoint/page_objects/index.ts b/x-pack/test/security_solution_endpoint/page_objects/index.ts index 3664a2033d8b7..2fb441464e7ee 100644 --- a/x-pack/test/security_solution_endpoint/page_objects/index.ts +++ b/x-pack/test/security_solution_endpoint/page_objects/index.ts @@ -11,6 +11,7 @@ import { TrustedAppsPageProvider } from './trusted_apps_page'; import { EndpointPageUtils } from './page_utils'; import { IngestManagerCreatePackagePolicy } from './ingest_manager_create_package_policy_page'; import { SecurityHostsPageProvider } from './hosts_page'; +import { FleetIntegrations } from './fleet_integrations_page'; export const pageObjects = { ...xpackFunctionalPageObjects, @@ -20,4 +21,5 @@ export const pageObjects = { endpointPageUtils: EndpointPageUtils, ingestManagerCreatePackagePolicy: IngestManagerCreatePackagePolicy, hosts: SecurityHostsPageProvider, + fleetIntegrations: FleetIntegrations, }; diff --git a/x-pack/test/security_solution_endpoint/services/endpoint_policy.ts b/x-pack/test/security_solution_endpoint/services/endpoint_policy.ts index 1b1d0bf96a187..5f54ab2539c5d 100644 --- a/x-pack/test/security_solution_endpoint/services/endpoint_policy.ts +++ b/x-pack/test/security_solution_endpoint/services/endpoint_policy.ts @@ -19,6 +19,9 @@ import { import { factory as policyConfigFactory } from '../../../plugins/security_solution/common/endpoint/models/policy_config'; import { Immutable } from '../../../plugins/security_solution/common/endpoint/types'; +// NOTE: import path below should be the deep path to the actual module - else we get CI errors +import { pkgKeyFromPackageInfo } from '../../../plugins/fleet/public/applications/fleet/services/pkg_key_from_package_info'; + const INGEST_API_ROOT = '/api/fleet'; const INGEST_API_AGENT_POLICIES = `${INGEST_API_ROOT}/agent_policies`; const INGEST_API_AGENT_POLICIES_DELETE = `${INGEST_API_AGENT_POLICIES}/delete`; @@ -106,6 +109,14 @@ export function EndpointPolicyTestResourcesProvider({ getService }: FtrProviderC })(); return { + /** + * Returns the endpoint package key for the currently installed package. This `pkgkey` can then + * be used to build URLs for Fleet pages or APIs + */ + async getEndpointPkgKey() { + return pkgKeyFromPackageInfo((await retrieveEndpointPackageInfo())!); + }, + /** * Retrieves the full Agent policy, which mirrors what the Elastic Agent would get * once they checkin. From eee237baff9b07314ac0250b2ac7bd519ae40c46 Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Mon, 14 Dec 2020 16:10:56 -0600 Subject: [PATCH 50/95] skip 'alerts should delete all selection' #77401 --- .../apps/triggers_actions_ui/alerts_list.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts_list.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts_list.ts index f7281a1d93a46..16b338c893736 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts_list.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts_list.ts @@ -349,7 +349,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await deleteAlerts([createdAlert.id]); }); - it('should delete all selection', async () => { + it.skip('should delete all selection', async () => { const namePrefix = generateUniqueKey(); let count = 0; const createdAlertsFirstPage = await Promise.all( From dd34712ce6400aac4fb307a7b6152050e75230b8 Mon Sep 17 00:00:00 2001 From: Joe Portner <5295965+jportner@users.noreply.github.com> Date: Mon, 14 Dec 2020 17:12:05 -0500 Subject: [PATCH 51/95] Update core usage stats collection (#85706) --- .../core_usage_data_service.test.ts | 22 +- .../core_usage_data_service.ts | 7 +- .../core_usage_stats_client.mock.ts | 8 + .../core_usage_stats_client.test.ts | 757 +++++++++++++++++- .../core_usage_stats_client.ts | 176 ++-- src/core/server/core_usage_data/types.ts | 82 +- .../saved_objects/routes/bulk_create.ts | 11 +- .../server/saved_objects/routes/bulk_get.ts | 10 +- .../saved_objects/routes/bulk_update.ts | 10 +- .../server/saved_objects/routes/create.ts | 10 +- .../server/saved_objects/routes/delete.ts | 11 +- .../server/saved_objects/routes/export.ts | 3 +- src/core/server/saved_objects/routes/find.ts | 10 +- src/core/server/saved_objects/routes/get.ts | 11 +- .../server/saved_objects/routes/import.ts | 3 +- src/core/server/saved_objects/routes/index.ts | 16 +- .../integration_tests/bulk_create.test.ts | 14 +- .../routes/integration_tests/bulk_get.test.ts | 14 +- .../integration_tests/bulk_update.test.ts | 14 +- .../routes/integration_tests/create.test.ts | 14 +- .../routes/integration_tests/delete.test.ts | 14 +- .../routes/integration_tests/export.test.ts | 4 +- .../routes/integration_tests/find.test.ts | 14 +- .../routes/integration_tests/get.test.ts | 14 +- .../routes/integration_tests/import.test.ts | 4 +- .../resolve_import_errors.test.ts | 4 +- .../routes/integration_tests/update.test.ts | 14 +- .../routes/resolve_import_errors.ts | 3 +- .../server/saved_objects/routes/update.ts | 10 +- src/core/server/server.api.md | 148 +++- src/core/server/server.ts | 1 + .../collectors/core/core_usage_collector.ts | 90 ++- src/plugins/telemetry/schema/oss_plugins.json | 216 ++++- .../apis/saved_objects/bulk_create.js | 6 +- .../apis/saved_objects/bulk_update.js | 4 +- .../apis/saved_objects/create.js | 4 +- .../apis/saved_objects/update.js | 2 +- .../usage_stats/usage_stats_client.test.ts | 2 +- .../server/usage_stats/usage_stats_client.ts | 4 +- 39 files changed, 1590 insertions(+), 171 deletions(-) diff --git a/src/core/server/core_usage_data/core_usage_data_service.test.ts b/src/core/server/core_usage_data/core_usage_data_service.test.ts index e22dfcb1e3a20..737c851f03bc9 100644 --- a/src/core/server/core_usage_data/core_usage_data_service.test.ts +++ b/src/core/server/core_usage_data/core_usage_data_service.test.ts @@ -29,6 +29,7 @@ import { config as RawHttpConfig } from '../http/http_config'; import { config as RawLoggingConfig } from '../logging/logging_config'; import { config as RawKibanaConfig } from '../kibana_config'; import { savedObjectsConfig as RawSavedObjectsConfig } from '../saved_objects/saved_objects_config'; +import { httpServiceMock } from '../http/http_service.mock'; import { metricsServiceMock } from '../metrics/metrics_service.mock'; import { savedObjectsServiceMock } from '../saved_objects/saved_objects_service.mock'; @@ -68,11 +69,12 @@ describe('CoreUsageDataService', () => { describe('setup', () => { it('creates internal repository', async () => { + const http = httpServiceMock.createInternalSetupContract(); const metrics = metricsServiceMock.createInternalSetupContract(); const savedObjectsStartPromise = Promise.resolve( savedObjectsServiceMock.createStartContract() ); - service.setup({ metrics, savedObjectsStartPromise }); + service.setup({ http, metrics, savedObjectsStartPromise }); const savedObjects = await savedObjectsStartPromise; expect(savedObjects.createInternalRepository).toHaveBeenCalledTimes(1); @@ -81,14 +83,12 @@ describe('CoreUsageDataService', () => { describe('#registerType', () => { it('registers core usage stats type', async () => { + const http = httpServiceMock.createInternalSetupContract(); const metrics = metricsServiceMock.createInternalSetupContract(); const savedObjectsStartPromise = Promise.resolve( savedObjectsServiceMock.createStartContract() ); - const coreUsageData = service.setup({ - metrics, - savedObjectsStartPromise, - }); + const coreUsageData = service.setup({ http, metrics, savedObjectsStartPromise }); const typeRegistry = typeRegistryMock.create(); coreUsageData.registerType(typeRegistry); @@ -104,14 +104,12 @@ describe('CoreUsageDataService', () => { describe('#getClient', () => { it('returns client', async () => { + const http = httpServiceMock.createInternalSetupContract(); const metrics = metricsServiceMock.createInternalSetupContract(); const savedObjectsStartPromise = Promise.resolve( savedObjectsServiceMock.createStartContract() ); - const coreUsageData = service.setup({ - metrics, - savedObjectsStartPromise, - }); + const coreUsageData = service.setup({ http, metrics, savedObjectsStartPromise }); const usageStatsClient = coreUsageData.getClient(); expect(usageStatsClient).toBeInstanceOf(CoreUsageStatsClient); @@ -122,11 +120,12 @@ describe('CoreUsageDataService', () => { describe('start', () => { describe('getCoreUsageData', () => { it('returns core metrics for default config', async () => { + const http = httpServiceMock.createInternalSetupContract(); const metrics = metricsServiceMock.createInternalSetupContract(); const savedObjectsStartPromise = Promise.resolve( savedObjectsServiceMock.createStartContract() ); - service.setup({ metrics, savedObjectsStartPromise }); + service.setup({ http, metrics, savedObjectsStartPromise }); const elasticsearch = elasticsearchServiceMock.createStart(); elasticsearch.client.asInternalUser.cat.indices.mockResolvedValueOnce({ body: [ @@ -296,6 +295,7 @@ describe('CoreUsageDataService', () => { observables.push(newObservable); return newObservable; }); + const http = httpServiceMock.createInternalSetupContract(); const metrics = metricsServiceMock.createInternalSetupContract(); metrics.getOpsMetrics$.mockImplementation(() => { const newObservable = hot('-a-------'); @@ -306,7 +306,7 @@ describe('CoreUsageDataService', () => { savedObjectsServiceMock.createStartContract() ); - service.setup({ metrics, savedObjectsStartPromise }); + service.setup({ http, metrics, savedObjectsStartPromise }); // Use the stopTimer$ to delay calling stop() until the third frame const stopTimer$ = cold('---a|'); diff --git a/src/core/server/core_usage_data/core_usage_data_service.ts b/src/core/server/core_usage_data/core_usage_data_service.ts index 02b4f2ac59133..07c583186b453 100644 --- a/src/core/server/core_usage_data/core_usage_data_service.ts +++ b/src/core/server/core_usage_data/core_usage_data_service.ts @@ -24,7 +24,7 @@ import { CoreService } from 'src/core/types'; import { Logger, SavedObjectsServiceStart, SavedObjectTypeRegistry } from 'src/core/server'; import { CoreContext } from '../core_context'; import { ElasticsearchConfigType } from '../elasticsearch/elasticsearch_config'; -import { HttpConfigType } from '../http'; +import { HttpConfigType, InternalHttpServiceSetup } from '../http'; import { LoggingConfigType } from '../logging'; import { SavedObjectsConfigType } from '../saved_objects/saved_objects_config'; import { @@ -42,6 +42,7 @@ import { CoreUsageStatsClient } from './core_usage_stats_client'; import { MetricsServiceSetup, OpsMetrics } from '..'; export interface SetupDeps { + http: InternalHttpServiceSetup; metrics: MetricsServiceSetup; savedObjectsStartPromise: Promise; } @@ -248,7 +249,7 @@ export class CoreUsageDataService implements CoreService { const debugLogger = (message: string) => this.logger.debug(message); - return new CoreUsageStatsClient(debugLogger, internalRepositoryPromise); + return new CoreUsageStatsClient(debugLogger, http.basePath, internalRepositoryPromise); }; this.coreUsageStatsClient = getClient(); diff --git a/src/core/server/core_usage_data/core_usage_stats_client.mock.ts b/src/core/server/core_usage_data/core_usage_stats_client.mock.ts index 3bfb411c9dd49..ef350a9bb4c5c 100644 --- a/src/core/server/core_usage_data/core_usage_stats_client.mock.ts +++ b/src/core/server/core_usage_data/core_usage_stats_client.mock.ts @@ -22,6 +22,14 @@ import { CoreUsageStatsClient } from '.'; const createUsageStatsClientMock = () => (({ getUsageStats: jest.fn().mockResolvedValue({}), + incrementSavedObjectsBulkCreate: jest.fn().mockResolvedValue(null), + incrementSavedObjectsBulkGet: jest.fn().mockResolvedValue(null), + incrementSavedObjectsBulkUpdate: jest.fn().mockResolvedValue(null), + incrementSavedObjectsCreate: jest.fn().mockResolvedValue(null), + incrementSavedObjectsDelete: jest.fn().mockResolvedValue(null), + incrementSavedObjectsFind: jest.fn().mockResolvedValue(null), + incrementSavedObjectsGet: jest.fn().mockResolvedValue(null), + incrementSavedObjectsUpdate: jest.fn().mockResolvedValue(null), incrementSavedObjectsImport: jest.fn().mockResolvedValue(null), incrementSavedObjectsResolveImportErrors: jest.fn().mockResolvedValue(null), incrementSavedObjectsExport: jest.fn().mockResolvedValue(null), diff --git a/src/core/server/core_usage_data/core_usage_stats_client.test.ts b/src/core/server/core_usage_data/core_usage_stats_client.test.ts index e4f47667fce6b..6b6e83f168f77 100644 --- a/src/core/server/core_usage_data/core_usage_stats_client.test.ts +++ b/src/core/server/core_usage_data/core_usage_stats_client.test.ts @@ -17,30 +17,43 @@ * under the License. */ -import { savedObjectsRepositoryMock } from '../mocks'; +import { httpServerMock, httpServiceMock, savedObjectsRepositoryMock } from '../mocks'; import { CORE_USAGE_STATS_TYPE, CORE_USAGE_STATS_ID } from './constants'; import { + BaseIncrementOptions, IncrementSavedObjectsImportOptions, IncrementSavedObjectsResolveImportErrorsOptions, IncrementSavedObjectsExportOptions, + BULK_CREATE_STATS_PREFIX, + BULK_GET_STATS_PREFIX, + BULK_UPDATE_STATS_PREFIX, + CREATE_STATS_PREFIX, + DELETE_STATS_PREFIX, + FIND_STATS_PREFIX, + GET_STATS_PREFIX, + UPDATE_STATS_PREFIX, IMPORT_STATS_PREFIX, RESOLVE_IMPORT_STATS_PREFIX, EXPORT_STATS_PREFIX, } from './core_usage_stats_client'; import { CoreUsageStatsClient } from '.'; +import { DEFAULT_NAMESPACE_STRING } from '../saved_objects/service/lib/utils'; describe('CoreUsageStatsClient', () => { - const setup = () => { + const setup = (namespace?: string) => { const debugLoggerMock = jest.fn(); + const basePathMock = httpServiceMock.createBasePath(); + // we could mock a return value for basePathMock.get, but it isn't necessary for testing purposes + basePathMock.remove.mockReturnValue(namespace ? `/s/${namespace}` : '/'); const repositoryMock = savedObjectsRepositoryMock.create(); const usageStatsClient = new CoreUsageStatsClient( debugLoggerMock, + basePathMock, Promise.resolve(repositoryMock) ); - return { usageStatsClient, debugLoggerMock, repositoryMock }; + return { usageStatsClient, debugLoggerMock, basePathMock, repositoryMock }; }; - - const firstPartyRequestHeaders = { 'kbn-version': 'a', origin: 'b', referer: 'c' }; // as long as these three header fields are truthy, this will be treated like a first-party request + const firstPartyRequestHeaders = { 'kbn-version': 'a', referer: 'b' }; // as long as these two header fields are truthy, this will be treated like a first-party request const incrementOptions = { refresh: false }; describe('#getUsageStats', () => { @@ -67,13 +80,616 @@ describe('CoreUsageStatsClient', () => { }); }); + describe('#incrementSavedObjectsBulkCreate', () => { + it('does not throw an error if repository incrementCounter operation fails', async () => { + const { usageStatsClient, repositoryMock } = setup(); + repositoryMock.incrementCounter.mockRejectedValue(new Error('Oh no!')); + + const request = httpServerMock.createKibanaRequest(); + await expect( + usageStatsClient.incrementSavedObjectsBulkCreate({ + request, + } as BaseIncrementOptions) + ).resolves.toBeUndefined(); + expect(repositoryMock.incrementCounter).toHaveBeenCalled(); + }); + + it('handles falsy options appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup(); + + const request = httpServerMock.createKibanaRequest(); + await usageStatsClient.incrementSavedObjectsBulkCreate({ + request, + } as BaseIncrementOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${BULK_CREATE_STATS_PREFIX}.total`, + `${BULK_CREATE_STATS_PREFIX}.namespace.default.total`, + `${BULK_CREATE_STATS_PREFIX}.namespace.default.kibanaRequest.no`, + ], + incrementOptions + ); + }); + + it('handles truthy options and the default namespace string appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup(DEFAULT_NAMESPACE_STRING); + + const request = httpServerMock.createKibanaRequest({ headers: firstPartyRequestHeaders }); + await usageStatsClient.incrementSavedObjectsBulkCreate({ + request, + } as BaseIncrementOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${BULK_CREATE_STATS_PREFIX}.total`, + `${BULK_CREATE_STATS_PREFIX}.namespace.default.total`, + `${BULK_CREATE_STATS_PREFIX}.namespace.default.kibanaRequest.yes`, + ], + incrementOptions + ); + }); + + it('handles a non-default space appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup('foo'); + + const request = httpServerMock.createKibanaRequest(); + await usageStatsClient.incrementSavedObjectsBulkCreate({ + request, + } as BaseIncrementOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${BULK_CREATE_STATS_PREFIX}.total`, + `${BULK_CREATE_STATS_PREFIX}.namespace.custom.total`, + `${BULK_CREATE_STATS_PREFIX}.namespace.custom.kibanaRequest.no`, + ], + incrementOptions + ); + }); + }); + + describe('#incrementSavedObjectsBulkGet', () => { + it('does not throw an error if repository incrementCounter operation fails', async () => { + const { usageStatsClient, repositoryMock } = setup(); + repositoryMock.incrementCounter.mockRejectedValue(new Error('Oh no!')); + + const request = httpServerMock.createKibanaRequest(); + await expect( + usageStatsClient.incrementSavedObjectsBulkGet({ + request, + } as BaseIncrementOptions) + ).resolves.toBeUndefined(); + expect(repositoryMock.incrementCounter).toHaveBeenCalled(); + }); + + it('handles falsy options appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup(); + + const request = httpServerMock.createKibanaRequest(); + await usageStatsClient.incrementSavedObjectsBulkGet({ + request, + } as BaseIncrementOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${BULK_GET_STATS_PREFIX}.total`, + `${BULK_GET_STATS_PREFIX}.namespace.default.total`, + `${BULK_GET_STATS_PREFIX}.namespace.default.kibanaRequest.no`, + ], + incrementOptions + ); + }); + + it('handles truthy options and the default namespace string appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup(DEFAULT_NAMESPACE_STRING); + + const request = httpServerMock.createKibanaRequest({ headers: firstPartyRequestHeaders }); + await usageStatsClient.incrementSavedObjectsBulkGet({ + request, + } as BaseIncrementOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${BULK_GET_STATS_PREFIX}.total`, + `${BULK_GET_STATS_PREFIX}.namespace.default.total`, + `${BULK_GET_STATS_PREFIX}.namespace.default.kibanaRequest.yes`, + ], + incrementOptions + ); + }); + + it('handles a non-default space appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup('foo'); + + const request = httpServerMock.createKibanaRequest(); + await usageStatsClient.incrementSavedObjectsBulkGet({ + request, + } as BaseIncrementOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${BULK_GET_STATS_PREFIX}.total`, + `${BULK_GET_STATS_PREFIX}.namespace.custom.total`, + `${BULK_GET_STATS_PREFIX}.namespace.custom.kibanaRequest.no`, + ], + incrementOptions + ); + }); + }); + + describe('#incrementSavedObjectsBulkUpdate', () => { + it('does not throw an error if repository incrementCounter operation fails', async () => { + const { usageStatsClient, repositoryMock } = setup(); + repositoryMock.incrementCounter.mockRejectedValue(new Error('Oh no!')); + + const request = httpServerMock.createKibanaRequest(); + await expect( + usageStatsClient.incrementSavedObjectsBulkUpdate({ + request, + } as BaseIncrementOptions) + ).resolves.toBeUndefined(); + expect(repositoryMock.incrementCounter).toHaveBeenCalled(); + }); + + it('handles falsy options appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup(); + + const request = httpServerMock.createKibanaRequest(); + await usageStatsClient.incrementSavedObjectsBulkUpdate({ + request, + } as BaseIncrementOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${BULK_UPDATE_STATS_PREFIX}.total`, + `${BULK_UPDATE_STATS_PREFIX}.namespace.default.total`, + `${BULK_UPDATE_STATS_PREFIX}.namespace.default.kibanaRequest.no`, + ], + incrementOptions + ); + }); + + it('handles truthy options and the default namespace string appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup(DEFAULT_NAMESPACE_STRING); + + const request = httpServerMock.createKibanaRequest({ headers: firstPartyRequestHeaders }); + await usageStatsClient.incrementSavedObjectsBulkUpdate({ + request, + } as BaseIncrementOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${BULK_UPDATE_STATS_PREFIX}.total`, + `${BULK_UPDATE_STATS_PREFIX}.namespace.default.total`, + `${BULK_UPDATE_STATS_PREFIX}.namespace.default.kibanaRequest.yes`, + ], + incrementOptions + ); + }); + + it('handles a non-default space appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup('foo'); + + const request = httpServerMock.createKibanaRequest(); + await usageStatsClient.incrementSavedObjectsBulkUpdate({ + request, + } as BaseIncrementOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${BULK_UPDATE_STATS_PREFIX}.total`, + `${BULK_UPDATE_STATS_PREFIX}.namespace.custom.total`, + `${BULK_UPDATE_STATS_PREFIX}.namespace.custom.kibanaRequest.no`, + ], + incrementOptions + ); + }); + }); + + describe('#incrementSavedObjectsCreate', () => { + it('does not throw an error if repository incrementCounter operation fails', async () => { + const { usageStatsClient, repositoryMock } = setup(); + repositoryMock.incrementCounter.mockRejectedValue(new Error('Oh no!')); + + const request = httpServerMock.createKibanaRequest(); + await expect( + usageStatsClient.incrementSavedObjectsCreate({ + request, + } as BaseIncrementOptions) + ).resolves.toBeUndefined(); + expect(repositoryMock.incrementCounter).toHaveBeenCalled(); + }); + + it('handles falsy options appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup(); + + const request = httpServerMock.createKibanaRequest(); + await usageStatsClient.incrementSavedObjectsCreate({ + request, + } as BaseIncrementOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${CREATE_STATS_PREFIX}.total`, + `${CREATE_STATS_PREFIX}.namespace.default.total`, + `${CREATE_STATS_PREFIX}.namespace.default.kibanaRequest.no`, + ], + incrementOptions + ); + }); + + it('handles truthy options and the default namespace string appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup(DEFAULT_NAMESPACE_STRING); + + const request = httpServerMock.createKibanaRequest({ headers: firstPartyRequestHeaders }); + await usageStatsClient.incrementSavedObjectsCreate({ + request, + } as BaseIncrementOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${CREATE_STATS_PREFIX}.total`, + `${CREATE_STATS_PREFIX}.namespace.default.total`, + `${CREATE_STATS_PREFIX}.namespace.default.kibanaRequest.yes`, + ], + incrementOptions + ); + }); + + it('handles a non-default space appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup('foo'); + + const request = httpServerMock.createKibanaRequest(); + await usageStatsClient.incrementSavedObjectsCreate({ + request, + } as BaseIncrementOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${CREATE_STATS_PREFIX}.total`, + `${CREATE_STATS_PREFIX}.namespace.custom.total`, + `${CREATE_STATS_PREFIX}.namespace.custom.kibanaRequest.no`, + ], + incrementOptions + ); + }); + }); + + describe('#incrementSavedObjectsDelete', () => { + it('does not throw an error if repository incrementCounter operation fails', async () => { + const { usageStatsClient, repositoryMock } = setup(); + repositoryMock.incrementCounter.mockRejectedValue(new Error('Oh no!')); + + const request = httpServerMock.createKibanaRequest(); + await expect( + usageStatsClient.incrementSavedObjectsDelete({ + request, + } as BaseIncrementOptions) + ).resolves.toBeUndefined(); + expect(repositoryMock.incrementCounter).toHaveBeenCalled(); + }); + + it('handles falsy options appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup(); + + const request = httpServerMock.createKibanaRequest(); + await usageStatsClient.incrementSavedObjectsDelete({ + request, + } as BaseIncrementOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${DELETE_STATS_PREFIX}.total`, + `${DELETE_STATS_PREFIX}.namespace.default.total`, + `${DELETE_STATS_PREFIX}.namespace.default.kibanaRequest.no`, + ], + incrementOptions + ); + }); + + it('handles truthy options and the default namespace string appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup(DEFAULT_NAMESPACE_STRING); + + const request = httpServerMock.createKibanaRequest({ headers: firstPartyRequestHeaders }); + await usageStatsClient.incrementSavedObjectsDelete({ + request, + } as BaseIncrementOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${DELETE_STATS_PREFIX}.total`, + `${DELETE_STATS_PREFIX}.namespace.default.total`, + `${DELETE_STATS_PREFIX}.namespace.default.kibanaRequest.yes`, + ], + incrementOptions + ); + }); + + it('handles a non-default space appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup('foo'); + + const request = httpServerMock.createKibanaRequest(); + await usageStatsClient.incrementSavedObjectsDelete({ + request, + } as BaseIncrementOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${DELETE_STATS_PREFIX}.total`, + `${DELETE_STATS_PREFIX}.namespace.custom.total`, + `${DELETE_STATS_PREFIX}.namespace.custom.kibanaRequest.no`, + ], + incrementOptions + ); + }); + }); + + describe('#incrementSavedObjectsFind', () => { + it('does not throw an error if repository incrementCounter operation fails', async () => { + const { usageStatsClient, repositoryMock } = setup(); + repositoryMock.incrementCounter.mockRejectedValue(new Error('Oh no!')); + + const request = httpServerMock.createKibanaRequest(); + await expect( + usageStatsClient.incrementSavedObjectsFind({ + request, + } as BaseIncrementOptions) + ).resolves.toBeUndefined(); + expect(repositoryMock.incrementCounter).toHaveBeenCalled(); + }); + + it('handles falsy options appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup(); + + const request = httpServerMock.createKibanaRequest(); + await usageStatsClient.incrementSavedObjectsFind({ + request, + } as BaseIncrementOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${FIND_STATS_PREFIX}.total`, + `${FIND_STATS_PREFIX}.namespace.default.total`, + `${FIND_STATS_PREFIX}.namespace.default.kibanaRequest.no`, + ], + incrementOptions + ); + }); + + it('handles truthy options and the default namespace string appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup(DEFAULT_NAMESPACE_STRING); + + const request = httpServerMock.createKibanaRequest({ headers: firstPartyRequestHeaders }); + await usageStatsClient.incrementSavedObjectsFind({ + request, + } as BaseIncrementOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${FIND_STATS_PREFIX}.total`, + `${FIND_STATS_PREFIX}.namespace.default.total`, + `${FIND_STATS_PREFIX}.namespace.default.kibanaRequest.yes`, + ], + incrementOptions + ); + }); + + it('handles a non-default space appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup('foo'); + + const request = httpServerMock.createKibanaRequest(); + await usageStatsClient.incrementSavedObjectsFind({ + request, + } as BaseIncrementOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${FIND_STATS_PREFIX}.total`, + `${FIND_STATS_PREFIX}.namespace.custom.total`, + `${FIND_STATS_PREFIX}.namespace.custom.kibanaRequest.no`, + ], + incrementOptions + ); + }); + }); + + describe('#incrementSavedObjectsGet', () => { + it('does not throw an error if repository incrementCounter operation fails', async () => { + const { usageStatsClient, repositoryMock } = setup(); + repositoryMock.incrementCounter.mockRejectedValue(new Error('Oh no!')); + + const request = httpServerMock.createKibanaRequest(); + await expect( + usageStatsClient.incrementSavedObjectsGet({ + request, + } as BaseIncrementOptions) + ).resolves.toBeUndefined(); + expect(repositoryMock.incrementCounter).toHaveBeenCalled(); + }); + + it('handles falsy options appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup(); + + const request = httpServerMock.createKibanaRequest(); + await usageStatsClient.incrementSavedObjectsGet({ + request, + } as BaseIncrementOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${GET_STATS_PREFIX}.total`, + `${GET_STATS_PREFIX}.namespace.default.total`, + `${GET_STATS_PREFIX}.namespace.default.kibanaRequest.no`, + ], + incrementOptions + ); + }); + + it('handles truthy options and the default namespace string appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup(DEFAULT_NAMESPACE_STRING); + + const request = httpServerMock.createKibanaRequest({ headers: firstPartyRequestHeaders }); + await usageStatsClient.incrementSavedObjectsGet({ + request, + } as BaseIncrementOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${GET_STATS_PREFIX}.total`, + `${GET_STATS_PREFIX}.namespace.default.total`, + `${GET_STATS_PREFIX}.namespace.default.kibanaRequest.yes`, + ], + incrementOptions + ); + }); + + it('handles a non-default space appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup('foo'); + + const request = httpServerMock.createKibanaRequest(); + await usageStatsClient.incrementSavedObjectsGet({ + request, + } as BaseIncrementOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${GET_STATS_PREFIX}.total`, + `${GET_STATS_PREFIX}.namespace.custom.total`, + `${GET_STATS_PREFIX}.namespace.custom.kibanaRequest.no`, + ], + incrementOptions + ); + }); + }); + + describe('#incrementSavedObjectsUpdate', () => { + it('does not throw an error if repository incrementCounter operation fails', async () => { + const { usageStatsClient, repositoryMock } = setup(); + repositoryMock.incrementCounter.mockRejectedValue(new Error('Oh no!')); + + const request = httpServerMock.createKibanaRequest(); + await expect( + usageStatsClient.incrementSavedObjectsUpdate({ + request, + } as BaseIncrementOptions) + ).resolves.toBeUndefined(); + expect(repositoryMock.incrementCounter).toHaveBeenCalled(); + }); + + it('handles falsy options appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup(); + + const request = httpServerMock.createKibanaRequest(); + await usageStatsClient.incrementSavedObjectsUpdate({ + request, + } as BaseIncrementOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${UPDATE_STATS_PREFIX}.total`, + `${UPDATE_STATS_PREFIX}.namespace.default.total`, + `${UPDATE_STATS_PREFIX}.namespace.default.kibanaRequest.no`, + ], + incrementOptions + ); + }); + + it('handles truthy options and the default namespace string appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup(DEFAULT_NAMESPACE_STRING); + + const request = httpServerMock.createKibanaRequest({ headers: firstPartyRequestHeaders }); + await usageStatsClient.incrementSavedObjectsUpdate({ + request, + } as BaseIncrementOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${UPDATE_STATS_PREFIX}.total`, + `${UPDATE_STATS_PREFIX}.namespace.default.total`, + `${UPDATE_STATS_PREFIX}.namespace.default.kibanaRequest.yes`, + ], + incrementOptions + ); + }); + + it('handles a non-default space appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup('foo'); + + const request = httpServerMock.createKibanaRequest(); + await usageStatsClient.incrementSavedObjectsUpdate({ + request, + } as BaseIncrementOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${UPDATE_STATS_PREFIX}.total`, + `${UPDATE_STATS_PREFIX}.namespace.custom.total`, + `${UPDATE_STATS_PREFIX}.namespace.custom.kibanaRequest.no`, + ], + incrementOptions + ); + }); + }); + describe('#incrementSavedObjectsImport', () => { it('does not throw an error if repository incrementCounter operation fails', async () => { const { usageStatsClient, repositoryMock } = setup(); repositoryMock.incrementCounter.mockRejectedValue(new Error('Oh no!')); + const request = httpServerMock.createKibanaRequest(); await expect( - usageStatsClient.incrementSavedObjectsImport({} as IncrementSavedObjectsImportOptions) + usageStatsClient.incrementSavedObjectsImport({ + request, + } as IncrementSavedObjectsImportOptions) ).resolves.toBeUndefined(); expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); }); @@ -81,14 +697,18 @@ describe('CoreUsageStatsClient', () => { it('handles falsy options appropriately', async () => { const { usageStatsClient, repositoryMock } = setup(); - await usageStatsClient.incrementSavedObjectsImport({} as IncrementSavedObjectsImportOptions); + const request = httpServerMock.createKibanaRequest(); + await usageStatsClient.incrementSavedObjectsImport({ + request, + } as IncrementSavedObjectsImportOptions); expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( CORE_USAGE_STATS_TYPE, CORE_USAGE_STATS_ID, [ `${IMPORT_STATS_PREFIX}.total`, - `${IMPORT_STATS_PREFIX}.kibanaRequest.no`, + `${IMPORT_STATS_PREFIX}.namespace.default.total`, + `${IMPORT_STATS_PREFIX}.namespace.default.kibanaRequest.no`, `${IMPORT_STATS_PREFIX}.createNewCopiesEnabled.no`, `${IMPORT_STATS_PREFIX}.overwriteEnabled.no`, ], @@ -96,11 +716,12 @@ describe('CoreUsageStatsClient', () => { ); }); - it('handles truthy options appropriately', async () => { - const { usageStatsClient, repositoryMock } = setup(); + it('handles truthy options and the default namespace string appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup(DEFAULT_NAMESPACE_STRING); + const request = httpServerMock.createKibanaRequest({ headers: firstPartyRequestHeaders }); await usageStatsClient.incrementSavedObjectsImport({ - headers: firstPartyRequestHeaders, + request, createNewCopies: true, overwrite: true, } as IncrementSavedObjectsImportOptions); @@ -110,13 +731,36 @@ describe('CoreUsageStatsClient', () => { CORE_USAGE_STATS_ID, [ `${IMPORT_STATS_PREFIX}.total`, - `${IMPORT_STATS_PREFIX}.kibanaRequest.yes`, + `${IMPORT_STATS_PREFIX}.namespace.default.total`, + `${IMPORT_STATS_PREFIX}.namespace.default.kibanaRequest.yes`, `${IMPORT_STATS_PREFIX}.createNewCopiesEnabled.yes`, `${IMPORT_STATS_PREFIX}.overwriteEnabled.yes`, ], incrementOptions ); }); + + it('handles a non-default space appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup('foo'); + + const request = httpServerMock.createKibanaRequest(); + await usageStatsClient.incrementSavedObjectsImport({ + request, + } as IncrementSavedObjectsImportOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${IMPORT_STATS_PREFIX}.total`, + `${IMPORT_STATS_PREFIX}.namespace.custom.total`, + `${IMPORT_STATS_PREFIX}.namespace.custom.kibanaRequest.no`, + `${IMPORT_STATS_PREFIX}.createNewCopiesEnabled.no`, + `${IMPORT_STATS_PREFIX}.overwriteEnabled.no`, + ], + incrementOptions + ); + }); }); describe('#incrementSavedObjectsResolveImportErrors', () => { @@ -124,10 +768,11 @@ describe('CoreUsageStatsClient', () => { const { usageStatsClient, repositoryMock } = setup(); repositoryMock.incrementCounter.mockRejectedValue(new Error('Oh no!')); + const request = httpServerMock.createKibanaRequest(); await expect( - usageStatsClient.incrementSavedObjectsResolveImportErrors( - {} as IncrementSavedObjectsResolveImportErrorsOptions - ) + usageStatsClient.incrementSavedObjectsResolveImportErrors({ + request, + } as IncrementSavedObjectsResolveImportErrorsOptions) ).resolves.toBeUndefined(); expect(repositoryMock.incrementCounter).toHaveBeenCalled(); }); @@ -135,27 +780,30 @@ describe('CoreUsageStatsClient', () => { it('handles falsy options appropriately', async () => { const { usageStatsClient, repositoryMock } = setup(); - await usageStatsClient.incrementSavedObjectsResolveImportErrors( - {} as IncrementSavedObjectsResolveImportErrorsOptions - ); + const request = httpServerMock.createKibanaRequest(); + await usageStatsClient.incrementSavedObjectsResolveImportErrors({ + request, + } as IncrementSavedObjectsResolveImportErrorsOptions); expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( CORE_USAGE_STATS_TYPE, CORE_USAGE_STATS_ID, [ `${RESOLVE_IMPORT_STATS_PREFIX}.total`, - `${RESOLVE_IMPORT_STATS_PREFIX}.kibanaRequest.no`, + `${RESOLVE_IMPORT_STATS_PREFIX}.namespace.default.total`, + `${RESOLVE_IMPORT_STATS_PREFIX}.namespace.default.kibanaRequest.no`, `${RESOLVE_IMPORT_STATS_PREFIX}.createNewCopiesEnabled.no`, ], incrementOptions ); }); - it('handles truthy options appropriately', async () => { - const { usageStatsClient, repositoryMock } = setup(); + it('handles truthy options and the default namespace string appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup(DEFAULT_NAMESPACE_STRING); + const request = httpServerMock.createKibanaRequest({ headers: firstPartyRequestHeaders }); await usageStatsClient.incrementSavedObjectsResolveImportErrors({ - headers: firstPartyRequestHeaders, + request, createNewCopies: true, } as IncrementSavedObjectsResolveImportErrorsOptions); expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); @@ -164,12 +812,34 @@ describe('CoreUsageStatsClient', () => { CORE_USAGE_STATS_ID, [ `${RESOLVE_IMPORT_STATS_PREFIX}.total`, - `${RESOLVE_IMPORT_STATS_PREFIX}.kibanaRequest.yes`, + `${RESOLVE_IMPORT_STATS_PREFIX}.namespace.default.total`, + `${RESOLVE_IMPORT_STATS_PREFIX}.namespace.default.kibanaRequest.yes`, `${RESOLVE_IMPORT_STATS_PREFIX}.createNewCopiesEnabled.yes`, ], incrementOptions ); }); + + it('handles a non-default space appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup('foo'); + + const request = httpServerMock.createKibanaRequest(); + await usageStatsClient.incrementSavedObjectsResolveImportErrors({ + request, + } as IncrementSavedObjectsResolveImportErrorsOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${RESOLVE_IMPORT_STATS_PREFIX}.total`, + `${RESOLVE_IMPORT_STATS_PREFIX}.namespace.custom.total`, + `${RESOLVE_IMPORT_STATS_PREFIX}.namespace.custom.kibanaRequest.no`, + `${RESOLVE_IMPORT_STATS_PREFIX}.createNewCopiesEnabled.no`, + ], + incrementOptions + ); + }); }); describe('#incrementSavedObjectsExport', () => { @@ -177,8 +847,11 @@ describe('CoreUsageStatsClient', () => { const { usageStatsClient, repositoryMock } = setup(); repositoryMock.incrementCounter.mockRejectedValue(new Error('Oh no!')); + const request = httpServerMock.createKibanaRequest(); await expect( - usageStatsClient.incrementSavedObjectsExport({} as IncrementSavedObjectsExportOptions) + usageStatsClient.incrementSavedObjectsExport({ + request, + } as IncrementSavedObjectsExportOptions) ).resolves.toBeUndefined(); expect(repositoryMock.incrementCounter).toHaveBeenCalled(); }); @@ -186,7 +859,9 @@ describe('CoreUsageStatsClient', () => { it('handles falsy options appropriately', async () => { const { usageStatsClient, repositoryMock } = setup(); + const request = httpServerMock.createKibanaRequest(); await usageStatsClient.incrementSavedObjectsExport({ + request, types: undefined, supportedTypes: ['foo', 'bar'], } as IncrementSavedObjectsExportOptions); @@ -196,18 +871,20 @@ describe('CoreUsageStatsClient', () => { CORE_USAGE_STATS_ID, [ `${EXPORT_STATS_PREFIX}.total`, - `${EXPORT_STATS_PREFIX}.kibanaRequest.no`, + `${EXPORT_STATS_PREFIX}.namespace.default.total`, + `${EXPORT_STATS_PREFIX}.namespace.default.kibanaRequest.no`, `${EXPORT_STATS_PREFIX}.allTypesSelected.no`, ], incrementOptions ); }); - it('handles truthy options appropriately', async () => { - const { usageStatsClient, repositoryMock } = setup(); + it('handles truthy options and the default namespace string appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup(DEFAULT_NAMESPACE_STRING); + const request = httpServerMock.createKibanaRequest({ headers: firstPartyRequestHeaders }); await usageStatsClient.incrementSavedObjectsExport({ - headers: firstPartyRequestHeaders, + request, types: ['foo', 'bar'], supportedTypes: ['foo', 'bar'], } as IncrementSavedObjectsExportOptions); @@ -217,11 +894,33 @@ describe('CoreUsageStatsClient', () => { CORE_USAGE_STATS_ID, [ `${EXPORT_STATS_PREFIX}.total`, - `${EXPORT_STATS_PREFIX}.kibanaRequest.yes`, + `${EXPORT_STATS_PREFIX}.namespace.default.total`, + `${EXPORT_STATS_PREFIX}.namespace.default.kibanaRequest.yes`, `${EXPORT_STATS_PREFIX}.allTypesSelected.yes`, ], incrementOptions ); }); + + it('handles a non-default space appropriately', async () => { + const { usageStatsClient, repositoryMock } = setup('foo'); + + const request = httpServerMock.createKibanaRequest(); + await usageStatsClient.incrementSavedObjectsExport({ + request, + } as IncrementSavedObjectsExportOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); + expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${EXPORT_STATS_PREFIX}.total`, + `${EXPORT_STATS_PREFIX}.namespace.custom.total`, + `${EXPORT_STATS_PREFIX}.namespace.custom.kibanaRequest.no`, + `${EXPORT_STATS_PREFIX}.allTypesSelected.no`, + ], + incrementOptions + ); + }); }); }); diff --git a/src/core/server/core_usage_data/core_usage_stats_client.ts b/src/core/server/core_usage_data/core_usage_stats_client.ts index 58356832d8b8a..c8d48597fae88 100644 --- a/src/core/server/core_usage_data/core_usage_stats_client.ts +++ b/src/core/server/core_usage_data/core_usage_stats_client.ts @@ -19,16 +19,19 @@ import { CORE_USAGE_STATS_TYPE, CORE_USAGE_STATS_ID } from './constants'; import { CoreUsageStats } from './types'; +import { DEFAULT_NAMESPACE_STRING } from '../saved_objects/service/lib/utils'; import { - Headers, ISavedObjectsRepository, SavedObjectsImportOptions, SavedObjectsResolveImportErrorsOptions, SavedObjectsExportOptions, + KibanaRequest, + IBasePath, } from '..'; -interface BaseIncrementOptions { - headers?: Headers; +/** @internal */ +export interface BaseIncrementOptions { + request: KibanaRequest; } /** @internal */ export type IncrementSavedObjectsImportOptions = BaseIncrementOptions & @@ -40,33 +43,47 @@ export type IncrementSavedObjectsResolveImportErrorsOptions = BaseIncrementOptio export type IncrementSavedObjectsExportOptions = BaseIncrementOptions & Pick & { supportedTypes: string[] }; +export const BULK_CREATE_STATS_PREFIX = 'apiCalls.savedObjectsBulkCreate'; +export const BULK_GET_STATS_PREFIX = 'apiCalls.savedObjectsBulkGet'; +export const BULK_UPDATE_STATS_PREFIX = 'apiCalls.savedObjectsBulkUpdate'; +export const CREATE_STATS_PREFIX = 'apiCalls.savedObjectsCreate'; +export const DELETE_STATS_PREFIX = 'apiCalls.savedObjectsDelete'; +export const FIND_STATS_PREFIX = 'apiCalls.savedObjectsFind'; +export const GET_STATS_PREFIX = 'apiCalls.savedObjectsGet'; +export const UPDATE_STATS_PREFIX = 'apiCalls.savedObjectsUpdate'; export const IMPORT_STATS_PREFIX = 'apiCalls.savedObjectsImport'; export const RESOLVE_IMPORT_STATS_PREFIX = 'apiCalls.savedObjectsResolveImportErrors'; export const EXPORT_STATS_PREFIX = 'apiCalls.savedObjectsExport'; const ALL_COUNTER_FIELDS = [ - `${IMPORT_STATS_PREFIX}.total`, - `${IMPORT_STATS_PREFIX}.kibanaRequest.yes`, - `${IMPORT_STATS_PREFIX}.kibanaRequest.no`, + // Saved Objects Client APIs + ...getFieldsForCounter(BULK_CREATE_STATS_PREFIX), + ...getFieldsForCounter(BULK_GET_STATS_PREFIX), + ...getFieldsForCounter(BULK_UPDATE_STATS_PREFIX), + ...getFieldsForCounter(CREATE_STATS_PREFIX), + ...getFieldsForCounter(DELETE_STATS_PREFIX), + ...getFieldsForCounter(FIND_STATS_PREFIX), + ...getFieldsForCounter(GET_STATS_PREFIX), + ...getFieldsForCounter(UPDATE_STATS_PREFIX), + // Saved Objects Management APIs + ...getFieldsForCounter(IMPORT_STATS_PREFIX), `${IMPORT_STATS_PREFIX}.createNewCopiesEnabled.yes`, `${IMPORT_STATS_PREFIX}.createNewCopiesEnabled.no`, `${IMPORT_STATS_PREFIX}.overwriteEnabled.yes`, `${IMPORT_STATS_PREFIX}.overwriteEnabled.no`, - `${RESOLVE_IMPORT_STATS_PREFIX}.total`, - `${RESOLVE_IMPORT_STATS_PREFIX}.kibanaRequest.yes`, - `${RESOLVE_IMPORT_STATS_PREFIX}.kibanaRequest.no`, + ...getFieldsForCounter(RESOLVE_IMPORT_STATS_PREFIX), `${RESOLVE_IMPORT_STATS_PREFIX}.createNewCopiesEnabled.yes`, `${RESOLVE_IMPORT_STATS_PREFIX}.createNewCopiesEnabled.no`, - `${EXPORT_STATS_PREFIX}.total`, - `${EXPORT_STATS_PREFIX}.kibanaRequest.yes`, - `${EXPORT_STATS_PREFIX}.kibanaRequest.no`, + ...getFieldsForCounter(EXPORT_STATS_PREFIX), `${EXPORT_STATS_PREFIX}.allTypesSelected.yes`, `${EXPORT_STATS_PREFIX}.allTypesSelected.no`, ]; +const SPACE_CONTEXT_REGEX = /^\/s\/([a-z0-9_\-]+)/; /** @internal */ export class CoreUsageStatsClient { constructor( private readonly debugLogger: (message: string) => void, + private readonly basePath: IBasePath, private readonly repositoryPromise: Promise ) {} @@ -88,67 +105,128 @@ export class CoreUsageStatsClient { return coreUsageStats; } - public async incrementSavedObjectsImport({ - headers, - createNewCopies, - overwrite, - }: IncrementSavedObjectsImportOptions) { - const isKibanaRequest = getIsKibanaRequest(headers); + public async incrementSavedObjectsBulkCreate(options: BaseIncrementOptions) { + await this.updateUsageStats([], BULK_CREATE_STATS_PREFIX, options); + } + + public async incrementSavedObjectsBulkGet(options: BaseIncrementOptions) { + await this.updateUsageStats([], BULK_GET_STATS_PREFIX, options); + } + + public async incrementSavedObjectsBulkUpdate(options: BaseIncrementOptions) { + await this.updateUsageStats([], BULK_UPDATE_STATS_PREFIX, options); + } + + public async incrementSavedObjectsCreate(options: BaseIncrementOptions) { + await this.updateUsageStats([], CREATE_STATS_PREFIX, options); + } + + public async incrementSavedObjectsDelete(options: BaseIncrementOptions) { + await this.updateUsageStats([], DELETE_STATS_PREFIX, options); + } + + public async incrementSavedObjectsFind(options: BaseIncrementOptions) { + await this.updateUsageStats([], FIND_STATS_PREFIX, options); + } + + public async incrementSavedObjectsGet(options: BaseIncrementOptions) { + await this.updateUsageStats([], GET_STATS_PREFIX, options); + } + + public async incrementSavedObjectsUpdate(options: BaseIncrementOptions) { + await this.updateUsageStats([], UPDATE_STATS_PREFIX, options); + } + + public async incrementSavedObjectsImport(options: IncrementSavedObjectsImportOptions) { + const { createNewCopies, overwrite } = options; const counterFieldNames = [ - 'total', - `kibanaRequest.${isKibanaRequest ? 'yes' : 'no'}`, `createNewCopiesEnabled.${createNewCopies ? 'yes' : 'no'}`, `overwriteEnabled.${overwrite ? 'yes' : 'no'}`, ]; - await this.updateUsageStats(counterFieldNames, IMPORT_STATS_PREFIX); + await this.updateUsageStats(counterFieldNames, IMPORT_STATS_PREFIX, options); } - public async incrementSavedObjectsResolveImportErrors({ - headers, - createNewCopies, - }: IncrementSavedObjectsResolveImportErrorsOptions) { - const isKibanaRequest = getIsKibanaRequest(headers); - const counterFieldNames = [ - 'total', - `kibanaRequest.${isKibanaRequest ? 'yes' : 'no'}`, - `createNewCopiesEnabled.${createNewCopies ? 'yes' : 'no'}`, - ]; - await this.updateUsageStats(counterFieldNames, RESOLVE_IMPORT_STATS_PREFIX); + public async incrementSavedObjectsResolveImportErrors( + options: IncrementSavedObjectsResolveImportErrorsOptions + ) { + const { createNewCopies } = options; + const counterFieldNames = [`createNewCopiesEnabled.${createNewCopies ? 'yes' : 'no'}`]; + await this.updateUsageStats(counterFieldNames, RESOLVE_IMPORT_STATS_PREFIX, options); } - public async incrementSavedObjectsExport({ - headers, - types, - supportedTypes, - }: IncrementSavedObjectsExportOptions) { - const isKibanaRequest = getIsKibanaRequest(headers); + public async incrementSavedObjectsExport(options: IncrementSavedObjectsExportOptions) { + const { types, supportedTypes } = options; const isAllTypesSelected = !!types && supportedTypes.every((x) => types.includes(x)); - const counterFieldNames = [ - 'total', - `kibanaRequest.${isKibanaRequest ? 'yes' : 'no'}`, - `allTypesSelected.${isAllTypesSelected ? 'yes' : 'no'}`, - ]; - await this.updateUsageStats(counterFieldNames, EXPORT_STATS_PREFIX); + const counterFieldNames = [`allTypesSelected.${isAllTypesSelected ? 'yes' : 'no'}`]; + await this.updateUsageStats(counterFieldNames, EXPORT_STATS_PREFIX, options); } - private async updateUsageStats(counterFieldNames: string[], prefix: string) { + private async updateUsageStats( + counterFieldNames: string[], + prefix: string, + { request }: BaseIncrementOptions + ) { const options = { refresh: false }; try { const repository = await this.repositoryPromise; + const fields = this.getFieldsToIncrement(counterFieldNames, prefix, request); await repository.incrementCounter( CORE_USAGE_STATS_TYPE, CORE_USAGE_STATS_ID, - counterFieldNames.map((x) => `${prefix}.${x}`), + fields, options ); } catch (err) { // do nothing } } + + private getIsDefaultNamespace(request: KibanaRequest) { + const requestBasePath = this.basePath.get(request); // obtain the original request basePath, as it may have been modified by a request interceptor + const pathToCheck = this.basePath.remove(requestBasePath); // remove the server basePath from the request basePath + const matchResult = pathToCheck.match(SPACE_CONTEXT_REGEX); // Look for `/s/space-url-context` in the base path + + if (!matchResult || matchResult.length === 0) { + return true; + } + + // Ignoring first result, we only want the capture group result at index 1 + const [, spaceId] = matchResult; + + return spaceId === DEFAULT_NAMESPACE_STRING; + } + + private getFieldsToIncrement( + counterFieldNames: string[], + prefix: string, + request: KibanaRequest + ) { + const isKibanaRequest = getIsKibanaRequest(request); + const isDefaultNamespace = this.getIsDefaultNamespace(request); + const namespaceField = isDefaultNamespace ? 'default' : 'custom'; + return [ + 'total', + `namespace.${namespaceField}.total`, + `namespace.${namespaceField}.kibanaRequest.${isKibanaRequest ? 'yes' : 'no'}`, + ...counterFieldNames, + ].map((x) => `${prefix}.${x}`); + } +} + +function getFieldsForCounter(prefix: string) { + return [ + 'total', + 'namespace.default.total', + 'namespace.default.kibanaRequest.yes', + 'namespace.default.kibanaRequest.no', + 'namespace.custom.total', + 'namespace.custom.kibanaRequest.yes', + 'namespace.custom.kibanaRequest.no', + ].map((x) => `${prefix}.${x}`); } -function getIsKibanaRequest(headers?: Headers) { - // The presence of these three request headers gives us a good indication that this is a first-party request from the Kibana client. +function getIsKibanaRequest({ headers }: KibanaRequest) { + // The presence of these two request headers gives us a good indication that this is a first-party request from the Kibana client. // We can't be 100% certain, but this is a reasonable attempt. - return headers && headers['kbn-version'] && headers.origin && headers.referer; + return headers && headers['kbn-version'] && headers.referer; } diff --git a/src/core/server/core_usage_data/types.ts b/src/core/server/core_usage_data/types.ts index aa41d75e6f2d4..b7952334b4be4 100644 --- a/src/core/server/core_usage_data/types.ts +++ b/src/core/server/core_usage_data/types.ts @@ -27,21 +27,91 @@ import { ISavedObjectTypeRegistry, SavedObjectTypeRegistry } from '..'; * includes point-in-time configuration information. * */ export interface CoreUsageStats { + // Saved Objects Client APIs + 'apiCalls.savedObjectsBulkCreate.total'?: number; + 'apiCalls.savedObjectsBulkCreate.namespace.default.total'?: number; + 'apiCalls.savedObjectsBulkCreate.namespace.default.kibanaRequest.yes'?: number; + 'apiCalls.savedObjectsBulkCreate.namespace.default.kibanaRequest.no'?: number; + 'apiCalls.savedObjectsBulkCreate.namespace.custom.total'?: number; + 'apiCalls.savedObjectsBulkCreate.namespace.custom.kibanaRequest.yes'?: number; + 'apiCalls.savedObjectsBulkCreate.namespace.custom.kibanaRequest.no'?: number; + 'apiCalls.savedObjectsBulkGet.total'?: number; + 'apiCalls.savedObjectsBulkGet.namespace.default.total'?: number; + 'apiCalls.savedObjectsBulkGet.namespace.default.kibanaRequest.yes'?: number; + 'apiCalls.savedObjectsBulkGet.namespace.default.kibanaRequest.no'?: number; + 'apiCalls.savedObjectsBulkGet.namespace.custom.total'?: number; + 'apiCalls.savedObjectsBulkGet.namespace.custom.kibanaRequest.yes'?: number; + 'apiCalls.savedObjectsBulkGet.namespace.custom.kibanaRequest.no'?: number; + 'apiCalls.savedObjectsBulkUpdate.total'?: number; + 'apiCalls.savedObjectsBulkUpdate.namespace.default.total'?: number; + 'apiCalls.savedObjectsBulkUpdate.namespace.default.kibanaRequest.yes'?: number; + 'apiCalls.savedObjectsBulkUpdate.namespace.default.kibanaRequest.no'?: number; + 'apiCalls.savedObjectsBulkUpdate.namespace.custom.total'?: number; + 'apiCalls.savedObjectsBulkUpdate.namespace.custom.kibanaRequest.yes'?: number; + 'apiCalls.savedObjectsBulkUpdate.namespace.custom.kibanaRequest.no'?: number; + 'apiCalls.savedObjectsCreate.total'?: number; + 'apiCalls.savedObjectsCreate.namespace.default.total'?: number; + 'apiCalls.savedObjectsCreate.namespace.default.kibanaRequest.yes'?: number; + 'apiCalls.savedObjectsCreate.namespace.default.kibanaRequest.no'?: number; + 'apiCalls.savedObjectsCreate.namespace.custom.total'?: number; + 'apiCalls.savedObjectsCreate.namespace.custom.kibanaRequest.yes'?: number; + 'apiCalls.savedObjectsCreate.namespace.custom.kibanaRequest.no'?: number; + 'apiCalls.savedObjectsDelete.total'?: number; + 'apiCalls.savedObjectsDelete.namespace.default.total'?: number; + 'apiCalls.savedObjectsDelete.namespace.default.kibanaRequest.yes'?: number; + 'apiCalls.savedObjectsDelete.namespace.default.kibanaRequest.no'?: number; + 'apiCalls.savedObjectsDelete.namespace.custom.total'?: number; + 'apiCalls.savedObjectsDelete.namespace.custom.kibanaRequest.yes'?: number; + 'apiCalls.savedObjectsDelete.namespace.custom.kibanaRequest.no'?: number; + 'apiCalls.savedObjectsFind.total'?: number; + 'apiCalls.savedObjectsFind.namespace.default.total'?: number; + 'apiCalls.savedObjectsFind.namespace.default.kibanaRequest.yes'?: number; + 'apiCalls.savedObjectsFind.namespace.default.kibanaRequest.no'?: number; + 'apiCalls.savedObjectsFind.namespace.custom.total'?: number; + 'apiCalls.savedObjectsFind.namespace.custom.kibanaRequest.yes'?: number; + 'apiCalls.savedObjectsFind.namespace.custom.kibanaRequest.no'?: number; + 'apiCalls.savedObjectsGet.total'?: number; + 'apiCalls.savedObjectsGet.namespace.default.total'?: number; + 'apiCalls.savedObjectsGet.namespace.default.kibanaRequest.yes'?: number; + 'apiCalls.savedObjectsGet.namespace.default.kibanaRequest.no'?: number; + 'apiCalls.savedObjectsGet.namespace.custom.total'?: number; + 'apiCalls.savedObjectsGet.namespace.custom.kibanaRequest.yes'?: number; + 'apiCalls.savedObjectsGet.namespace.custom.kibanaRequest.no'?: number; + 'apiCalls.savedObjectsUpdate.total'?: number; + 'apiCalls.savedObjectsUpdate.namespace.default.total'?: number; + 'apiCalls.savedObjectsUpdate.namespace.default.kibanaRequest.yes'?: number; + 'apiCalls.savedObjectsUpdate.namespace.default.kibanaRequest.no'?: number; + 'apiCalls.savedObjectsUpdate.namespace.custom.total'?: number; + 'apiCalls.savedObjectsUpdate.namespace.custom.kibanaRequest.yes'?: number; + 'apiCalls.savedObjectsUpdate.namespace.custom.kibanaRequest.no'?: number; + // Saved Objects Management APIs 'apiCalls.savedObjectsImport.total'?: number; - 'apiCalls.savedObjectsImport.kibanaRequest.yes'?: number; - 'apiCalls.savedObjectsImport.kibanaRequest.no'?: number; + 'apiCalls.savedObjectsImport.namespace.default.total'?: number; + 'apiCalls.savedObjectsImport.namespace.default.kibanaRequest.yes'?: number; + 'apiCalls.savedObjectsImport.namespace.default.kibanaRequest.no'?: number; + 'apiCalls.savedObjectsImport.namespace.custom.total'?: number; + 'apiCalls.savedObjectsImport.namespace.custom.kibanaRequest.yes'?: number; + 'apiCalls.savedObjectsImport.namespace.custom.kibanaRequest.no'?: number; 'apiCalls.savedObjectsImport.createNewCopiesEnabled.yes'?: number; 'apiCalls.savedObjectsImport.createNewCopiesEnabled.no'?: number; 'apiCalls.savedObjectsImport.overwriteEnabled.yes'?: number; 'apiCalls.savedObjectsImport.overwriteEnabled.no'?: number; 'apiCalls.savedObjectsResolveImportErrors.total'?: number; - 'apiCalls.savedObjectsResolveImportErrors.kibanaRequest.yes'?: number; - 'apiCalls.savedObjectsResolveImportErrors.kibanaRequest.no'?: number; + 'apiCalls.savedObjectsResolveImportErrors.namespace.default.total'?: number; + 'apiCalls.savedObjectsResolveImportErrors.namespace.default.kibanaRequest.yes'?: number; + 'apiCalls.savedObjectsResolveImportErrors.namespace.default.kibanaRequest.no'?: number; + 'apiCalls.savedObjectsResolveImportErrors.namespace.custom.total'?: number; + 'apiCalls.savedObjectsResolveImportErrors.namespace.custom.kibanaRequest.yes'?: number; + 'apiCalls.savedObjectsResolveImportErrors.namespace.custom.kibanaRequest.no'?: number; 'apiCalls.savedObjectsResolveImportErrors.createNewCopiesEnabled.yes'?: number; 'apiCalls.savedObjectsResolveImportErrors.createNewCopiesEnabled.no'?: number; 'apiCalls.savedObjectsExport.total'?: number; - 'apiCalls.savedObjectsExport.kibanaRequest.yes'?: number; - 'apiCalls.savedObjectsExport.kibanaRequest.no'?: number; + 'apiCalls.savedObjectsExport.namespace.default.total'?: number; + 'apiCalls.savedObjectsExport.namespace.default.kibanaRequest.yes'?: number; + 'apiCalls.savedObjectsExport.namespace.default.kibanaRequest.no'?: number; + 'apiCalls.savedObjectsExport.namespace.custom.total'?: number; + 'apiCalls.savedObjectsExport.namespace.custom.kibanaRequest.yes'?: number; + 'apiCalls.savedObjectsExport.namespace.custom.kibanaRequest.no'?: number; 'apiCalls.savedObjectsExport.allTypesSelected.yes'?: number; 'apiCalls.savedObjectsExport.allTypesSelected.no'?: number; } diff --git a/src/core/server/saved_objects/routes/bulk_create.ts b/src/core/server/saved_objects/routes/bulk_create.ts index b048c5d8f99bf..b1286f3a1f06c 100644 --- a/src/core/server/saved_objects/routes/bulk_create.ts +++ b/src/core/server/saved_objects/routes/bulk_create.ts @@ -19,8 +19,13 @@ import { schema } from '@kbn/config-schema'; import { IRouter } from '../../http'; +import { CoreUsageDataSetup } from '../../core_usage_data'; -export const registerBulkCreateRoute = (router: IRouter) => { +interface RouteDependencies { + coreUsageData: CoreUsageDataSetup; +} + +export const registerBulkCreateRoute = (router: IRouter, { coreUsageData }: RouteDependencies) => { router.post( { path: '/_bulk_create', @@ -51,6 +56,10 @@ export const registerBulkCreateRoute = (router: IRouter) => { }, router.handleLegacyErrors(async (context, req, res) => { const { overwrite } = req.query; + + const usageStatsClient = coreUsageData.getClient(); + usageStatsClient.incrementSavedObjectsBulkCreate({ request: req }).catch(() => {}); + const result = await context.core.savedObjects.client.bulkCreate(req.body, { overwrite }); return res.ok({ body: result }); }) diff --git a/src/core/server/saved_objects/routes/bulk_get.ts b/src/core/server/saved_objects/routes/bulk_get.ts index 067388dcf9220..41c77520b4faf 100644 --- a/src/core/server/saved_objects/routes/bulk_get.ts +++ b/src/core/server/saved_objects/routes/bulk_get.ts @@ -19,8 +19,13 @@ import { schema } from '@kbn/config-schema'; import { IRouter } from '../../http'; +import { CoreUsageDataSetup } from '../../core_usage_data'; -export const registerBulkGetRoute = (router: IRouter) => { +interface RouteDependencies { + coreUsageData: CoreUsageDataSetup; +} + +export const registerBulkGetRoute = (router: IRouter, { coreUsageData }: RouteDependencies) => { router.post( { path: '/_bulk_get', @@ -35,6 +40,9 @@ export const registerBulkGetRoute = (router: IRouter) => { }, }, router.handleLegacyErrors(async (context, req, res) => { + const usageStatsClient = coreUsageData.getClient(); + usageStatsClient.incrementSavedObjectsBulkGet({ request: req }).catch(() => {}); + const result = await context.core.savedObjects.client.bulkGet(req.body); return res.ok({ body: result }); }) diff --git a/src/core/server/saved_objects/routes/bulk_update.ts b/src/core/server/saved_objects/routes/bulk_update.ts index 882213644146a..b4014b5422d5d 100644 --- a/src/core/server/saved_objects/routes/bulk_update.ts +++ b/src/core/server/saved_objects/routes/bulk_update.ts @@ -19,8 +19,13 @@ import { schema } from '@kbn/config-schema'; import { IRouter } from '../../http'; +import { CoreUsageDataSetup } from '../../core_usage_data'; -export const registerBulkUpdateRoute = (router: IRouter) => { +interface RouteDependencies { + coreUsageData: CoreUsageDataSetup; +} + +export const registerBulkUpdateRoute = (router: IRouter, { coreUsageData }: RouteDependencies) => { router.put( { path: '/_bulk_update', @@ -46,6 +51,9 @@ export const registerBulkUpdateRoute = (router: IRouter) => { }, }, router.handleLegacyErrors(async (context, req, res) => { + const usageStatsClient = coreUsageData.getClient(); + usageStatsClient.incrementSavedObjectsBulkUpdate({ request: req }).catch(() => {}); + const savedObject = await context.core.savedObjects.client.bulkUpdate(req.body); return res.ok({ body: savedObject }); }) diff --git a/src/core/server/saved_objects/routes/create.ts b/src/core/server/saved_objects/routes/create.ts index 816315705a375..cb6a849be9f2d 100644 --- a/src/core/server/saved_objects/routes/create.ts +++ b/src/core/server/saved_objects/routes/create.ts @@ -19,8 +19,13 @@ import { schema } from '@kbn/config-schema'; import { IRouter } from '../../http'; +import { CoreUsageDataSetup } from '../../core_usage_data'; -export const registerCreateRoute = (router: IRouter) => { +interface RouteDependencies { + coreUsageData: CoreUsageDataSetup; +} + +export const registerCreateRoute = (router: IRouter, { coreUsageData }: RouteDependencies) => { router.post( { path: '/{type}/{id?}', @@ -53,6 +58,9 @@ export const registerCreateRoute = (router: IRouter) => { const { overwrite } = req.query; const { attributes, migrationVersion, references, initialNamespaces } = req.body; + const usageStatsClient = coreUsageData.getClient(); + usageStatsClient.incrementSavedObjectsCreate({ request: req }).catch(() => {}); + const options = { id, overwrite, migrationVersion, references, initialNamespaces }; const result = await context.core.savedObjects.client.create(type, attributes, options); return res.ok({ body: result }); diff --git a/src/core/server/saved_objects/routes/delete.ts b/src/core/server/saved_objects/routes/delete.ts index d99397d2a050c..69d2290325a93 100644 --- a/src/core/server/saved_objects/routes/delete.ts +++ b/src/core/server/saved_objects/routes/delete.ts @@ -19,8 +19,13 @@ import { schema } from '@kbn/config-schema'; import { IRouter } from '../../http'; +import { CoreUsageDataSetup } from '../../core_usage_data'; -export const registerDeleteRoute = (router: IRouter) => { +interface RouteDependencies { + coreUsageData: CoreUsageDataSetup; +} + +export const registerDeleteRoute = (router: IRouter, { coreUsageData }: RouteDependencies) => { router.delete( { path: '/{type}/{id}', @@ -37,6 +42,10 @@ export const registerDeleteRoute = (router: IRouter) => { router.handleLegacyErrors(async (context, req, res) => { const { type, id } = req.params; const { force } = req.query; + + const usageStatsClient = coreUsageData.getClient(); + usageStatsClient.incrementSavedObjectsDelete({ request: req }).catch(() => {}); + const result = await context.core.savedObjects.client.delete(type, id, { force }); return res.ok({ body: result }); }) diff --git a/src/core/server/saved_objects/routes/export.ts b/src/core/server/saved_objects/routes/export.ts index 387280d777eaa..8f5c19d927d40 100644 --- a/src/core/server/saved_objects/routes/export.ts +++ b/src/core/server/saved_objects/routes/export.ts @@ -104,10 +104,9 @@ export const registerExportRoute = ( } } - const { headers } = req; const usageStatsClient = coreUsageData.getClient(); usageStatsClient - .incrementSavedObjectsExport({ headers, types, supportedTypes }) + .incrementSavedObjectsExport({ request: req, types, supportedTypes }) .catch(() => {}); const exportStream = await exportSavedObjectsToStream({ diff --git a/src/core/server/saved_objects/routes/find.ts b/src/core/server/saved_objects/routes/find.ts index 915d0cccf7af9..7ddcfa91da22d 100644 --- a/src/core/server/saved_objects/routes/find.ts +++ b/src/core/server/saved_objects/routes/find.ts @@ -19,8 +19,13 @@ import { schema } from '@kbn/config-schema'; import { IRouter } from '../../http'; +import { CoreUsageDataSetup } from '../../core_usage_data'; -export const registerFindRoute = (router: IRouter) => { +interface RouteDependencies { + coreUsageData: CoreUsageDataSetup; +} + +export const registerFindRoute = (router: IRouter, { coreUsageData }: RouteDependencies) => { const referenceSchema = schema.object({ type: schema.string(), id: schema.string(), @@ -61,6 +66,9 @@ export const registerFindRoute = (router: IRouter) => { const namespaces = typeof req.query.namespaces === 'string' ? [req.query.namespaces] : req.query.namespaces; + const usageStatsClient = coreUsageData.getClient(); + usageStatsClient.incrementSavedObjectsFind({ request: req }).catch(() => {}); + const result = await context.core.savedObjects.client.find({ perPage: query.per_page, page: query.page, diff --git a/src/core/server/saved_objects/routes/get.ts b/src/core/server/saved_objects/routes/get.ts index f1b974c70b1a9..d29229eab33ff 100644 --- a/src/core/server/saved_objects/routes/get.ts +++ b/src/core/server/saved_objects/routes/get.ts @@ -19,8 +19,13 @@ import { schema } from '@kbn/config-schema'; import { IRouter } from '../../http'; +import { CoreUsageDataSetup } from '../../core_usage_data'; -export const registerGetRoute = (router: IRouter) => { +interface RouteDependencies { + coreUsageData: CoreUsageDataSetup; +} + +export const registerGetRoute = (router: IRouter, { coreUsageData }: RouteDependencies) => { router.get( { path: '/{type}/{id}', @@ -33,6 +38,10 @@ export const registerGetRoute = (router: IRouter) => { }, router.handleLegacyErrors(async (context, req, res) => { const { type, id } = req.params; + + const usageStatsClient = coreUsageData.getClient(); + usageStatsClient.incrementSavedObjectsGet({ request: req }).catch(() => {}); + const savedObject = await context.core.savedObjects.client.get(type, id); return res.ok({ body: savedObject }); }) diff --git a/src/core/server/saved_objects/routes/import.ts b/src/core/server/saved_objects/routes/import.ts index 27be710c0a92a..ebc52c32e2c70 100644 --- a/src/core/server/saved_objects/routes/import.ts +++ b/src/core/server/saved_objects/routes/import.ts @@ -75,10 +75,9 @@ export const registerImportRoute = ( router.handleLegacyErrors(async (context, req, res) => { const { overwrite, createNewCopies } = req.query; - const { headers } = req; const usageStatsClient = coreUsageData.getClient(); usageStatsClient - .incrementSavedObjectsImport({ headers, createNewCopies, overwrite }) + .incrementSavedObjectsImport({ request: req, createNewCopies, overwrite }) .catch(() => {}); const file = req.body.file as FileStream; diff --git a/src/core/server/saved_objects/routes/index.ts b/src/core/server/saved_objects/routes/index.ts index 19154b8583654..0ffd1104d35e2 100644 --- a/src/core/server/saved_objects/routes/index.ts +++ b/src/core/server/saved_objects/routes/index.ts @@ -51,14 +51,14 @@ export function registerRoutes({ }) { const router = http.createRouter('/api/saved_objects/'); - registerGetRoute(router); - registerCreateRoute(router); - registerDeleteRoute(router); - registerFindRoute(router); - registerUpdateRoute(router); - registerBulkGetRoute(router); - registerBulkCreateRoute(router); - registerBulkUpdateRoute(router); + registerGetRoute(router, { coreUsageData }); + registerCreateRoute(router, { coreUsageData }); + registerDeleteRoute(router, { coreUsageData }); + registerFindRoute(router, { coreUsageData }); + registerUpdateRoute(router, { coreUsageData }); + registerBulkGetRoute(router, { coreUsageData }); + registerBulkCreateRoute(router, { coreUsageData }); + registerBulkUpdateRoute(router, { coreUsageData }); registerLogLegacyImportRoute(router, logger); registerExportRoute(router, { config, coreUsageData }); registerImportRoute(router, { config, coreUsageData }); diff --git a/src/core/server/saved_objects/routes/integration_tests/bulk_create.test.ts b/src/core/server/saved_objects/routes/integration_tests/bulk_create.test.ts index 3d455ff9d594c..186b21ef361a9 100644 --- a/src/core/server/saved_objects/routes/integration_tests/bulk_create.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/bulk_create.test.ts @@ -21,6 +21,9 @@ import supertest from 'supertest'; import { UnwrapPromise } from '@kbn/utility-types'; import { registerBulkCreateRoute } from '../bulk_create'; import { savedObjectsClientMock } from '../../../../../core/server/mocks'; +import { CoreUsageStatsClient } from '../../../core_usage_data'; +import { coreUsageStatsClientMock } from '../../../core_usage_data/core_usage_stats_client.mock'; +import { coreUsageDataServiceMock } from '../../../core_usage_data/core_usage_data_service.mock'; import { setupServer } from '../test_utils'; type SetupServerReturn = UnwrapPromise>; @@ -30,6 +33,7 @@ describe('POST /api/saved_objects/_bulk_create', () => { let httpSetup: SetupServerReturn['httpSetup']; let handlerContext: SetupServerReturn['handlerContext']; let savedObjectsClient: ReturnType; + let coreUsageStatsClient: jest.Mocked; beforeEach(async () => { ({ server, httpSetup, handlerContext } = await setupServer()); @@ -37,7 +41,10 @@ describe('POST /api/saved_objects/_bulk_create', () => { savedObjectsClient.bulkCreate.mockResolvedValue({ saved_objects: [] }); const router = httpSetup.createRouter('/api/saved_objects/'); - registerBulkCreateRoute(router); + coreUsageStatsClient = coreUsageStatsClientMock.create(); + coreUsageStatsClient.incrementSavedObjectsBulkCreate.mockRejectedValue(new Error('Oh no!')); // intentionally throw this error, which is swallowed, so we can assert that the operation does not fail + const coreUsageData = coreUsageDataServiceMock.createSetupContract(coreUsageStatsClient); + registerBulkCreateRoute(router, { coreUsageData }); await server.start(); }); @@ -46,7 +53,7 @@ describe('POST /api/saved_objects/_bulk_create', () => { await server.stop(); }); - it('formats successful response', async () => { + it('formats successful response and records usage stats', async () => { const clientResponse = { saved_objects: [ { @@ -75,6 +82,9 @@ describe('POST /api/saved_objects/_bulk_create', () => { .expect(200); expect(result.body).toEqual(clientResponse); + expect(coreUsageStatsClient.incrementSavedObjectsBulkCreate).toHaveBeenCalledWith({ + request: expect.anything(), + }); }); it('calls upon savedObjectClient.bulkCreate', async () => { diff --git a/src/core/server/saved_objects/routes/integration_tests/bulk_get.test.ts b/src/core/server/saved_objects/routes/integration_tests/bulk_get.test.ts index 5deea94299d7d..c6028f86fcc7c 100644 --- a/src/core/server/saved_objects/routes/integration_tests/bulk_get.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/bulk_get.test.ts @@ -21,6 +21,9 @@ import supertest from 'supertest'; import { UnwrapPromise } from '@kbn/utility-types'; import { registerBulkGetRoute } from '../bulk_get'; import { savedObjectsClientMock } from '../../../../../core/server/mocks'; +import { CoreUsageStatsClient } from '../../../core_usage_data'; +import { coreUsageStatsClientMock } from '../../../core_usage_data/core_usage_stats_client.mock'; +import { coreUsageDataServiceMock } from '../../../core_usage_data/core_usage_data_service.mock'; import { setupServer } from '../test_utils'; type SetupServerReturn = UnwrapPromise>; @@ -30,6 +33,7 @@ describe('POST /api/saved_objects/_bulk_get', () => { let httpSetup: SetupServerReturn['httpSetup']; let handlerContext: SetupServerReturn['handlerContext']; let savedObjectsClient: ReturnType; + let coreUsageStatsClient: jest.Mocked; beforeEach(async () => { ({ server, httpSetup, handlerContext } = await setupServer()); @@ -39,7 +43,10 @@ describe('POST /api/saved_objects/_bulk_get', () => { saved_objects: [], }); const router = httpSetup.createRouter('/api/saved_objects/'); - registerBulkGetRoute(router); + coreUsageStatsClient = coreUsageStatsClientMock.create(); + coreUsageStatsClient.incrementSavedObjectsBulkGet.mockRejectedValue(new Error('Oh no!')); // intentionally throw this error, which is swallowed, so we can assert that the operation does not fail + const coreUsageData = coreUsageDataServiceMock.createSetupContract(coreUsageStatsClient); + registerBulkGetRoute(router, { coreUsageData }); await server.start(); }); @@ -48,7 +55,7 @@ describe('POST /api/saved_objects/_bulk_get', () => { await server.stop(); }); - it('formats successful response', async () => { + it('formats successful response and records usage stats', async () => { const clientResponse = { saved_objects: [ { @@ -74,6 +81,9 @@ describe('POST /api/saved_objects/_bulk_get', () => { .expect(200); expect(result.body).toEqual(clientResponse); + expect(coreUsageStatsClient.incrementSavedObjectsBulkGet).toHaveBeenCalledWith({ + request: expect.anything(), + }); }); it('calls upon savedObjectClient.bulkGet', async () => { diff --git a/src/core/server/saved_objects/routes/integration_tests/bulk_update.test.ts b/src/core/server/saved_objects/routes/integration_tests/bulk_update.test.ts index 45f310ecc3fa2..c038c5303dd69 100644 --- a/src/core/server/saved_objects/routes/integration_tests/bulk_update.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/bulk_update.test.ts @@ -21,6 +21,9 @@ import supertest from 'supertest'; import { UnwrapPromise } from '@kbn/utility-types'; import { registerBulkUpdateRoute } from '../bulk_update'; import { savedObjectsClientMock } from '../../../../../core/server/mocks'; +import { CoreUsageStatsClient } from '../../../core_usage_data'; +import { coreUsageStatsClientMock } from '../../../core_usage_data/core_usage_stats_client.mock'; +import { coreUsageDataServiceMock } from '../../../core_usage_data/core_usage_data_service.mock'; import { setupServer } from '../test_utils'; type SetupServerReturn = UnwrapPromise>; @@ -30,13 +33,17 @@ describe('PUT /api/saved_objects/_bulk_update', () => { let httpSetup: SetupServerReturn['httpSetup']; let handlerContext: SetupServerReturn['handlerContext']; let savedObjectsClient: ReturnType; + let coreUsageStatsClient: jest.Mocked; beforeEach(async () => { ({ server, httpSetup, handlerContext } = await setupServer()); savedObjectsClient = handlerContext.savedObjects.client; const router = httpSetup.createRouter('/api/saved_objects/'); - registerBulkUpdateRoute(router); + coreUsageStatsClient = coreUsageStatsClientMock.create(); + coreUsageStatsClient.incrementSavedObjectsBulkUpdate.mockRejectedValue(new Error('Oh no!')); // intentionally throw this error, which is swallowed, so we can assert that the operation does not fail + const coreUsageData = coreUsageDataServiceMock.createSetupContract(coreUsageStatsClient); + registerBulkUpdateRoute(router, { coreUsageData }); await server.start(); }); @@ -45,7 +52,7 @@ describe('PUT /api/saved_objects/_bulk_update', () => { await server.stop(); }); - it('formats successful response', async () => { + it('formats successful response and records usage stats', async () => { const time = Date.now().toLocaleString(); const clientResponse = [ { @@ -92,6 +99,9 @@ describe('PUT /api/saved_objects/_bulk_update', () => { .expect(200); expect(result.body).toEqual({ saved_objects: clientResponse }); + expect(coreUsageStatsClient.incrementSavedObjectsBulkUpdate).toHaveBeenCalledWith({ + request: expect.anything(), + }); }); it('calls upon savedObjectClient.bulkUpdate', async () => { diff --git a/src/core/server/saved_objects/routes/integration_tests/create.test.ts b/src/core/server/saved_objects/routes/integration_tests/create.test.ts index 9e69c3dbc64ec..8c209a05f2948 100644 --- a/src/core/server/saved_objects/routes/integration_tests/create.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/create.test.ts @@ -21,6 +21,9 @@ import supertest from 'supertest'; import { UnwrapPromise } from '@kbn/utility-types'; import { registerCreateRoute } from '../create'; import { savedObjectsClientMock } from '../../service/saved_objects_client.mock'; +import { CoreUsageStatsClient } from '../../../core_usage_data'; +import { coreUsageStatsClientMock } from '../../../core_usage_data/core_usage_stats_client.mock'; +import { coreUsageDataServiceMock } from '../../../core_usage_data/core_usage_data_service.mock'; import { setupServer } from '../test_utils'; type SetupServerReturn = UnwrapPromise>; @@ -30,6 +33,7 @@ describe('POST /api/saved_objects/{type}', () => { let httpSetup: SetupServerReturn['httpSetup']; let handlerContext: SetupServerReturn['handlerContext']; let savedObjectsClient: ReturnType; + let coreUsageStatsClient: jest.Mocked; const clientResponse = { id: 'logstash-*', @@ -46,7 +50,10 @@ describe('POST /api/saved_objects/{type}', () => { savedObjectsClient.create.mockImplementation(() => Promise.resolve(clientResponse)); const router = httpSetup.createRouter('/api/saved_objects/'); - registerCreateRoute(router); + coreUsageStatsClient = coreUsageStatsClientMock.create(); + coreUsageStatsClient.incrementSavedObjectsCreate.mockRejectedValue(new Error('Oh no!')); // intentionally throw this error, which is swallowed, so we can assert that the operation does not fail + const coreUsageData = coreUsageDataServiceMock.createSetupContract(coreUsageStatsClient); + registerCreateRoute(router, { coreUsageData }); await server.start(); }); @@ -55,7 +62,7 @@ describe('POST /api/saved_objects/{type}', () => { await server.stop(); }); - it('formats successful response', async () => { + it('formats successful response and records usage stats', async () => { const result = await supertest(httpSetup.server.listener) .post('/api/saved_objects/index-pattern') .send({ @@ -66,6 +73,9 @@ describe('POST /api/saved_objects/{type}', () => { .expect(200); expect(result.body).toEqual(clientResponse); + expect(coreUsageStatsClient.incrementSavedObjectsCreate).toHaveBeenCalledWith({ + request: expect.anything(), + }); }); it('requires attributes', async () => { diff --git a/src/core/server/saved_objects/routes/integration_tests/delete.test.ts b/src/core/server/saved_objects/routes/integration_tests/delete.test.ts index ff8642a34929f..c70754632980a 100644 --- a/src/core/server/saved_objects/routes/integration_tests/delete.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/delete.test.ts @@ -21,6 +21,9 @@ import supertest from 'supertest'; import { UnwrapPromise } from '@kbn/utility-types'; import { registerDeleteRoute } from '../delete'; import { savedObjectsClientMock } from '../../../../../core/server/mocks'; +import { CoreUsageStatsClient } from '../../../core_usage_data'; +import { coreUsageStatsClientMock } from '../../../core_usage_data/core_usage_stats_client.mock'; +import { coreUsageDataServiceMock } from '../../../core_usage_data/core_usage_data_service.mock'; import { setupServer } from '../test_utils'; type SetupServerReturn = UnwrapPromise>; @@ -30,13 +33,17 @@ describe('DELETE /api/saved_objects/{type}/{id}', () => { let httpSetup: SetupServerReturn['httpSetup']; let handlerContext: SetupServerReturn['handlerContext']; let savedObjectsClient: ReturnType; + let coreUsageStatsClient: jest.Mocked; beforeEach(async () => { ({ server, httpSetup, handlerContext } = await setupServer()); savedObjectsClient = handlerContext.savedObjects.client; const router = httpSetup.createRouter('/api/saved_objects/'); - registerDeleteRoute(router); + coreUsageStatsClient = coreUsageStatsClientMock.create(); + coreUsageStatsClient.incrementSavedObjectsDelete.mockRejectedValue(new Error('Oh no!')); // intentionally throw this error, which is swallowed, so we can assert that the operation does not fail + const coreUsageData = coreUsageDataServiceMock.createSetupContract(coreUsageStatsClient); + registerDeleteRoute(router, { coreUsageData }); await server.start(); }); @@ -45,12 +52,15 @@ describe('DELETE /api/saved_objects/{type}/{id}', () => { await server.stop(); }); - it('formats successful response', async () => { + it('formats successful response and records usage stats', async () => { const result = await supertest(httpSetup.server.listener) .delete('/api/saved_objects/index-pattern/logstash-*') .expect(200); expect(result.body).toEqual({}); + expect(coreUsageStatsClient.incrementSavedObjectsDelete).toHaveBeenCalledWith({ + request: expect.anything(), + }); }); it('calls upon savedObjectClient.delete', async () => { diff --git a/src/core/server/saved_objects/routes/integration_tests/export.test.ts b/src/core/server/saved_objects/routes/integration_tests/export.test.ts index c37ed2da97681..d5b1e492e573f 100644 --- a/src/core/server/saved_objects/routes/integration_tests/export.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/export.test.ts @@ -54,7 +54,7 @@ describe('POST /api/saved_objects/_export', () => { const router = httpSetup.createRouter('/api/saved_objects/'); coreUsageStatsClient = coreUsageStatsClientMock.create(); - coreUsageStatsClient.incrementSavedObjectsExport.mockRejectedValue(new Error('Oh no!')); // this error is intentionally swallowed so the export does not fail + coreUsageStatsClient.incrementSavedObjectsExport.mockRejectedValue(new Error('Oh no!')); // intentionally throw this error, which is swallowed, so we can assert that the operation does not fail const coreUsageData = coreUsageDataServiceMock.createSetupContract(coreUsageStatsClient); registerExportRoute(router, { config, coreUsageData }); @@ -118,7 +118,7 @@ describe('POST /api/saved_objects/_export', () => { }) ); expect(coreUsageStatsClient.incrementSavedObjectsExport).toHaveBeenCalledWith({ - headers: expect.anything(), + request: expect.anything(), types: ['search'], supportedTypes: ['index-pattern', 'search'], }); diff --git a/src/core/server/saved_objects/routes/integration_tests/find.test.ts b/src/core/server/saved_objects/routes/integration_tests/find.test.ts index 9a426ef48c7da..8e3de04648b83 100644 --- a/src/core/server/saved_objects/routes/integration_tests/find.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/find.test.ts @@ -23,6 +23,9 @@ import querystring from 'querystring'; import { UnwrapPromise } from '@kbn/utility-types'; import { registerFindRoute } from '../find'; import { savedObjectsClientMock } from '../../../../../core/server/mocks'; +import { CoreUsageStatsClient } from '../../../core_usage_data'; +import { coreUsageStatsClientMock } from '../../../core_usage_data/core_usage_stats_client.mock'; +import { coreUsageDataServiceMock } from '../../../core_usage_data/core_usage_data_service.mock'; import { setupServer } from '../test_utils'; type SetupServerReturn = UnwrapPromise>; @@ -32,6 +35,7 @@ describe('GET /api/saved_objects/_find', () => { let httpSetup: SetupServerReturn['httpSetup']; let handlerContext: SetupServerReturn['handlerContext']; let savedObjectsClient: ReturnType; + let coreUsageStatsClient: jest.Mocked; const clientResponse = { total: 0, @@ -47,7 +51,10 @@ describe('GET /api/saved_objects/_find', () => { savedObjectsClient.find.mockResolvedValue(clientResponse); const router = httpSetup.createRouter('/api/saved_objects/'); - registerFindRoute(router); + coreUsageStatsClient = coreUsageStatsClientMock.create(); + coreUsageStatsClient.incrementSavedObjectsFind.mockRejectedValue(new Error('Oh no!')); // intentionally throw this error, which is swallowed, so we can assert that the operation does not fail + const coreUsageData = coreUsageDataServiceMock.createSetupContract(coreUsageStatsClient); + registerFindRoute(router, { coreUsageData }); await server.start(); }); @@ -66,7 +73,7 @@ describe('GET /api/saved_objects/_find', () => { ); }); - it('formats successful response', async () => { + it('formats successful response and records usage stats', async () => { const findResponse = { total: 2, per_page: 2, @@ -103,6 +110,9 @@ describe('GET /api/saved_objects/_find', () => { .expect(200); expect(result.body).toEqual(findResponse); + expect(coreUsageStatsClient.incrementSavedObjectsFind).toHaveBeenCalledWith({ + request: expect.anything(), + }); }); it('calls upon savedObjectClient.find with defaults', async () => { diff --git a/src/core/server/saved_objects/routes/integration_tests/get.test.ts b/src/core/server/saved_objects/routes/integration_tests/get.test.ts index 1e3405d7a318f..e05b6b09659fa 100644 --- a/src/core/server/saved_objects/routes/integration_tests/get.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/get.test.ts @@ -21,6 +21,9 @@ import supertest from 'supertest'; import { registerGetRoute } from '../get'; import { ContextService } from '../../../context'; import { savedObjectsClientMock } from '../../service/saved_objects_client.mock'; +import { CoreUsageStatsClient } from '../../../core_usage_data'; +import { coreUsageStatsClientMock } from '../../../core_usage_data/core_usage_stats_client.mock'; +import { coreUsageDataServiceMock } from '../../../core_usage_data/core_usage_data_service.mock'; import { HttpService, InternalHttpServiceSetup } from '../../../http'; import { createHttpServer, createCoreContext } from '../../../http/test_utils'; import { coreMock } from '../../../mocks'; @@ -32,6 +35,7 @@ describe('GET /api/saved_objects/{type}/{id}', () => { let httpSetup: InternalHttpServiceSetup; let handlerContext: ReturnType; let savedObjectsClient: ReturnType; + let coreUsageStatsClient: jest.Mocked; beforeEach(async () => { const coreContext = createCoreContext({ coreId }); @@ -50,7 +54,10 @@ describe('GET /api/saved_objects/{type}/{id}', () => { }); const router = httpSetup.createRouter('/api/saved_objects/'); - registerGetRoute(router); + coreUsageStatsClient = coreUsageStatsClientMock.create(); + coreUsageStatsClient.incrementSavedObjectsGet.mockRejectedValue(new Error('Oh no!')); // intentionally throw this error, which is swallowed, so we can assert that the operation does not fail + const coreUsageData = coreUsageDataServiceMock.createSetupContract(coreUsageStatsClient); + registerGetRoute(router, { coreUsageData }); await server.start(); }); @@ -59,7 +66,7 @@ describe('GET /api/saved_objects/{type}/{id}', () => { await server.stop(); }); - it('formats successful response', async () => { + it('formats successful response and records usage stats', async () => { const clientResponse = { id: 'logstash-*', title: 'logstash-*', @@ -77,6 +84,9 @@ describe('GET /api/saved_objects/{type}/{id}', () => { .expect(200); expect(result.body).toEqual(clientResponse); + expect(coreUsageStatsClient.incrementSavedObjectsGet).toHaveBeenCalledWith({ + request: expect.anything(), + }); }); it('calls upon savedObjectClient.get', async () => { diff --git a/src/core/server/saved_objects/routes/integration_tests/import.test.ts b/src/core/server/saved_objects/routes/integration_tests/import.test.ts index 9dfb7f79a925d..b80deb87725d4 100644 --- a/src/core/server/saved_objects/routes/integration_tests/import.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/import.test.ts @@ -76,7 +76,7 @@ describe(`POST ${URL}`, () => { const router = httpSetup.createRouter('/internal/saved_objects/'); coreUsageStatsClient = coreUsageStatsClientMock.create(); - coreUsageStatsClient.incrementSavedObjectsImport.mockRejectedValue(new Error('Oh no!')); // this error is intentionally swallowed so the import does not fail + coreUsageStatsClient.incrementSavedObjectsImport.mockRejectedValue(new Error('Oh no!')); // intentionally throw this error, which is swallowed, so we can assert that the operation does not fail const coreUsageData = coreUsageDataServiceMock.createSetupContract(coreUsageStatsClient); registerImportRoute(router, { config, coreUsageData }); @@ -106,7 +106,7 @@ describe(`POST ${URL}`, () => { expect(result.body).toEqual({ success: true, successCount: 0 }); expect(savedObjectsClient.bulkCreate).not.toHaveBeenCalled(); // no objects were created expect(coreUsageStatsClient.incrementSavedObjectsImport).toHaveBeenCalledWith({ - headers: expect.anything(), + request: expect.anything(), createNewCopies: false, overwrite: false, }); diff --git a/src/core/server/saved_objects/routes/integration_tests/resolve_import_errors.test.ts b/src/core/server/saved_objects/routes/integration_tests/resolve_import_errors.test.ts index 46f4d2435bf67..f135e34231cb6 100644 --- a/src/core/server/saved_objects/routes/integration_tests/resolve_import_errors.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/resolve_import_errors.test.ts @@ -82,7 +82,7 @@ describe(`POST ${URL}`, () => { const router = httpSetup.createRouter('/api/saved_objects/'); coreUsageStatsClient = coreUsageStatsClientMock.create(); coreUsageStatsClient.incrementSavedObjectsResolveImportErrors.mockRejectedValue( - new Error('Oh no!') // this error is intentionally swallowed so the export does not fail + new Error('Oh no!') // intentionally throw this error, which is swallowed, so we can assert that the operation does not fail ); const coreUsageData = coreUsageDataServiceMock.createSetupContract(coreUsageStatsClient); registerResolveImportErrorsRoute(router, { config, coreUsageData }); @@ -117,7 +117,7 @@ describe(`POST ${URL}`, () => { expect(result.body).toEqual({ success: true, successCount: 0 }); expect(savedObjectsClient.bulkCreate).not.toHaveBeenCalled(); // no objects were created expect(coreUsageStatsClient.incrementSavedObjectsResolveImportErrors).toHaveBeenCalledWith({ - headers: expect.anything(), + request: expect.anything(), createNewCopies: false, }); }); diff --git a/src/core/server/saved_objects/routes/integration_tests/update.test.ts b/src/core/server/saved_objects/routes/integration_tests/update.test.ts index dfccb651d72d7..433ffb49e05e4 100644 --- a/src/core/server/saved_objects/routes/integration_tests/update.test.ts +++ b/src/core/server/saved_objects/routes/integration_tests/update.test.ts @@ -21,6 +21,9 @@ import supertest from 'supertest'; import { UnwrapPromise } from '@kbn/utility-types'; import { registerUpdateRoute } from '../update'; import { savedObjectsClientMock } from '../../../../../core/server/mocks'; +import { CoreUsageStatsClient } from '../../../core_usage_data'; +import { coreUsageStatsClientMock } from '../../../core_usage_data/core_usage_stats_client.mock'; +import { coreUsageDataServiceMock } from '../../../core_usage_data/core_usage_data_service.mock'; import { setupServer } from '../test_utils'; type SetupServerReturn = UnwrapPromise>; @@ -30,6 +33,7 @@ describe('PUT /api/saved_objects/{type}/{id?}', () => { let httpSetup: SetupServerReturn['httpSetup']; let handlerContext: SetupServerReturn['handlerContext']; let savedObjectsClient: ReturnType; + let coreUsageStatsClient: jest.Mocked; beforeEach(async () => { const clientResponse = { @@ -47,7 +51,10 @@ describe('PUT /api/saved_objects/{type}/{id?}', () => { savedObjectsClient.update.mockResolvedValue(clientResponse); const router = httpSetup.createRouter('/api/saved_objects/'); - registerUpdateRoute(router); + coreUsageStatsClient = coreUsageStatsClientMock.create(); + coreUsageStatsClient.incrementSavedObjectsUpdate.mockRejectedValue(new Error('Oh no!')); // intentionally throw this error, which is swallowed, so we can assert that the operation does not fail + const coreUsageData = coreUsageDataServiceMock.createSetupContract(coreUsageStatsClient); + registerUpdateRoute(router, { coreUsageData }); await server.start(); }); @@ -56,7 +63,7 @@ describe('PUT /api/saved_objects/{type}/{id?}', () => { await server.stop(); }); - it('formats successful response', async () => { + it('formats successful response and records usage stats', async () => { const clientResponse = { id: 'logstash-*', title: 'logstash-*', @@ -79,6 +86,9 @@ describe('PUT /api/saved_objects/{type}/{id?}', () => { .expect(200); expect(result.body).toEqual(clientResponse); + expect(coreUsageStatsClient.incrementSavedObjectsUpdate).toHaveBeenCalledWith({ + request: expect.anything(), + }); }); it('calls upon savedObjectClient.update', async () => { diff --git a/src/core/server/saved_objects/routes/resolve_import_errors.ts b/src/core/server/saved_objects/routes/resolve_import_errors.ts index 34c178a975304..5db5454b224d7 100644 --- a/src/core/server/saved_objects/routes/resolve_import_errors.ts +++ b/src/core/server/saved_objects/routes/resolve_import_errors.ts @@ -83,10 +83,9 @@ export const registerResolveImportErrorsRoute = ( router.handleLegacyErrors(async (context, req, res) => { const { createNewCopies } = req.query; - const { headers } = req; const usageStatsClient = coreUsageData.getClient(); usageStatsClient - .incrementSavedObjectsResolveImportErrors({ headers, createNewCopies }) + .incrementSavedObjectsResolveImportErrors({ request: req, createNewCopies }) .catch(() => {}); const file = req.body.file as FileStream; diff --git a/src/core/server/saved_objects/routes/update.ts b/src/core/server/saved_objects/routes/update.ts index c0d94d362e648..95137cb6e77cf 100644 --- a/src/core/server/saved_objects/routes/update.ts +++ b/src/core/server/saved_objects/routes/update.ts @@ -19,8 +19,13 @@ import { schema } from '@kbn/config-schema'; import { IRouter } from '../../http'; +import { CoreUsageDataSetup } from '../../core_usage_data'; -export const registerUpdateRoute = (router: IRouter) => { +interface RouteDependencies { + coreUsageData: CoreUsageDataSetup; +} + +export const registerUpdateRoute = (router: IRouter, { coreUsageData }: RouteDependencies) => { router.put( { path: '/{type}/{id}', @@ -49,6 +54,9 @@ export const registerUpdateRoute = (router: IRouter) => { const { attributes, version, references } = req.body; const options = { version, references }; + const usageStatsClient = coreUsageData.getClient(); + usageStatsClient.incrementSavedObjectsUpdate({ request: req }).catch(() => {}); + const result = await context.core.savedObjects.client.update(type, id, attributes, options); return res.ok({ body: result }); }) diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index a39bbecd16ff5..1ab06b7912d1f 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -541,24 +541,138 @@ export interface CoreUsageDataStart { // @internal export interface CoreUsageStats { + // (undocumented) + 'apiCalls.savedObjectsBulkCreate.namespace.custom.kibanaRequest.no'?: number; + // (undocumented) + 'apiCalls.savedObjectsBulkCreate.namespace.custom.kibanaRequest.yes'?: number; + // (undocumented) + 'apiCalls.savedObjectsBulkCreate.namespace.custom.total'?: number; + // (undocumented) + 'apiCalls.savedObjectsBulkCreate.namespace.default.kibanaRequest.no'?: number; + // (undocumented) + 'apiCalls.savedObjectsBulkCreate.namespace.default.kibanaRequest.yes'?: number; + // (undocumented) + 'apiCalls.savedObjectsBulkCreate.namespace.default.total'?: number; + // (undocumented) + 'apiCalls.savedObjectsBulkCreate.total'?: number; + // (undocumented) + 'apiCalls.savedObjectsBulkGet.namespace.custom.kibanaRequest.no'?: number; + // (undocumented) + 'apiCalls.savedObjectsBulkGet.namespace.custom.kibanaRequest.yes'?: number; + // (undocumented) + 'apiCalls.savedObjectsBulkGet.namespace.custom.total'?: number; + // (undocumented) + 'apiCalls.savedObjectsBulkGet.namespace.default.kibanaRequest.no'?: number; + // (undocumented) + 'apiCalls.savedObjectsBulkGet.namespace.default.kibanaRequest.yes'?: number; + // (undocumented) + 'apiCalls.savedObjectsBulkGet.namespace.default.total'?: number; + // (undocumented) + 'apiCalls.savedObjectsBulkGet.total'?: number; + // (undocumented) + 'apiCalls.savedObjectsBulkUpdate.namespace.custom.kibanaRequest.no'?: number; + // (undocumented) + 'apiCalls.savedObjectsBulkUpdate.namespace.custom.kibanaRequest.yes'?: number; + // (undocumented) + 'apiCalls.savedObjectsBulkUpdate.namespace.custom.total'?: number; + // (undocumented) + 'apiCalls.savedObjectsBulkUpdate.namespace.default.kibanaRequest.no'?: number; + // (undocumented) + 'apiCalls.savedObjectsBulkUpdate.namespace.default.kibanaRequest.yes'?: number; + // (undocumented) + 'apiCalls.savedObjectsBulkUpdate.namespace.default.total'?: number; + // (undocumented) + 'apiCalls.savedObjectsBulkUpdate.total'?: number; + // (undocumented) + 'apiCalls.savedObjectsCreate.namespace.custom.kibanaRequest.no'?: number; + // (undocumented) + 'apiCalls.savedObjectsCreate.namespace.custom.kibanaRequest.yes'?: number; + // (undocumented) + 'apiCalls.savedObjectsCreate.namespace.custom.total'?: number; + // (undocumented) + 'apiCalls.savedObjectsCreate.namespace.default.kibanaRequest.no'?: number; + // (undocumented) + 'apiCalls.savedObjectsCreate.namespace.default.kibanaRequest.yes'?: number; + // (undocumented) + 'apiCalls.savedObjectsCreate.namespace.default.total'?: number; + // (undocumented) + 'apiCalls.savedObjectsCreate.total'?: number; + // (undocumented) + 'apiCalls.savedObjectsDelete.namespace.custom.kibanaRequest.no'?: number; + // (undocumented) + 'apiCalls.savedObjectsDelete.namespace.custom.kibanaRequest.yes'?: number; + // (undocumented) + 'apiCalls.savedObjectsDelete.namespace.custom.total'?: number; + // (undocumented) + 'apiCalls.savedObjectsDelete.namespace.default.kibanaRequest.no'?: number; + // (undocumented) + 'apiCalls.savedObjectsDelete.namespace.default.kibanaRequest.yes'?: number; + // (undocumented) + 'apiCalls.savedObjectsDelete.namespace.default.total'?: number; + // (undocumented) + 'apiCalls.savedObjectsDelete.total'?: number; // (undocumented) 'apiCalls.savedObjectsExport.allTypesSelected.no'?: number; // (undocumented) 'apiCalls.savedObjectsExport.allTypesSelected.yes'?: number; // (undocumented) - 'apiCalls.savedObjectsExport.kibanaRequest.no'?: number; + 'apiCalls.savedObjectsExport.namespace.custom.kibanaRequest.no'?: number; + // (undocumented) + 'apiCalls.savedObjectsExport.namespace.custom.kibanaRequest.yes'?: number; + // (undocumented) + 'apiCalls.savedObjectsExport.namespace.custom.total'?: number; + // (undocumented) + 'apiCalls.savedObjectsExport.namespace.default.kibanaRequest.no'?: number; + // (undocumented) + 'apiCalls.savedObjectsExport.namespace.default.kibanaRequest.yes'?: number; // (undocumented) - 'apiCalls.savedObjectsExport.kibanaRequest.yes'?: number; + 'apiCalls.savedObjectsExport.namespace.default.total'?: number; // (undocumented) 'apiCalls.savedObjectsExport.total'?: number; // (undocumented) + 'apiCalls.savedObjectsFind.namespace.custom.kibanaRequest.no'?: number; + // (undocumented) + 'apiCalls.savedObjectsFind.namespace.custom.kibanaRequest.yes'?: number; + // (undocumented) + 'apiCalls.savedObjectsFind.namespace.custom.total'?: number; + // (undocumented) + 'apiCalls.savedObjectsFind.namespace.default.kibanaRequest.no'?: number; + // (undocumented) + 'apiCalls.savedObjectsFind.namespace.default.kibanaRequest.yes'?: number; + // (undocumented) + 'apiCalls.savedObjectsFind.namespace.default.total'?: number; + // (undocumented) + 'apiCalls.savedObjectsFind.total'?: number; + // (undocumented) + 'apiCalls.savedObjectsGet.namespace.custom.kibanaRequest.no'?: number; + // (undocumented) + 'apiCalls.savedObjectsGet.namespace.custom.kibanaRequest.yes'?: number; + // (undocumented) + 'apiCalls.savedObjectsGet.namespace.custom.total'?: number; + // (undocumented) + 'apiCalls.savedObjectsGet.namespace.default.kibanaRequest.no'?: number; + // (undocumented) + 'apiCalls.savedObjectsGet.namespace.default.kibanaRequest.yes'?: number; + // (undocumented) + 'apiCalls.savedObjectsGet.namespace.default.total'?: number; + // (undocumented) + 'apiCalls.savedObjectsGet.total'?: number; + // (undocumented) 'apiCalls.savedObjectsImport.createNewCopiesEnabled.no'?: number; // (undocumented) 'apiCalls.savedObjectsImport.createNewCopiesEnabled.yes'?: number; // (undocumented) - 'apiCalls.savedObjectsImport.kibanaRequest.no'?: number; + 'apiCalls.savedObjectsImport.namespace.custom.kibanaRequest.no'?: number; + // (undocumented) + 'apiCalls.savedObjectsImport.namespace.custom.kibanaRequest.yes'?: number; + // (undocumented) + 'apiCalls.savedObjectsImport.namespace.custom.total'?: number; + // (undocumented) + 'apiCalls.savedObjectsImport.namespace.default.kibanaRequest.no'?: number; // (undocumented) - 'apiCalls.savedObjectsImport.kibanaRequest.yes'?: number; + 'apiCalls.savedObjectsImport.namespace.default.kibanaRequest.yes'?: number; + // (undocumented) + 'apiCalls.savedObjectsImport.namespace.default.total'?: number; // (undocumented) 'apiCalls.savedObjectsImport.overwriteEnabled.no'?: number; // (undocumented) @@ -570,11 +684,33 @@ export interface CoreUsageStats { // (undocumented) 'apiCalls.savedObjectsResolveImportErrors.createNewCopiesEnabled.yes'?: number; // (undocumented) - 'apiCalls.savedObjectsResolveImportErrors.kibanaRequest.no'?: number; + 'apiCalls.savedObjectsResolveImportErrors.namespace.custom.kibanaRequest.no'?: number; + // (undocumented) + 'apiCalls.savedObjectsResolveImportErrors.namespace.custom.kibanaRequest.yes'?: number; + // (undocumented) + 'apiCalls.savedObjectsResolveImportErrors.namespace.custom.total'?: number; + // (undocumented) + 'apiCalls.savedObjectsResolveImportErrors.namespace.default.kibanaRequest.no'?: number; // (undocumented) - 'apiCalls.savedObjectsResolveImportErrors.kibanaRequest.yes'?: number; + 'apiCalls.savedObjectsResolveImportErrors.namespace.default.kibanaRequest.yes'?: number; + // (undocumented) + 'apiCalls.savedObjectsResolveImportErrors.namespace.default.total'?: number; // (undocumented) 'apiCalls.savedObjectsResolveImportErrors.total'?: number; + // (undocumented) + 'apiCalls.savedObjectsUpdate.namespace.custom.kibanaRequest.no'?: number; + // (undocumented) + 'apiCalls.savedObjectsUpdate.namespace.custom.kibanaRequest.yes'?: number; + // (undocumented) + 'apiCalls.savedObjectsUpdate.namespace.custom.total'?: number; + // (undocumented) + 'apiCalls.savedObjectsUpdate.namespace.default.kibanaRequest.no'?: number; + // (undocumented) + 'apiCalls.savedObjectsUpdate.namespace.default.kibanaRequest.yes'?: number; + // (undocumented) + 'apiCalls.savedObjectsUpdate.namespace.default.total'?: number; + // (undocumented) + 'apiCalls.savedObjectsUpdate.total'?: number; } // @public (undocumented) diff --git a/src/core/server/server.ts b/src/core/server/server.ts index 75530e557de04..08f0a191151dd 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -166,6 +166,7 @@ export class Server { const metricsSetup = await this.metrics.setup({ http: httpSetup }); const coreUsageDataSetup = this.coreUsageData.setup({ + http: httpSetup, metrics: metricsSetup, savedObjectsStartPromise: this.savedObjectsStartPromise, }); diff --git a/src/plugins/kibana_usage_collection/server/collectors/core/core_usage_collector.ts b/src/plugins/kibana_usage_collection/server/collectors/core/core_usage_collector.ts index d30a3c5ab6861..a0960b30a2e87 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/core/core_usage_collector.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/core/core_usage_collector.ts @@ -115,21 +115,99 @@ export function getCoreUsageCollector( }, }, }, + // Saved Objects Client APIs + 'apiCalls.savedObjectsBulkCreate.total': { type: 'long' }, + 'apiCalls.savedObjectsBulkCreate.namespace.default.total': { type: 'long' }, + 'apiCalls.savedObjectsBulkCreate.namespace.default.kibanaRequest.yes': { type: 'long' }, + 'apiCalls.savedObjectsBulkCreate.namespace.default.kibanaRequest.no': { type: 'long' }, + 'apiCalls.savedObjectsBulkCreate.namespace.custom.total': { type: 'long' }, + 'apiCalls.savedObjectsBulkCreate.namespace.custom.kibanaRequest.yes': { type: 'long' }, + 'apiCalls.savedObjectsBulkCreate.namespace.custom.kibanaRequest.no': { type: 'long' }, + 'apiCalls.savedObjectsBulkGet.total': { type: 'long' }, + 'apiCalls.savedObjectsBulkGet.namespace.default.total': { type: 'long' }, + 'apiCalls.savedObjectsBulkGet.namespace.default.kibanaRequest.yes': { type: 'long' }, + 'apiCalls.savedObjectsBulkGet.namespace.default.kibanaRequest.no': { type: 'long' }, + 'apiCalls.savedObjectsBulkGet.namespace.custom.total': { type: 'long' }, + 'apiCalls.savedObjectsBulkGet.namespace.custom.kibanaRequest.yes': { type: 'long' }, + 'apiCalls.savedObjectsBulkGet.namespace.custom.kibanaRequest.no': { type: 'long' }, + 'apiCalls.savedObjectsBulkUpdate.total': { type: 'long' }, + 'apiCalls.savedObjectsBulkUpdate.namespace.default.total': { type: 'long' }, + 'apiCalls.savedObjectsBulkUpdate.namespace.default.kibanaRequest.yes': { type: 'long' }, + 'apiCalls.savedObjectsBulkUpdate.namespace.default.kibanaRequest.no': { type: 'long' }, + 'apiCalls.savedObjectsBulkUpdate.namespace.custom.total': { type: 'long' }, + 'apiCalls.savedObjectsBulkUpdate.namespace.custom.kibanaRequest.yes': { type: 'long' }, + 'apiCalls.savedObjectsBulkUpdate.namespace.custom.kibanaRequest.no': { type: 'long' }, + 'apiCalls.savedObjectsCreate.total': { type: 'long' }, + 'apiCalls.savedObjectsCreate.namespace.default.total': { type: 'long' }, + 'apiCalls.savedObjectsCreate.namespace.default.kibanaRequest.yes': { type: 'long' }, + 'apiCalls.savedObjectsCreate.namespace.default.kibanaRequest.no': { type: 'long' }, + 'apiCalls.savedObjectsCreate.namespace.custom.total': { type: 'long' }, + 'apiCalls.savedObjectsCreate.namespace.custom.kibanaRequest.yes': { type: 'long' }, + 'apiCalls.savedObjectsCreate.namespace.custom.kibanaRequest.no': { type: 'long' }, + 'apiCalls.savedObjectsDelete.total': { type: 'long' }, + 'apiCalls.savedObjectsDelete.namespace.default.total': { type: 'long' }, + 'apiCalls.savedObjectsDelete.namespace.default.kibanaRequest.yes': { type: 'long' }, + 'apiCalls.savedObjectsDelete.namespace.default.kibanaRequest.no': { type: 'long' }, + 'apiCalls.savedObjectsDelete.namespace.custom.total': { type: 'long' }, + 'apiCalls.savedObjectsDelete.namespace.custom.kibanaRequest.yes': { type: 'long' }, + 'apiCalls.savedObjectsDelete.namespace.custom.kibanaRequest.no': { type: 'long' }, + 'apiCalls.savedObjectsFind.total': { type: 'long' }, + 'apiCalls.savedObjectsFind.namespace.default.total': { type: 'long' }, + 'apiCalls.savedObjectsFind.namespace.default.kibanaRequest.yes': { type: 'long' }, + 'apiCalls.savedObjectsFind.namespace.default.kibanaRequest.no': { type: 'long' }, + 'apiCalls.savedObjectsFind.namespace.custom.total': { type: 'long' }, + 'apiCalls.savedObjectsFind.namespace.custom.kibanaRequest.yes': { type: 'long' }, + 'apiCalls.savedObjectsFind.namespace.custom.kibanaRequest.no': { type: 'long' }, + 'apiCalls.savedObjectsGet.total': { type: 'long' }, + 'apiCalls.savedObjectsGet.namespace.default.total': { type: 'long' }, + 'apiCalls.savedObjectsGet.namespace.default.kibanaRequest.yes': { type: 'long' }, + 'apiCalls.savedObjectsGet.namespace.default.kibanaRequest.no': { type: 'long' }, + 'apiCalls.savedObjectsGet.namespace.custom.total': { type: 'long' }, + 'apiCalls.savedObjectsGet.namespace.custom.kibanaRequest.yes': { type: 'long' }, + 'apiCalls.savedObjectsGet.namespace.custom.kibanaRequest.no': { type: 'long' }, + 'apiCalls.savedObjectsUpdate.total': { type: 'long' }, + 'apiCalls.savedObjectsUpdate.namespace.default.total': { type: 'long' }, + 'apiCalls.savedObjectsUpdate.namespace.default.kibanaRequest.yes': { type: 'long' }, + 'apiCalls.savedObjectsUpdate.namespace.default.kibanaRequest.no': { type: 'long' }, + 'apiCalls.savedObjectsUpdate.namespace.custom.total': { type: 'long' }, + 'apiCalls.savedObjectsUpdate.namespace.custom.kibanaRequest.yes': { type: 'long' }, + 'apiCalls.savedObjectsUpdate.namespace.custom.kibanaRequest.no': { type: 'long' }, + // Saved Objects Management APIs 'apiCalls.savedObjectsImport.total': { type: 'long' }, - 'apiCalls.savedObjectsImport.kibanaRequest.yes': { type: 'long' }, - 'apiCalls.savedObjectsImport.kibanaRequest.no': { type: 'long' }, + 'apiCalls.savedObjectsImport.namespace.default.total': { type: 'long' }, + 'apiCalls.savedObjectsImport.namespace.default.kibanaRequest.yes': { type: 'long' }, + 'apiCalls.savedObjectsImport.namespace.default.kibanaRequest.no': { type: 'long' }, + 'apiCalls.savedObjectsImport.namespace.custom.total': { type: 'long' }, + 'apiCalls.savedObjectsImport.namespace.custom.kibanaRequest.yes': { type: 'long' }, + 'apiCalls.savedObjectsImport.namespace.custom.kibanaRequest.no': { type: 'long' }, 'apiCalls.savedObjectsImport.createNewCopiesEnabled.yes': { type: 'long' }, 'apiCalls.savedObjectsImport.createNewCopiesEnabled.no': { type: 'long' }, 'apiCalls.savedObjectsImport.overwriteEnabled.yes': { type: 'long' }, 'apiCalls.savedObjectsImport.overwriteEnabled.no': { type: 'long' }, 'apiCalls.savedObjectsResolveImportErrors.total': { type: 'long' }, - 'apiCalls.savedObjectsResolveImportErrors.kibanaRequest.yes': { type: 'long' }, - 'apiCalls.savedObjectsResolveImportErrors.kibanaRequest.no': { type: 'long' }, + 'apiCalls.savedObjectsResolveImportErrors.namespace.default.total': { type: 'long' }, + 'apiCalls.savedObjectsResolveImportErrors.namespace.default.kibanaRequest.yes': { + type: 'long', + }, + 'apiCalls.savedObjectsResolveImportErrors.namespace.default.kibanaRequest.no': { + type: 'long', + }, + 'apiCalls.savedObjectsResolveImportErrors.namespace.custom.total': { type: 'long' }, + 'apiCalls.savedObjectsResolveImportErrors.namespace.custom.kibanaRequest.yes': { + type: 'long', + }, + 'apiCalls.savedObjectsResolveImportErrors.namespace.custom.kibanaRequest.no': { + type: 'long', + }, 'apiCalls.savedObjectsResolveImportErrors.createNewCopiesEnabled.yes': { type: 'long' }, 'apiCalls.savedObjectsResolveImportErrors.createNewCopiesEnabled.no': { type: 'long' }, 'apiCalls.savedObjectsExport.total': { type: 'long' }, - 'apiCalls.savedObjectsExport.kibanaRequest.yes': { type: 'long' }, - 'apiCalls.savedObjectsExport.kibanaRequest.no': { type: 'long' }, + 'apiCalls.savedObjectsExport.namespace.default.total': { type: 'long' }, + 'apiCalls.savedObjectsExport.namespace.default.kibanaRequest.yes': { type: 'long' }, + 'apiCalls.savedObjectsExport.namespace.default.kibanaRequest.no': { type: 'long' }, + 'apiCalls.savedObjectsExport.namespace.custom.total': { type: 'long' }, + 'apiCalls.savedObjectsExport.namespace.custom.kibanaRequest.yes': { type: 'long' }, + 'apiCalls.savedObjectsExport.namespace.custom.kibanaRequest.no': { type: 'long' }, 'apiCalls.savedObjectsExport.allTypesSelected.yes': { type: 'long' }, 'apiCalls.savedObjectsExport.allTypesSelected.no': { type: 'long' }, }, diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json index 55384329f9af7..d486c06568c1b 100644 --- a/src/plugins/telemetry/schema/oss_plugins.json +++ b/src/plugins/telemetry/schema/oss_plugins.json @@ -1517,13 +1517,193 @@ } } }, + "apiCalls.savedObjectsBulkCreate.total": { + "type": "long" + }, + "apiCalls.savedObjectsBulkCreate.namespace.default.total": { + "type": "long" + }, + "apiCalls.savedObjectsBulkCreate.namespace.default.kibanaRequest.yes": { + "type": "long" + }, + "apiCalls.savedObjectsBulkCreate.namespace.default.kibanaRequest.no": { + "type": "long" + }, + "apiCalls.savedObjectsBulkCreate.namespace.custom.total": { + "type": "long" + }, + "apiCalls.savedObjectsBulkCreate.namespace.custom.kibanaRequest.yes": { + "type": "long" + }, + "apiCalls.savedObjectsBulkCreate.namespace.custom.kibanaRequest.no": { + "type": "long" + }, + "apiCalls.savedObjectsBulkGet.total": { + "type": "long" + }, + "apiCalls.savedObjectsBulkGet.namespace.default.total": { + "type": "long" + }, + "apiCalls.savedObjectsBulkGet.namespace.default.kibanaRequest.yes": { + "type": "long" + }, + "apiCalls.savedObjectsBulkGet.namespace.default.kibanaRequest.no": { + "type": "long" + }, + "apiCalls.savedObjectsBulkGet.namespace.custom.total": { + "type": "long" + }, + "apiCalls.savedObjectsBulkGet.namespace.custom.kibanaRequest.yes": { + "type": "long" + }, + "apiCalls.savedObjectsBulkGet.namespace.custom.kibanaRequest.no": { + "type": "long" + }, + "apiCalls.savedObjectsBulkUpdate.total": { + "type": "long" + }, + "apiCalls.savedObjectsBulkUpdate.namespace.default.total": { + "type": "long" + }, + "apiCalls.savedObjectsBulkUpdate.namespace.default.kibanaRequest.yes": { + "type": "long" + }, + "apiCalls.savedObjectsBulkUpdate.namespace.default.kibanaRequest.no": { + "type": "long" + }, + "apiCalls.savedObjectsBulkUpdate.namespace.custom.total": { + "type": "long" + }, + "apiCalls.savedObjectsBulkUpdate.namespace.custom.kibanaRequest.yes": { + "type": "long" + }, + "apiCalls.savedObjectsBulkUpdate.namespace.custom.kibanaRequest.no": { + "type": "long" + }, + "apiCalls.savedObjectsCreate.total": { + "type": "long" + }, + "apiCalls.savedObjectsCreate.namespace.default.total": { + "type": "long" + }, + "apiCalls.savedObjectsCreate.namespace.default.kibanaRequest.yes": { + "type": "long" + }, + "apiCalls.savedObjectsCreate.namespace.default.kibanaRequest.no": { + "type": "long" + }, + "apiCalls.savedObjectsCreate.namespace.custom.total": { + "type": "long" + }, + "apiCalls.savedObjectsCreate.namespace.custom.kibanaRequest.yes": { + "type": "long" + }, + "apiCalls.savedObjectsCreate.namespace.custom.kibanaRequest.no": { + "type": "long" + }, + "apiCalls.savedObjectsDelete.total": { + "type": "long" + }, + "apiCalls.savedObjectsDelete.namespace.default.total": { + "type": "long" + }, + "apiCalls.savedObjectsDelete.namespace.default.kibanaRequest.yes": { + "type": "long" + }, + "apiCalls.savedObjectsDelete.namespace.default.kibanaRequest.no": { + "type": "long" + }, + "apiCalls.savedObjectsDelete.namespace.custom.total": { + "type": "long" + }, + "apiCalls.savedObjectsDelete.namespace.custom.kibanaRequest.yes": { + "type": "long" + }, + "apiCalls.savedObjectsDelete.namespace.custom.kibanaRequest.no": { + "type": "long" + }, + "apiCalls.savedObjectsFind.total": { + "type": "long" + }, + "apiCalls.savedObjectsFind.namespace.default.total": { + "type": "long" + }, + "apiCalls.savedObjectsFind.namespace.default.kibanaRequest.yes": { + "type": "long" + }, + "apiCalls.savedObjectsFind.namespace.default.kibanaRequest.no": { + "type": "long" + }, + "apiCalls.savedObjectsFind.namespace.custom.total": { + "type": "long" + }, + "apiCalls.savedObjectsFind.namespace.custom.kibanaRequest.yes": { + "type": "long" + }, + "apiCalls.savedObjectsFind.namespace.custom.kibanaRequest.no": { + "type": "long" + }, + "apiCalls.savedObjectsGet.total": { + "type": "long" + }, + "apiCalls.savedObjectsGet.namespace.default.total": { + "type": "long" + }, + "apiCalls.savedObjectsGet.namespace.default.kibanaRequest.yes": { + "type": "long" + }, + "apiCalls.savedObjectsGet.namespace.default.kibanaRequest.no": { + "type": "long" + }, + "apiCalls.savedObjectsGet.namespace.custom.total": { + "type": "long" + }, + "apiCalls.savedObjectsGet.namespace.custom.kibanaRequest.yes": { + "type": "long" + }, + "apiCalls.savedObjectsGet.namespace.custom.kibanaRequest.no": { + "type": "long" + }, + "apiCalls.savedObjectsUpdate.total": { + "type": "long" + }, + "apiCalls.savedObjectsUpdate.namespace.default.total": { + "type": "long" + }, + "apiCalls.savedObjectsUpdate.namespace.default.kibanaRequest.yes": { + "type": "long" + }, + "apiCalls.savedObjectsUpdate.namespace.default.kibanaRequest.no": { + "type": "long" + }, + "apiCalls.savedObjectsUpdate.namespace.custom.total": { + "type": "long" + }, + "apiCalls.savedObjectsUpdate.namespace.custom.kibanaRequest.yes": { + "type": "long" + }, + "apiCalls.savedObjectsUpdate.namespace.custom.kibanaRequest.no": { + "type": "long" + }, "apiCalls.savedObjectsImport.total": { "type": "long" }, - "apiCalls.savedObjectsImport.kibanaRequest.yes": { + "apiCalls.savedObjectsImport.namespace.default.total": { + "type": "long" + }, + "apiCalls.savedObjectsImport.namespace.default.kibanaRequest.yes": { + "type": "long" + }, + "apiCalls.savedObjectsImport.namespace.default.kibanaRequest.no": { + "type": "long" + }, + "apiCalls.savedObjectsImport.namespace.custom.total": { + "type": "long" + }, + "apiCalls.savedObjectsImport.namespace.custom.kibanaRequest.yes": { "type": "long" }, - "apiCalls.savedObjectsImport.kibanaRequest.no": { + "apiCalls.savedObjectsImport.namespace.custom.kibanaRequest.no": { "type": "long" }, "apiCalls.savedObjectsImport.createNewCopiesEnabled.yes": { @@ -1541,10 +1721,22 @@ "apiCalls.savedObjectsResolveImportErrors.total": { "type": "long" }, - "apiCalls.savedObjectsResolveImportErrors.kibanaRequest.yes": { + "apiCalls.savedObjectsResolveImportErrors.namespace.default.total": { + "type": "long" + }, + "apiCalls.savedObjectsResolveImportErrors.namespace.default.kibanaRequest.yes": { + "type": "long" + }, + "apiCalls.savedObjectsResolveImportErrors.namespace.default.kibanaRequest.no": { + "type": "long" + }, + "apiCalls.savedObjectsResolveImportErrors.namespace.custom.total": { + "type": "long" + }, + "apiCalls.savedObjectsResolveImportErrors.namespace.custom.kibanaRequest.yes": { "type": "long" }, - "apiCalls.savedObjectsResolveImportErrors.kibanaRequest.no": { + "apiCalls.savedObjectsResolveImportErrors.namespace.custom.kibanaRequest.no": { "type": "long" }, "apiCalls.savedObjectsResolveImportErrors.createNewCopiesEnabled.yes": { @@ -1556,10 +1748,22 @@ "apiCalls.savedObjectsExport.total": { "type": "long" }, - "apiCalls.savedObjectsExport.kibanaRequest.yes": { + "apiCalls.savedObjectsExport.namespace.default.total": { + "type": "long" + }, + "apiCalls.savedObjectsExport.namespace.default.kibanaRequest.yes": { + "type": "long" + }, + "apiCalls.savedObjectsExport.namespace.default.kibanaRequest.no": { + "type": "long" + }, + "apiCalls.savedObjectsExport.namespace.custom.total": { + "type": "long" + }, + "apiCalls.savedObjectsExport.namespace.custom.kibanaRequest.yes": { "type": "long" }, - "apiCalls.savedObjectsExport.kibanaRequest.no": { + "apiCalls.savedObjectsExport.namespace.custom.kibanaRequest.no": { "type": "long" }, "apiCalls.savedObjectsExport.allTypesSelected.yes": { diff --git a/test/api_integration/apis/saved_objects/bulk_create.js b/test/api_integration/apis/saved_objects/bulk_create.js index 7db968df8357a..a78acea1d0299 100644 --- a/test/api_integration/apis/saved_objects/bulk_create.js +++ b/test/api_integration/apis/saved_objects/bulk_create.js @@ -68,7 +68,7 @@ export default function ({ getService }) { type: 'dashboard', id: 'a01b2f57-fcfd-4864-b735-09e28f0d815e', updated_at: resp.body.saved_objects[1].updated_at, - version: 'WzgsMV0=', + version: resp.body.saved_objects[1].version, attributes: { title: 'A great new dashboard', }, @@ -117,7 +117,7 @@ export default function ({ getService }) { type: 'visualization', id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab', updated_at: resp.body.saved_objects[0].updated_at, - version: 'WzAsMV0=', + version: resp.body.saved_objects[0].version, attributes: { title: 'An existing visualization', }, @@ -131,7 +131,7 @@ export default function ({ getService }) { type: 'dashboard', id: 'a01b2f57-fcfd-4864-b735-09e28f0d815e', updated_at: resp.body.saved_objects[1].updated_at, - version: 'WzEsMV0=', + version: resp.body.saved_objects[1].version, attributes: { title: 'A great new dashboard', }, diff --git a/test/api_integration/apis/saved_objects/bulk_update.js b/test/api_integration/apis/saved_objects/bulk_update.js index 973ce382ea813..58c72575c04bb 100644 --- a/test/api_integration/apis/saved_objects/bulk_update.js +++ b/test/api_integration/apis/saved_objects/bulk_update.js @@ -61,7 +61,7 @@ export default function ({ getService }) { expect(_.omit(firstObject, ['updated_at'])).to.eql({ id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab', type: 'visualization', - version: 'WzgsMV0=', + version: firstObject.version, attributes: { title: 'An existing visualization', }, @@ -74,7 +74,7 @@ export default function ({ getService }) { expect(_.omit(secondObject, ['updated_at'])).to.eql({ id: 'be3733a0-9efe-11e7-acb3-3dab96693fab', type: 'dashboard', - version: 'WzksMV0=', + version: secondObject.version, attributes: { title: 'An existing dashboard', }, diff --git a/test/api_integration/apis/saved_objects/create.js b/test/api_integration/apis/saved_objects/create.js index c1300125441bc..15aecb6e547a0 100644 --- a/test/api_integration/apis/saved_objects/create.js +++ b/test/api_integration/apis/saved_objects/create.js @@ -53,7 +53,7 @@ export default function ({ getService }) { type: 'visualization', migrationVersion: resp.body.migrationVersion, updated_at: resp.body.updated_at, - version: 'WzgsMV0=', + version: resp.body.version, attributes: { title: 'My favorite vis', }, @@ -100,7 +100,7 @@ export default function ({ getService }) { type: 'visualization', migrationVersion: resp.body.migrationVersion, updated_at: resp.body.updated_at, - version: 'WzAsMV0=', + version: resp.body.version, attributes: { title: 'My favorite vis', }, diff --git a/test/api_integration/apis/saved_objects/update.js b/test/api_integration/apis/saved_objects/update.js index 7803c39897f28..14b363a512ea1 100644 --- a/test/api_integration/apis/saved_objects/update.js +++ b/test/api_integration/apis/saved_objects/update.js @@ -52,7 +52,7 @@ export default function ({ getService }) { id: resp.body.id, type: 'visualization', updated_at: resp.body.updated_at, - version: 'WzgsMV0=', + version: resp.body.version, attributes: { title: 'My second favorite vis', }, diff --git a/x-pack/plugins/spaces/server/usage_stats/usage_stats_client.test.ts b/x-pack/plugins/spaces/server/usage_stats/usage_stats_client.test.ts index b313c0be32b95..20d38d5809149 100644 --- a/x-pack/plugins/spaces/server/usage_stats/usage_stats_client.test.ts +++ b/x-pack/plugins/spaces/server/usage_stats/usage_stats_client.test.ts @@ -22,7 +22,7 @@ describe('UsageStatsClient', () => { return { usageStatsClient, debugLoggerMock, repositoryMock }; }; - const firstPartyRequestHeaders = { 'kbn-version': 'a', origin: 'b', referer: 'c' }; // as long as these three header fields are truthy, this will be treated like a first-party request + const firstPartyRequestHeaders = { 'kbn-version': 'a', referer: 'b' }; // as long as these two header fields are truthy, this will be treated like a first-party request const incrementOptions = { refresh: false }; describe('#getUsageStats', () => { diff --git a/x-pack/plugins/spaces/server/usage_stats/usage_stats_client.ts b/x-pack/plugins/spaces/server/usage_stats/usage_stats_client.ts index 4c9d11a11ccca..22e8bb5c4d39a 100644 --- a/x-pack/plugins/spaces/server/usage_stats/usage_stats_client.ts +++ b/x-pack/plugins/spaces/server/usage_stats/usage_stats_client.ts @@ -102,7 +102,7 @@ export class UsageStatsClient { } function getIsKibanaRequest(headers?: Headers) { - // The presence of these three request headers gives us a good indication that this is a first-party request from the Kibana client. + // The presence of these two request headers gives us a good indication that this is a first-party request from the Kibana client. // We can't be 100% certain, but this is a reasonable attempt. - return headers && headers['kbn-version'] && headers.origin && headers.referer; + return headers && headers['kbn-version'] && headers.referer; } From 84d7b9e4ac01244a0b772d73abcde551d913552c Mon Sep 17 00:00:00 2001 From: Chandler Prall Date: Mon, 14 Dec 2020 15:18:29 -0700 Subject: [PATCH 52/95] Upgrade EUI to v30.6.0 and update jest snapshots (#85504) --- package.json | 2 +- .../collapsible_nav.test.tsx.snap | 2 ++ .../header/__snapshots__/header.test.tsx.snap | 1 + .../dashboard_empty_screen.test.tsx.snap | 5 +++ .../saved_objects_installer.test.js.snap | 12 +++++++ .../header/__snapshots__/header.test.tsx.snap | 3 ++ .../warning_call_out.test.tsx.snap | 2 ++ .../inspector_panel.test.tsx.snap | 1 + .../__snapshots__/header.test.tsx.snap | 4 +++ .../__test__/__snapshots__/List.test.tsx.snap | 2 ++ .../time_filter.stories.storyshot | 5 +++ .../__snapshots__/asset.stories.storyshot | 8 +++++ .../asset_manager.stories.storyshot | 12 +++++++ .../color_palette.stories.storyshot | 36 +++++++++++++++++++ .../color_picker.stories.storyshot | 21 +++++++++++ .../color_picker_popover.stories.storyshot | 4 +++ .../custom_element_modal.stories.storyshot | 12 +++++++ .../datasource_component.stories.storyshot | 3 ++ .../element_card.stories.storyshot | 2 ++ .../keyboard_shortcuts_doc.stories.storyshot | 1 + .../element_controls.stories.storyshot | 2 ++ .../element_grid.stories.storyshot | 9 +++++ .../saved_elements_modal.stories.storyshot | 18 ++++++++++ .../shape_picker.stories.storyshot | 2 ++ .../shape_picker_popover.stories.storyshot | 3 ++ .../sidebar_header.stories.storyshot | 4 +++ .../text_style_picker.stories.storyshot | 8 +++++ .../__snapshots__/toolbar.stories.storyshot | 5 +++ .../delete_var.stories.storyshot | 2 ++ .../__snapshots__/edit_var.stories.storyshot | 4 +++ .../var_config.stories.storyshot | 1 + .../__snapshots__/edit_menu.stories.storyshot | 6 ++++ .../element_menu.stories.storyshot | 1 + .../__snapshots__/pdf_panel.stories.storyshot | 1 + .../share_menu.stories.storyshot | 1 + .../__snapshots__/view_menu.stories.storyshot | 4 +++ .../workpad_templates.stories.storyshot | 4 +++ .../extended_template.stories.storyshot | 3 ++ .../simple_template.stories.storyshot | 2 ++ .../simple_template.stories.storyshot | 5 +++ .../__snapshots__/canvas.stories.storyshot | 6 ++++ .../__snapshots__/footer.stories.storyshot | 4 +++ .../page_controls.stories.storyshot | 3 ++ .../__snapshots__/settings.stories.storyshot | 2 ++ .../extend_index_management.test.tsx.snap | 2 ++ .../upload_license.test.tsx.snap | 20 +++++++++++ .../upgrade_failure.test.js.snap | 12 +++++++ .../collection_enabled.test.js.snap | 3 ++ .../collection_interval.test.js.snap | 3 ++ .../remote_cluster_form.test.js.snap | 3 ++ .../report_info_button.test.tsx.snap | 5 +++ .../overwritten_session_page.test.tsx.snap | 1 + .../__snapshots__/link_to_app.test.tsx.snap | 1 + .../__snapshots__/index.test.tsx.snap | 2 ++ .../__snapshots__/empty_state.test.tsx.snap | 6 ++++ yarn.lock | 11 +++--- 56 files changed, 301 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index ba6ac1e70248b..9ee9df67b8aea 100644 --- a/package.json +++ b/package.json @@ -106,7 +106,7 @@ "@elastic/datemath": "link:packages/elastic-datemath", "@elastic/elasticsearch": "7.10.0", "@elastic/ems-client": "7.11.0", - "@elastic/eui": "30.5.1", + "@elastic/eui": "30.6.0", "@elastic/filesaver": "1.1.2", "@elastic/good": "^9.0.1-kibana3", "@elastic/node-crypto": "1.2.1", diff --git a/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap index 201f2e5f8f14b..c836686ec602b 100644 --- a/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap +++ b/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap @@ -1974,6 +1974,7 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` >